'use strict'; const { getDb } = require('../db/database'); const { getBankSyncConfig } = require('./bankSyncConfigService'); const { syncDataSource } = require('./bankSyncService'); const DEFAULT_INTERVAL_HOURS = 4; // 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 hours = parseFloat(process.env.SIMPLEFIN_SYNC_INTERVAL_HOURS); return Number.isFinite(hours) && hours >= 0.5 ? Math.round(hours * 3600000) : DEFAULT_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(); const hours = intervalMs() / 3600000; console.log(`[bankSync] Auto-sync worker started (interval: ${hours}h)`); } function stop() { clearTimeout(timer); timer = null; } function getStatus() { return { running, interval_hours: intervalMs() / 3600000, last_run_at: lastRunAt, next_run_at: nextRunAt, }; } module.exports = { start, stop, getStatus };