refactor(sync): centralize sync constants in bankSyncConfigService, wire through config/UI
This commit is contained in:
parent
a73d0afe07
commit
a2ac241cd3
|
|
@ -32,7 +32,7 @@ export default function BankSyncAdminCard() {
|
|||
const [saving, setSaving] = useState(false);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [syncInterval, setSyncInterval] = useState(4);
|
||||
const [syncDays, setSyncDays] = useState(90);
|
||||
const [syncDays, setSyncDays] = useState(30);
|
||||
|
||||
useEffect(() => {
|
||||
api.bankSyncConfig()
|
||||
|
|
@ -40,7 +40,7 @@ export default function BankSyncAdminCard() {
|
|||
setConfig(d);
|
||||
setEnabled(!!d.enabled);
|
||||
setSyncInterval(d.sync_interval_hours ?? 4);
|
||||
setSyncDays(d.sync_days ?? 90);
|
||||
setSyncDays(d.sync_days ?? 30);
|
||||
})
|
||||
.catch(err => setLoadError(err.message || 'Failed to load bank sync config'))
|
||||
.finally(() => setLoading(false));
|
||||
|
|
@ -49,12 +49,13 @@ export default function BankSyncAdminCard() {
|
|||
const handleSave = async () => {
|
||||
const hours = parseFloat(syncInterval);
|
||||
const days = parseInt(syncDays, 10);
|
||||
const maxDays = config?.sync_days_max ?? 45;
|
||||
if (!Number.isFinite(hours) || hours < 0.5 || hours > 168) {
|
||||
toast.error('Sync interval must be between 0.5 and 168 hours.');
|
||||
return;
|
||||
}
|
||||
if (!Number.isFinite(days) || days < 1 || days > 45) {
|
||||
toast.error('Routine sync lookback must be 1–45 days. SimpleFIN Bridge enforces a 45-day hard limit — values above 45 return errors.');
|
||||
if (!Number.isFinite(days) || days < 1 || days > maxDays) {
|
||||
toast.error(`Routine sync lookback must be 1–${maxDays} days. SimpleFIN Bridge enforces a ${maxDays}-day hard limit — values above ${maxDays} return errors.`);
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
|
|
@ -63,7 +64,7 @@ export default function BankSyncAdminCard() {
|
|||
setConfig(result);
|
||||
setEnabled(!!result.enabled);
|
||||
setSyncInterval(result.sync_interval_hours ?? 4);
|
||||
setSyncDays(result.sync_days ?? 90);
|
||||
setSyncDays(result.sync_days ?? 30);
|
||||
toast.success('Bank sync settings saved.');
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'Failed to update bank sync setting.');
|
||||
|
|
@ -96,6 +97,8 @@ export default function BankSyncAdminCard() {
|
|||
|| parseFloat(syncInterval) !== config?.sync_interval_hours
|
||||
|| parseInt(syncDays, 10) !== config?.sync_days;
|
||||
const worker = config?.worker;
|
||||
const seedDays = config?.seed_days ?? 44;
|
||||
const maxDays = config?.sync_days_max ?? 45;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
|
@ -161,12 +164,13 @@ export default function BankSyncAdminCard() {
|
|||
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
|
||||
Initial connect & backfill
|
||||
</p>
|
||||
<span className="font-mono text-sm font-bold">6 days</span>
|
||||
<span className="font-mono text-sm font-bold">{seedDays} days</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The first sync (and any manual backfill) always fetches the maximum 60 days of history
|
||||
to build a complete transaction picture. This is fixed — SimpleFIN Bridge enforces a
|
||||
strict <strong>60-day hard limit</strong> and will return possible errors.
|
||||
The first sync (and any manual backfill) fetches up to <strong>{seedDays} days</strong> of
|
||||
history to build a complete transaction picture. This is fixed — SimpleFIN Bridge enforces a
|
||||
strict <strong>{maxDays}-day hard limit</strong>, so this stays one day under it to avoid
|
||||
latency-related errors.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -184,10 +188,10 @@ export default function BankSyncAdminCard() {
|
|||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="45"
|
||||
max={maxDays}
|
||||
step="1"
|
||||
value={syncDays}
|
||||
onChange={e => setSyncDays(Math.min(45, Math.max(1, parseInt(e.target.value, 10) || 30)))}
|
||||
onChange={e => setSyncDays(Math.min(maxDays, Math.max(1, parseInt(e.target.value, 10) || 30)))}
|
||||
className="w-20 text-sm text-right"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">days</span>
|
||||
|
|
@ -195,11 +199,11 @@ export default function BankSyncAdminCard() {
|
|||
</div>
|
||||
|
||||
{/* Amber warning at the SimpleFIN limit */}
|
||||
{parseInt(syncDays, 10) >= 45 && (
|
||||
{parseInt(syncDays, 10) >= maxDays && (
|
||||
<div className="flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/8 px-3 py-2.5">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-amber-600 dark:text-amber-400">
|
||||
45 days is SimpleFIN Bridge's maximum. Requests at this limit may occasionally
|
||||
{maxDays} days is SimpleFIN Bridge's maximum. Requests at this limit may occasionally
|
||||
fail due to request latency — 30 days or less is recommended for reliable routine syncs.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -209,8 +213,8 @@ export default function BankSyncAdminCard() {
|
|||
<div className="flex items-start gap-2 text-xs text-muted-foreground">
|
||||
<Info className="h-3.5 w-3.5 shrink-0 mt-0.5" />
|
||||
<span>
|
||||
SimpleFIN Bridge enforces a <strong>45-day maximum</strong> on all requests.
|
||||
Any value above 45 will cause sync errors for all users.
|
||||
SimpleFIN Bridge enforces a <strong>{maxDays}-day maximum</strong> on all requests.
|
||||
Any value above {maxDays} will cause sync errors for all users.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -289,7 +289,8 @@ function AccountRow({ account, sourceId, expanded, onToggleExpand, onToggleMonit
|
|||
|
||||
export default function BankSyncSection({ onConnectionChange, cardProps = {} }) {
|
||||
const [enabled, setEnabled] = useState(null);
|
||||
const [syncDays, setSyncDays] = useState(90);
|
||||
const [syncDays, setSyncDays] = useState(30);
|
||||
const [seedDays, setSeedDays] = useState(44);
|
||||
const [connections, setConnections] = useState([]);
|
||||
const [accountsBySource, setAccountsBySource] = useState({});
|
||||
const [accountsLoading, setAccountsLoading] = useState({});
|
||||
|
|
@ -377,7 +378,8 @@ export default function BankSyncSection({ onConnectionChange, cardProps = {} })
|
|||
api.dataSources({ type: 'provider_sync' }),
|
||||
]);
|
||||
setEnabled(status.enabled);
|
||||
setSyncDays(status.sync_days ?? 90);
|
||||
setSyncDays(status.sync_days ?? 30);
|
||||
setSeedDays(status.seed_days ?? 44);
|
||||
const conns = Array.isArray(sources) ? sources.filter(s => s.provider === 'simplefin') : [];
|
||||
setConnections(conns);
|
||||
onConnectionChange?.(conns[0] || null);
|
||||
|
|
@ -484,7 +486,7 @@ export default function BankSyncSection({ onConnectionChange, cardProps = {} })
|
|||
if (result.errlist) {
|
||||
toast.warning(`Backfill complete — ${result.transactionsNew} new transaction(s). Some connections need attention: ${result.errlist}`);
|
||||
} else {
|
||||
toast.success(`Backfill complete — ${result.transactionsNew} new transaction(s) pulled from the last 90 days.`);
|
||||
toast.success(`Backfill complete — ${result.transactionsNew} new transaction(s) pulled from the last ${seedDays} days.`);
|
||||
}
|
||||
await load();
|
||||
} catch (err) {
|
||||
|
|
@ -632,11 +634,11 @@ export default function BankSyncSection({ onConnectionChange, cardProps = {} })
|
|||
onClick={() => handleBackfill(conn.id)}
|
||||
disabled={syncing === conn.id || backfilling === conn.id}
|
||||
className="h-8 text-xs gap-1.5 text-muted-foreground"
|
||||
title="Pull up to 90 days of transaction history"
|
||||
title={`Pull up to ${seedDays} days of transaction history`}
|
||||
>
|
||||
{backfilling === conn.id
|
||||
? <><Loader2 className="h-3.5 w-3.5 animate-spin" />Backfilling…</>
|
||||
: <><History className="h-3.5 w-3.5" />90d Backfill</>}
|
||||
: <><History className="h-3.5 w-3.5" />{seedDays}d Backfill</>}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm" variant="ghost"
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ router.get('/', (req, res) => {
|
|||
// ─── GET /api/data-sources/simplefin/status ──────────────────────────────────
|
||||
|
||||
router.get('/simplefin/status', (req, res) => {
|
||||
const { enabled, sync_days } = getBankSyncConfig();
|
||||
const { enabled, sync_days, seed_days } = getBankSyncConfig();
|
||||
const db = getDb();
|
||||
|
||||
const hasConnections = !!db.prepare(
|
||||
|
|
@ -102,7 +102,7 @@ router.get('/simplefin/status', (req, res) => {
|
|||
'SELECT 1 FROM bill_merchant_rules WHERE user_id = ? LIMIT 1'
|
||||
).get(req.user.id);
|
||||
|
||||
res.json({ enabled, sync_days, has_connections: hasConnections, has_merchant_rules: hasMerchantRules });
|
||||
res.json({ enabled, sync_days, seed_days, has_connections: hasConnections, has_merchant_rules: hasMerchantRules });
|
||||
});
|
||||
|
||||
// ─── POST /api/data-sources/simplefin/connect ────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ function getBankSyncConfig() {
|
|||
return {
|
||||
enabled,
|
||||
sync_days: syncDays,
|
||||
seed_days: SYNC_DAYS_EFFECTIVE, // initial connect / explicit backfill window
|
||||
sync_days_max: SYNC_DAYS_MAX, // SimpleFIN Bridge hard limit
|
||||
sync_interval_hours: syncIntervalHours,
|
||||
};
|
||||
}
|
||||
|
|
@ -67,4 +69,7 @@ function setSyncDays(days) {
|
|||
return getBankSyncConfig();
|
||||
}
|
||||
|
||||
module.exports = { getBankSyncConfig, setBankSyncEnabled, setSyncIntervalHours, setSyncDays };
|
||||
module.exports = {
|
||||
getBankSyncConfig, setBankSyncEnabled, setSyncIntervalHours, setSyncDays,
|
||||
SYNC_DAYS_MAX, SYNC_DAYS_EFFECTIVE, SYNC_DAYS_DEFAULT,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,15 +8,12 @@ const {
|
|||
normalizeTransaction,
|
||||
sanitizeErrorMessage,
|
||||
} = require('./simplefinService');
|
||||
const { getBankSyncConfig } = require('./bankSyncConfigService');
|
||||
const { getBankSyncConfig, SYNC_DAYS_EFFECTIVE, SYNC_DAYS_DEFAULT } = require('./bankSyncConfigService');
|
||||
const { decorateDataSource } = require('./transactionService');
|
||||
const { applyMerchantRules } = require('./billMerchantRuleService');
|
||||
const { applySpendingCategoryRules } = require('./spendingService');
|
||||
const { autoMatchForUser } = require('./matchSuggestionService');
|
||||
|
||||
const SEED_SYNC_DAYS = 44; // Initial connect / explicit backfill (SimpleFIN Bridge 45-day cap, 1-day buffer)
|
||||
const ROUTINE_SYNC_DAYS = 30; // Fallback if admin config is missing
|
||||
|
||||
function sinceEpochDays(days) {
|
||||
return Math.floor((Date.now() - days * 86400 * 1000) / 1000);
|
||||
}
|
||||
|
|
@ -86,10 +83,10 @@ async function runSync(db, userId, dataSource, { days } = {}) {
|
|||
const accessUrl = decryptSecret(dataSource.encrypted_secret);
|
||||
const isFirstSync = !dataSource.last_sync_at;
|
||||
// Explicit `days` param (e.g. backfill) takes precedence.
|
||||
// Initial seed always uses the full SEED_SYNC_DAYS window regardless of admin config.
|
||||
// Routine syncs use the admin-configured sync_days (default 30); falls back to ROUTINE_SYNC_DAYS.
|
||||
// Initial seed always uses the full SYNC_DAYS_EFFECTIVE window regardless of admin config.
|
||||
// Routine syncs use the admin-configured sync_days (default 30); falls back to SYNC_DAYS_DEFAULT.
|
||||
const config = getBankSyncConfig();
|
||||
const syncDays = days ?? (isFirstSync ? SEED_SYNC_DAYS : (config.sync_days || ROUTINE_SYNC_DAYS));
|
||||
const syncDays = days ?? (isFirstSync ? SYNC_DAYS_EFFECTIVE : (config.sync_days || SYNC_DAYS_DEFAULT));
|
||||
const since = sinceEpochDays(syncDays);
|
||||
|
||||
const raw = await fetchAccountsAndTransactions(accessUrl, since);
|
||||
|
|
@ -211,7 +208,7 @@ async function backfillDataSource(db, userId, dataSourceId) {
|
|||
|
||||
let syncResult;
|
||||
try {
|
||||
syncResult = await runSync(db, userId, dataSource, { days: SEED_SYNC_DAYS });
|
||||
syncResult = await runSync(db, userId, dataSource, { days: SYNC_DAYS_EFFECTIVE });
|
||||
} catch (err) {
|
||||
const msg = safeErrorMessage(err);
|
||||
db.prepare(`
|
||||
|
|
|
|||
Loading…
Reference in New Issue