06/05 Optimize app: implement Plaid
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
from datetime import datetime
|
||||
from app.extensions import db
|
||||
from app.utils.crypto import EncryptedText
|
||||
|
||||
|
||||
class PlaidItem(db.Model):
|
||||
"""One Plaid Item = one bank connection (may contain multiple accounts)."""
|
||||
__tablename__ = 'plaid_items'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
item_id = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
||||
access_token = db.Column(EncryptedText, nullable=False)
|
||||
institution_id = db.Column(db.String(50), nullable=True)
|
||||
institution_name = db.Column(db.String(100), nullable=True)
|
||||
cursor = db.Column(db.String(500), nullable=True) # /transactions/sync cursor
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
last_synced_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
accounts = db.relationship('PlaidAccount', back_populates='item',
|
||||
cascade='all, delete-orphan', lazy='dynamic')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<PlaidItem {self.institution_name} ({self.item_id[:12]}…)>'
|
||||
|
||||
|
||||
class PlaidAccount(db.Model):
|
||||
"""Maps one Plaid account inside an item to a PFM account."""
|
||||
__tablename__ = 'plaid_accounts'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
item_id = db.Column(db.Integer, db.ForeignKey('plaid_items.id'), nullable=False)
|
||||
plaid_account_id = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
||||
pfm_account_id = db.Column(db.Integer, db.ForeignKey('accounts.id'), nullable=True)
|
||||
|
||||
account_name = db.Column(db.String(100), nullable=True)
|
||||
account_type = db.Column(db.String(50), nullable=True) # depository / credit / investment
|
||||
account_subtype = db.Column(db.String(50), nullable=True) # checking / savings / credit card
|
||||
mask = db.Column(db.String(10), nullable=True) # last 4 digits
|
||||
|
||||
last_sync_date = db.Column(db.Date, nullable=True)
|
||||
|
||||
# Credit card billing info (populated from /liabilities/get)
|
||||
cc_due_date = db.Column(db.Date, nullable=True)
|
||||
cc_minimum_payment = db.Column(db.Numeric(12, 2), nullable=True)
|
||||
cc_last_statement_balance = db.Column(db.Numeric(12, 2), nullable=True)
|
||||
cc_is_overdue = db.Column(db.Boolean, default=False)
|
||||
cc_updated_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
item = db.relationship('PlaidItem', back_populates='accounts')
|
||||
pfm_account = db.relationship('Account')
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
suffix = f' ••••{self.mask}' if self.mask else ''
|
||||
return f'{self.account_name}{suffix}'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<PlaidAccount {self.account_name} → PFM #{self.pfm_account_id}>'
|
||||
|
||||
|
||||
class PlaidSyncPreview(db.Model):
|
||||
"""Temporary server-side storage for Plaid sync preview data (per item)."""
|
||||
__tablename__ = 'plaid_sync_previews'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
item_id = db.Column(db.Integer, db.ForeignKey('plaid_items.id'),
|
||||
nullable=False, unique=True, index=True)
|
||||
data_json = db.Column(db.Text, nullable=False) # JSON array of parsed transactions
|
||||
next_cursor = db.Column(db.String(500), nullable=True) # advance item cursor on confirm
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<PlaidSyncPreview item_id={self.item_id}>'
|
||||
Reference in New Issue
Block a user