Private
Public Access
1
0

Implement Phase 6: automated bank sync (Plaid + SimpleFIN) & settings

Add automated transaction syncing via Plaid and SimpleFIN providers,
a Settings page for managing connections and account mappings,
background scheduled syncing, and sync history logging.

New backend: BankSync plugin project with PlaidSyncProvider and
SimpleFinSyncProvider, SyncConnection/LinkedAccount/SyncLog models,
AES-256 encryption service, BankSyncService orchestration,
BankSyncBackgroundService (15-min tick), and BankSyncController
(13 endpoints). EF migration creates sync_connections,
linked_accounts, and sync_logs tables.

New frontend: Settings view with 3 tabs (Profile, Bank Connections,
Sync History), bankSync API service, BankSyncComplete SignalR
notifications, and Settings nav item in sidebar + avatar dropdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-08 15:08:23 -05:00
parent eb49147080
commit 3e0446c9be
34 changed files with 3956 additions and 8 deletions
+52
View File
@@ -0,0 +1,52 @@
import api from './api'
import type {
SyncConnection,
LinkedAccountInfo,
DiscoveredAccount,
SyncResult,
SyncLogEntry,
CreateLinkTokenResponse
} from '@/types'
export const bankSyncApi = {
getConnections: () =>
api.get<SyncConnection[]>('/bank-sync/connections'),
getConnection: (id: string) =>
api.get<SyncConnection>(`/bank-sync/connections/${id}`),
updateConnection: (id: string, data: { isActive: boolean; syncIntervalMinutes: number }) =>
api.put<SyncConnection>(`/bank-sync/connections/${id}`, data),
deleteConnection: (id: string) =>
api.delete(`/bank-sync/connections/${id}`),
createLinkToken: () =>
api.post<CreateLinkTokenResponse>('/bank-sync/plaid/create-link-token'),
completePlaidLink: (publicToken: string) =>
api.post<SyncConnection>('/bank-sync/plaid/complete-link', { publicToken }),
connectSimpleFin: (setupToken: string) =>
api.post<SyncConnection>('/bank-sync/simplefin/connect', { setupToken }),
discoverAccounts: (connectionId: string) =>
api.get<DiscoveredAccount[]>(`/bank-sync/connections/${connectionId}/discover-accounts`),
linkAccount: (connectionId: string, data: { linkedAccountId: string; accountId: string | null; autoCreate: boolean }) =>
api.post<LinkedAccountInfo>(`/bank-sync/connections/${connectionId}/link-account`, data),
updateLinkedAccount: (linkedAccountId: string, data: { accountId: string | null; isEnabled: boolean }) =>
api.put<LinkedAccountInfo>(`/bank-sync/linked-accounts/${linkedAccountId}`, data),
syncNow: (connectionId: string) =>
api.post<SyncResult>(`/bank-sync/connections/${connectionId}/sync-now`),
syncAll: () =>
api.post<SyncResult>('/bank-sync/sync-all'),
getSyncLogs: (connectionId?: string, limit = 50) =>
api.get<SyncLogEntry[]>('/bank-sync/sync-logs', {
params: { connectionId, limit }
}),
}