feat: subscription badges + status improvements + dailyWorker fix (batch 0.33.8.3)
- Sub badge (indigo) in all 4 locations, toggleable in Bills prefs - SimpleFIN Sync card on Status page - dailyWorker.start() now called on startup - tracker.overdue_count uses real SQL query - Status page accuracy: dynamic headers, Degraded state, worker last_error check - Removed SimpleFIN prefix from Recommendations title - Bump v0.33.8.2 -> v0.33.8.3
This commit is contained in:
parent
6e9fd6873f
commit
0f9f48e255
20
HISTORY.md
20
HISTORY.md
|
|
@ -1,5 +1,25 @@
|
||||||
# Bill Tracker — Changelog
|
# Bill Tracker — Changelog
|
||||||
|
|
||||||
|
## v0.33.8.3
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- **Subscription badge (indigo)** — `Sub` badge in all four locations (desktop tracker, mobile tracker, desktop bills table, mobile bills row), toggleable via Bills page display preferences.
|
||||||
|
- **SimpleFIN Sync status card** — New card in Operations grid on Status page showing connections, accounts, last sync, next check, interval, and any errors.
|
||||||
|
- **Daily worker now starts** — `dailyWorker.start()` was never called; autopay marking, notifications, session pruning, and scheduled cleanup are now active.
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **Tracker overdue count** — `overdue_count` now runs a real SQL query: active monthly bills where due_day < today, no payment this month, and not skipped.
|
||||||
|
- **Status page accuracy** — Header subtitle reflects `data.ok` ("All systems operational" / "One or more systems need attention"). Application card shows "Degraded" in red when not ok. Worker status pill now checks `last_error` — a running worker with errors shows "Error" red instead of "Running" green. "Stopped" renamed to "Error" for accuracy.
|
||||||
|
- **SimpleFIN Recommendations title** — Removed redundant "SimpleFIN" prefix from card title on Subscriptions page.
|
||||||
|
|
||||||
|
### 🛠 Internal
|
||||||
|
|
||||||
|
- `routes/status.js` — `bank_sync` block returns config, worker state, DB aggregates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.33.8.2
|
## v0.33.8.2
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,11 @@ function BillCard({ bill, prefs = ALL_ON, onEdit, onToggle, onDelete, onHistory,
|
||||||
2FA
|
2FA
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{prefs.showSubscription && !!bill.is_subscription && (
|
||||||
|
<span className="shrink-0 rounded bg-indigo-500/15 px-1.5 py-0.5 text-[11px] font-semibold text-indigo-600 dark:text-indigo-300">
|
||||||
|
Sub
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{hasHistory && (
|
{hasHistory && (
|
||||||
<span
|
<span
|
||||||
className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-sky-500/25 bg-sky-500/10 text-sky-500"
|
className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-sky-500/25 bg-sky-500/10 text-sky-500"
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,9 @@ export const MobileBillRow = React.memo(function MobileBillRow({ bill, onEdit, o
|
||||||
{bill.has_2fa && (
|
{bill.has_2fa && (
|
||||||
<span className="rounded bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-violet-300">2FA</span>
|
<span className="rounded bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-semibold text-violet-300">2FA</span>
|
||||||
)}
|
)}
|
||||||
|
{bill.is_subscription && (
|
||||||
|
<span className="rounded bg-indigo-500/15 px-1.5 py-0.5 text-[10px] font-semibold text-indigo-600 dark:text-indigo-300">Sub</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,14 @@ export const MobileTrackerRow = React.memo(function MobileTrackerRow({ row, year
|
||||||
AP
|
AP
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{row.is_subscription && (
|
||||||
|
<span
|
||||||
|
className="inline-flex shrink-0 rounded border border-indigo-500/25 bg-indigo-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-indigo-600 dark:text-indigo-300"
|
||||||
|
title="Subscription"
|
||||||
|
>
|
||||||
|
Sub
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{row.monthly_notes && (
|
{row.monthly_notes && (
|
||||||
<p className="mt-1 line-clamp-2 text-xs italic text-amber-500/80" title={row.monthly_notes}>
|
<p className="mt-1 line-clamp-2 text-xs italic text-amber-500/80" title={row.monthly_notes}>
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ const PREFS_DEFAULTS = {
|
||||||
showMinPayment: true,
|
showMinPayment: true,
|
||||||
showAutopay: true,
|
showAutopay: true,
|
||||||
show2fa: true,
|
show2fa: true,
|
||||||
|
showSubscription: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const PREFS_LABELS = [
|
const PREFS_LABELS = [
|
||||||
|
|
@ -200,6 +201,7 @@ const PREFS_LABELS = [
|
||||||
['showMinPayment', 'Min payment'],
|
['showMinPayment', 'Min payment'],
|
||||||
['showAutopay', 'Autopay badge'],
|
['showAutopay', 'Autopay badge'],
|
||||||
['show2fa', '2FA badge'],
|
['show2fa', '2FA badge'],
|
||||||
|
['showSubscription', 'Subscription badge'],
|
||||||
];
|
];
|
||||||
|
|
||||||
function useDisplayPrefs() {
|
function useDisplayPrefs() {
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ function ReleaseNotesSection({ version, historyMeta }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusCard title="Release Notes" status={`v${version.version}`} tone="good">
|
<StatusCard title="Release Notes" status={`v${version.version}`} tone="good">
|
||||||
<StatRow label="Latest Release" value={categories[0] ?? null} />
|
<StatRow label="Total Changes" value={version.notes?.length ? `${version.notes.length} items` : null} />
|
||||||
<StatRow label="Last Updated" value={formatDateTime(historyMeta?.updated_at)} />
|
<StatRow label="Last Updated" value={formatDateTime(historyMeta?.updated_at)} />
|
||||||
<div className="py-2 border-b border-border/50">
|
<div className="py-2 border-b border-border/50">
|
||||||
<p className="text-sm text-muted-foreground">Preview</p>
|
<p className="text-sm text-muted-foreground">Preview</p>
|
||||||
|
|
@ -285,14 +285,26 @@ export default function StatusPage() {
|
||||||
const server = data?.server ?? data?.clock ?? {};
|
const server = data?.server ?? data?.clock ?? {};
|
||||||
const tracker = data?.tracker ?? data?.tracker_health ?? {};
|
const tracker = data?.tracker ?? data?.tracker_health ?? {};
|
||||||
const cleanup = data?.cleanup ?? {};
|
const cleanup = data?.cleanup ?? {};
|
||||||
|
const bankSync = data?.bank_sync ?? data?.simplefin ?? {};
|
||||||
const errors = data?.errors ?? data?.recent_errors ?? [];
|
const errors = data?.errors ?? data?.recent_errors ?? [];
|
||||||
|
|
||||||
const dbOk = db.connected ?? (db.status === 'connected') ?? true;
|
const dbOk = db.connected ?? (db.status === 'connected') ?? true;
|
||||||
const workerOk = worker.running ?? worker.enabled ?? null;
|
const workerOk = !worker.enabled ? null : worker.last_error ? false : true;
|
||||||
const notificationsConfigured = notifications.configured ?? notifications.smtp_configured ?? null;
|
const notificationsConfigured = notifications.configured ?? notifications.smtp_configured ?? null;
|
||||||
const backupsEnabled = backups.enabled ?? null;
|
const backupsEnabled = backups.enabled ?? null;
|
||||||
const recentErrors = Array.isArray(errors) ? errors : [];
|
const recentErrors = Array.isArray(errors) ? errors : [];
|
||||||
|
|
||||||
|
const bankSyncEnabled = bankSync.enabled ?? false;
|
||||||
|
const bankSyncStatus = !bankSyncEnabled ? 'Disabled'
|
||||||
|
: bankSync.running ? 'Syncing'
|
||||||
|
: bankSync.last_error ? 'Error'
|
||||||
|
: bankSync.source_count > 0 ? 'Connected'
|
||||||
|
: 'No Sources';
|
||||||
|
const bankSyncTone = !bankSyncEnabled ? 'muted'
|
||||||
|
: bankSync.last_error ? 'warn'
|
||||||
|
: bankSync.source_count > 0 ? 'good'
|
||||||
|
: 'warn';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
|
|
@ -301,7 +313,7 @@ export default function StatusPage() {
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold tracking-tight">Server Status</h1>
|
<h1 className="text-2xl font-bold tracking-tight">Server Status</h1>
|
||||||
<p className="text-sm text-muted-foreground mt-0.5">
|
<p className="text-sm text-muted-foreground mt-0.5">
|
||||||
{data ? 'All systems operational' : 'Loading…'}
|
{!data ? 'Loading…' : data.ok ? 'All systems operational' : 'One or more systems need attention'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" size="sm" onClick={load} disabled={loading}>
|
<Button variant="outline" size="sm" onClick={load} disabled={loading}>
|
||||||
|
|
@ -322,7 +334,7 @@ export default function StatusPage() {
|
||||||
<div className="grid gap-4 mb-6 md:grid-cols-2">
|
<div className="grid gap-4 mb-6 md:grid-cols-2">
|
||||||
|
|
||||||
{/* Application */}
|
{/* Application */}
|
||||||
<StatusCard title="Application" status="Online" tone="good">
|
<StatusCard title="Application" status={data?.ok ? 'Online' : 'Degraded'} tone={data?.ok ? 'good' : 'bad'}>
|
||||||
<StatRow label="Version" value={(app.version ?? data?.version) ? `v${app.version ?? data?.version}` : null} />
|
<StatRow label="Version" value={(app.version ?? data?.version) ? `v${app.version ?? data?.version}` : null} />
|
||||||
<StatRow label="Environment" value={app.environment ?? app.env ?? data?.environment ?? data?.env} />
|
<StatRow label="Environment" value={app.environment ?? app.env ?? data?.environment ?? data?.env} />
|
||||||
<StatRow
|
<StatRow
|
||||||
|
|
@ -384,7 +396,7 @@ export default function StatusPage() {
|
||||||
|
|
||||||
<StatusCard
|
<StatusCard
|
||||||
title="Daily Worker"
|
title="Daily Worker"
|
||||||
status={workerOk === null ? 'Pending' : workerOk ? 'Running' : 'Stopped'}
|
status={workerOk === null ? 'Pending' : workerOk ? 'Running' : 'Error'}
|
||||||
tone={workerOk === null ? 'muted' : workerOk ? 'good' : 'bad'}
|
tone={workerOk === null ? 'muted' : workerOk ? 'good' : 'bad'}
|
||||||
>
|
>
|
||||||
<StatRow label="Enabled" value={worker.enabled === undefined ? null : worker.enabled ? 'Yes' : 'No'} />
|
<StatRow label="Enabled" value={worker.enabled === undefined ? null : worker.enabled ? 'Yes' : 'No'} />
|
||||||
|
|
@ -393,6 +405,19 @@ export default function StatusPage() {
|
||||||
<StatRow label="Last Error" value={worker.last_error} last />
|
<StatRow label="Last Error" value={worker.last_error} last />
|
||||||
</StatusCard>
|
</StatusCard>
|
||||||
|
|
||||||
|
<StatusCard
|
||||||
|
title="SimpleFIN Sync"
|
||||||
|
status={bankSyncStatus}
|
||||||
|
tone={bankSyncTone}
|
||||||
|
>
|
||||||
|
<StatRow label="Connections" value={bankSyncEnabled ? (bankSync.source_count ?? 0) : null} />
|
||||||
|
<StatRow label="Accounts" value={bankSyncEnabled ? (bankSync.account_count ?? 0) : null} />
|
||||||
|
<StatRow label="Last Sync" value={bankSyncEnabled ? formatDateTime(bankSync.last_sync_at) : null} />
|
||||||
|
<StatRow label="Next Check" value={bankSyncEnabled ? formatDateTime(bankSync.next_run_at) : null} />
|
||||||
|
<StatRow label="Interval" value={bankSyncEnabled && bankSync.interval_hours ? `Every ${bankSync.interval_hours}h` : null} />
|
||||||
|
<StatRow label="Last Error" value={bankSync.last_error} last />
|
||||||
|
</StatusCard>
|
||||||
|
|
||||||
<StatusCard
|
<StatusCard
|
||||||
title="Notifications"
|
title="Notifications"
|
||||||
status={notificationsConfigured === null ? 'Pending' : notificationsConfigured ? 'Configured' : 'Missing'}
|
status={notificationsConfigured === null ? 'Pending' : notificationsConfigured ? 'Configured' : 'Missing'}
|
||||||
|
|
|
||||||
|
|
@ -459,7 +459,7 @@ export default function SubscriptionsPage() {
|
||||||
<CardHeader className="px-4 pb-3 sm:px-6">
|
<CardHeader className="px-4 pb-3 sm:px-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Sparkles className="h-4 w-4 text-primary" />
|
<Sparkles className="h-4 w-4 text-primary" />
|
||||||
<CardTitle className="text-base">SimpleFIN Recommendations</CardTitle>
|
<CardTitle className="text-base">Recommendations</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>Recurring unmatched bank charges that look like subscriptions.</CardDescription>
|
<CardDescription>Recurring unmatched bank charges that look like subscriptions.</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -1044,6 +1044,14 @@ function Row({ row, year, month, refresh, index, onEditBill }) {
|
||||||
AP
|
AP
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{row.is_subscription && (
|
||||||
|
<span
|
||||||
|
className="inline-flex shrink-0 rounded border border-indigo-500/25 bg-indigo-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-indigo-600 dark:text-indigo-300"
|
||||||
|
title="Subscription"
|
||||||
|
>
|
||||||
|
Sub
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
size="icon" variant="ghost"
|
size="icon" variant="ghost"
|
||||||
className="h-7 w-7 opacity-100 transition-opacity text-muted-foreground hover:text-foreground hover:bg-accent lg:opacity-0 lg:group-hover:opacity-100"
|
className="h-7 w-7 opacity-100 transition-opacity text-muted-foreground hover:text-foreground hover:bg-accent lg:opacity-0 lg:group-hover:opacity-100"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "bill-tracker",
|
"name": "bill-tracker",
|
||||||
"version": "0.33.8.2",
|
"version": "0.33.8.3",
|
||||||
"description": "Monthly bill tracking system",
|
"description": "Monthly bill tracking system",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ const { getStatusRuntime, recordError } = require('../services/statusRuntime');
|
||||||
const { listBackups } = require('../services/backupService');
|
const { listBackups } = require('../services/backupService');
|
||||||
const { getScheduleStatus } = require('../services/backupScheduler');
|
const { getScheduleStatus } = require('../services/backupScheduler');
|
||||||
const { checkForUpdates } = require('../services/updateCheckService');
|
const { checkForUpdates } = require('../services/updateCheckService');
|
||||||
|
const { getStatus: getBankSyncWorkerStatus } = require('../services/bankSyncWorker');
|
||||||
|
const { getBankSyncConfig } = require('../services/bankSyncConfigService');
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let pkg;
|
let pkg;
|
||||||
|
|
@ -188,6 +190,7 @@ router.get('/', async (req, res) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const range = monthRange(now);
|
const range = monthRange(now);
|
||||||
|
const todayDay = now.getDate();
|
||||||
const billCount = db.prepare('SELECT COUNT(*) AS n FROM bills WHERE active = 1').get().n;
|
const billCount = db.prepare('SELECT COUNT(*) AS n FROM bills WHERE active = 1').get().n;
|
||||||
const paymentCount = db.prepare(
|
const paymentCount = db.prepare(
|
||||||
'SELECT COUNT(*) AS n FROM payments WHERE paid_date BETWEEN ? AND ? AND deleted_at IS NULL'
|
'SELECT COUNT(*) AS n FROM payments WHERE paid_date BETWEEN ? AND ? AND deleted_at IS NULL'
|
||||||
|
|
@ -195,6 +198,20 @@ router.get('/', async (req, res) => {
|
||||||
const skippedCount = db.prepare(
|
const skippedCount = db.prepare(
|
||||||
'SELECT COUNT(*) AS n FROM monthly_bill_state WHERE year = ? AND month = ? AND is_skipped = 1'
|
'SELECT COUNT(*) AS n FROM monthly_bill_state WHERE year = ? AND month = ? AND is_skipped = 1'
|
||||||
).get(range.year, range.month).n;
|
).get(range.year, range.month).n;
|
||||||
|
const overdueCount = db.prepare(`
|
||||||
|
SELECT COUNT(*) AS n FROM bills b
|
||||||
|
WHERE b.active = 1
|
||||||
|
AND b.billing_cycle = 'monthly'
|
||||||
|
AND CAST(b.due_day AS INTEGER) < ?
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM payments p
|
||||||
|
WHERE p.bill_id = b.id AND p.paid_date BETWEEN ? AND ? AND p.deleted_at IS NULL
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM monthly_bill_state mbs
|
||||||
|
WHERE mbs.bill_id = b.id AND mbs.year = ? AND mbs.month = ? AND mbs.is_skipped = 1
|
||||||
|
)
|
||||||
|
`).get(todayDay, range.start, range.end, range.year, range.month).n;
|
||||||
|
|
||||||
tracker = {
|
tracker = {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|
@ -204,7 +221,7 @@ router.get('/', async (req, res) => {
|
||||||
payment_count: paymentCount,
|
payment_count: paymentCount,
|
||||||
bills_this_month: billCount,
|
bills_this_month: billCount,
|
||||||
payments_this_month: paymentCount,
|
payments_this_month: paymentCount,
|
||||||
overdue_count: null,
|
overdue_count: overdueCount,
|
||||||
skipped_count: skippedCount,
|
skipped_count: skippedCount,
|
||||||
last_error: null,
|
last_error: null,
|
||||||
};
|
};
|
||||||
|
|
@ -218,6 +235,69 @@ router.get('/', async (req, res) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bank sync (SimpleFIN) status
|
||||||
|
let bankSync = {
|
||||||
|
enabled: false,
|
||||||
|
running: false,
|
||||||
|
source_count: 0,
|
||||||
|
account_count: 0,
|
||||||
|
transaction_count: 0,
|
||||||
|
interval_hours: null,
|
||||||
|
sync_days: null,
|
||||||
|
last_sync_at: null,
|
||||||
|
next_run_at: null,
|
||||||
|
last_error: null,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const config = getBankSyncConfig();
|
||||||
|
const workerStatus = getBankSyncWorkerStatus();
|
||||||
|
bankSync = {
|
||||||
|
enabled: config.enabled,
|
||||||
|
running: workerStatus.running,
|
||||||
|
interval_hours: config.sync_interval_hours,
|
||||||
|
sync_days: config.sync_days,
|
||||||
|
source_count: 0,
|
||||||
|
account_count: 0,
|
||||||
|
transaction_count: 0,
|
||||||
|
last_sync_at: null,
|
||||||
|
next_run_at: workerStatus.next_run_at,
|
||||||
|
last_error: null,
|
||||||
|
};
|
||||||
|
if (db) {
|
||||||
|
const sourceRow = db.prepare(`
|
||||||
|
SELECT COUNT(*) AS source_count, MAX(last_sync_at) AS last_sync_at
|
||||||
|
FROM data_sources WHERE type = 'provider_sync' AND provider = 'simplefin'
|
||||||
|
`).get();
|
||||||
|
const accountRow = db.prepare(`
|
||||||
|
SELECT COUNT(fa.id) AS account_count
|
||||||
|
FROM financial_accounts fa
|
||||||
|
INNER JOIN data_sources ds ON ds.id = fa.data_source_id
|
||||||
|
WHERE ds.type = 'provider_sync' AND ds.provider = 'simplefin'
|
||||||
|
`).get();
|
||||||
|
const txRow = db.prepare(`
|
||||||
|
SELECT COUNT(t.id) AS transaction_count
|
||||||
|
FROM transactions t
|
||||||
|
INNER JOIN data_sources ds ON ds.id = t.data_source_id
|
||||||
|
WHERE ds.type = 'provider_sync' AND ds.provider = 'simplefin'
|
||||||
|
`).get();
|
||||||
|
const errorRow = db.prepare(`
|
||||||
|
SELECT last_error FROM data_sources
|
||||||
|
WHERE type = 'provider_sync' AND provider = 'simplefin' AND last_error IS NOT NULL
|
||||||
|
ORDER BY updated_at DESC LIMIT 1
|
||||||
|
`).get();
|
||||||
|
bankSync = {
|
||||||
|
...bankSync,
|
||||||
|
source_count: sourceRow.source_count ?? 0,
|
||||||
|
account_count: accountRow.account_count ?? 0,
|
||||||
|
transaction_count: txRow.transaction_count ?? 0,
|
||||||
|
last_sync_at: sourceRow.last_sync_at || null,
|
||||||
|
last_error: errorRow?.last_error || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
recordError('Bank Sync Status', err);
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup status — safe read-only summary from settings, no paths or secrets
|
// Cleanup status — safe read-only summary from settings, no paths or secrets
|
||||||
let cleanup = { ok: true, last_run_at: null, last_result: null };
|
let cleanup = { ok: true, last_run_at: null, last_result: null };
|
||||||
try {
|
try {
|
||||||
|
|
@ -285,6 +365,8 @@ router.get('/', async (req, res) => {
|
||||||
backup: backups,
|
backup: backups,
|
||||||
server,
|
server,
|
||||||
tracker,
|
tracker,
|
||||||
|
bank_sync: bankSync,
|
||||||
|
simplefin: bankSync,
|
||||||
cleanup,
|
cleanup,
|
||||||
update,
|
update,
|
||||||
recent_errors: recentErrors,
|
recent_errors: recentErrors,
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,13 @@ async function main() {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[bankSync] Failed to start auto-sync worker:', err.message);
|
console.error('[bankSync] Failed to start auto-sync worker:', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start daily worker (autopay marking, notifications, session pruning, cleanup)
|
||||||
|
try {
|
||||||
|
require('./workers/dailyWorker').start();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[dailyWorker] Failed to start daily worker:', err.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue