154 lines
4.1 KiB
TypeScript
154 lines
4.1 KiB
TypeScript
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<string, unknown>,
|
|
) => Record<string, unknown>;
|
|
}
|
|
|
|
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<string | null> {
|
|
const metadata = await db.sync_metadata.get(LAST_SYNC_KEY);
|
|
return metadata?.value || null;
|
|
}
|
|
|
|
async setLastSyncTime(timestamp: string): Promise<void> {
|
|
await db.sync_metadata.put({
|
|
key: LAST_SYNC_KEY,
|
|
value: timestamp,
|
|
});
|
|
}
|
|
|
|
async sync(): Promise<SyncResult> {
|
|
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<void> {
|
|
const lastSync = await this.getLastSyncTime();
|
|
|
|
const params: Record<string, string> = {};
|
|
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<Transaction[]> {
|
|
return await db.transactions.toArray();
|
|
}
|
|
|
|
async getById(id: UUID): Promise<Transaction | null> {
|
|
return (await db.transactions.get(id)) || null;
|
|
}
|
|
|
|
async getByAccountId(accountId: UUID): Promise<Transaction[]> {
|
|
return await db.transactions
|
|
.where('account_id')
|
|
.equals(accountId)
|
|
.toArray();
|
|
}
|
|
|
|
isSyncing(): boolean {
|
|
return this.syncInProgress;
|
|
}
|
|
|
|
async clearAll(): Promise<void> {
|
|
await db.transactions.clear();
|
|
await db.sync_metadata.delete(LAST_SYNC_KEY);
|
|
}
|
|
}
|