'use strict'; const { getDb } = require('../db/database'); const { getBankSyncConfig } = require('./bankSyncConfigService'); const { syncDataSource } = require('./bankSyncService'); // Skip a source if it was synced less than this long ago (catches recent manual syncs) const MIN_SYNC_AGE_MS = 60 * 60 * 1000; // 1 hour // Pause between each source to avoid hammering SimpleFIN const STAGGER_DELAY_MS = 3000; let timer = null; let running = false; let lastRunAt = null; let nextRunAt = null; function intervalMs() { const { sync_interval_hours } = getBankSyncConfig(); return Math.round(sync_interval_hours * 3600000); } function needsSync(source) { if (!source.last_sync_at) return true; const age = Date.now() - new Date(source.last_sync_at).getTime(); return age >= MIN_SYNC_AGE_MS; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function runCycle() { if (running) return; const { enabled } = getBankSyncConfig(); if (!enabled) return; let db, sources; try { db = getDb(); sources = db.prepare(` SELECT * FROM data_sources WHERE type = 'provider_sync' AND provider = 'simplefin' ORDER BY last_sync_at ASC `).all(); } catch (err) { console.error('[bankSync] Worker failed to load sources:', err.message); return; } if (sources.length === 0) return; running = true; lastRunAt = new Date().toISOString(); let synced = 0; let skipped = 0; let failed = 0; for (let i = 0; i < sources.length; i++) { const source = sources[i]; if (!needsSync(source)) { skipped++; continue; } try { await syncDataSource(db, source.user_id, source.id); synced++; } catch { // syncDataSource already writes last_error to the data_sources row failed++; } // Stagger requests — don't fire them all simultaneously if (i < sources.length - 1) await sleep(STAGGER_DELAY_MS); } if (synced > 0 || failed > 0) { console.log(`[bankSync] Auto-sync complete: ${synced} synced, ${failed} failed, ${skipped} skipped`); } running = false; } function scheduleNext() { const ms = intervalMs(); nextRunAt = new Date(Date.now() + ms).toISOString(); timer = setTimeout(() => { runCycle() .catch(err => { console.error('[bankSync] Worker cycle error:', err.message); running = false; }) .finally(scheduleNext); }, ms); // Don't hold the event loop open if the server is shutting down if (timer.unref) timer.unref(); } function start() { if (timer) return; scheduleNext(); console.log(`[bankSync] Auto-sync worker started (interval: ${getBankSyncConfig().sync_interval_hours}h)`); } function stop() { clearTimeout(timer); timer = null; } function getStatus() { return { running, interval_hours: getBankSyncConfig().sync_interval_hours, last_run_at: lastRunAt, next_run_at: nextRunAt, }; } module.exports = { start, stop, getStatus };