import type { Transaction } from '@/types/transaction'; import type { UUID } from '@/types/uuid'; import axios from 'axios'; import { db } from './dexie-db'; export interface SyncResult { success: boolean; inserted: number; updated: number; errors: string[]; } interface SyncOptions { endpoint: string; transformFromServer?: ( data: Record, ) => Record; } const LAST_SYNC_KEY = 'last_sync_transactions'; export class TransactionSyncManager { private syncInProgress = false; private options: SyncOptions; constructor(options: SyncOptions) { this.options = options; } async getLastSyncTime(): Promise { const metadata = await db.sync_metadata.get(LAST_SYNC_KEY); return metadata?.value || null; } async setLastSyncTime(timestamp: string): Promise { await db.sync_metadata.put({ key: LAST_SYNC_KEY, value: timestamp, }); } async sync(): Promise { if (this.syncInProgress) { return { success: false, inserted: 0, updated: 0, errors: ['Sync already in progress'], }; } this.syncInProgress = true; const result: SyncResult = { success: true, inserted: 0, updated: 0, errors: [], }; try { await this.syncFromServer(result); await this.setLastSyncTime(new Date().toISOString()); } catch (error) { result.success = false; result.errors.push( error instanceof Error ? error.message : 'Unknown error', ); } finally { this.syncInProgress = false; } return result; } private async syncFromServer(result: SyncResult): Promise { const lastSync = await this.getLastSyncTime(); const params: Record = {}; if (lastSync) { params.since = lastSync; } const response = await axios.get(this.options.endpoint, { params }); const serverData = response.data.data || response.data; if (!Array.isArray(serverData)) { throw new Error('Invalid server response format'); } const localRecords = await db.transactions.toArray(); const localMap = new Map(localRecords.map((r) => [r.id, r])); const toInsert: Transaction[] = []; const toUpdate: Transaction[] = []; for (const serverRecord of serverData) { const transformed = ( this.options.transformFromServer ? this.options.transformFromServer(serverRecord) : serverRecord ) as Transaction; const localRecord = localMap.get(transformed.id); if (!localRecord) { toInsert.push(transformed); } else { const serverDate = new Date(transformed.updated_at); const localDate = new Date(localRecord.updated_at); if (serverDate > localDate) { toUpdate.push(transformed); } } } if (toInsert.length > 0) { await db.transactions.bulkPut(toInsert); result.inserted += toInsert.length; } if (toUpdate.length > 0) { await db.transactions.bulkPut(toUpdate); result.updated += toUpdate.length; } } async getAll(): Promise { return await db.transactions.toArray(); } async getById(id: UUID): Promise { return (await db.transactions.get(id)) || null; } async getByAccountId(accountId: UUID): Promise { return await db.transactions .where('account_id') .equals(accountId) .toArray(); } isSyncing(): boolean { return this.syncInProgress; } async clearAll(): Promise { await db.transactions.clear(); await db.sync_metadata.delete(LAST_SYNC_KEY); } }