Phase 5: alert subscriptions UI + multiple watchlists + queue fixes
CI / Test & Type-Check (push) Canceled after 0s
CI / Test & Type-Check (push) Canceled after 0s
UI: - /alerts page: event history with acknowledge, subscription create/manage with toggle - /admin/smtp: SMTP config form (host, port, auth, test) - Watchlist sidebar: dropdown selector for multiple watchlists, create/delete - Sidebar: alerts count badge, SMTP link under admin - Mobile tab nav: alerts tab added - Client trpc.ts: all new API methods + types Backend: - watchlists.listByWatchlist procedure + listSymbolsByWatchlist repo fn - yfinance min-interval 1500->2000ms to reduce Edge 429s - Fixed e.date.slice error in yfinance-adjustments with typeof guard - Removed defunct BITF from watchlist+queue - Cleared 83 failed + 12 backoff queue jobs Docs: - FUNCTIONAL_DESIGN.md: alerts + multiple watchlists + SMTP documented - TECH_DESIGN.md: new modules, tRPC procs, routes updated
This commit is contained in:
@@ -66,6 +66,46 @@ const SCHEDULE_MS = 30_000;
|
||||
const scheduleTimer = setInterval(() => { queue.enqueueDueSchedules().catch((e) => console.error('[schedule error]', e)); }, SCHEDULE_MS);
|
||||
scheduleTimer.unref();
|
||||
|
||||
// Per-fetch alert check: run alongside the schedule cycle for critical alert types.
|
||||
const perFetchAlertTimer = setInterval(async () => {
|
||||
try {
|
||||
const alerts = await runProducers(database, 'per-fetch');
|
||||
if (alerts.length > 0) {
|
||||
console.log(`[alert] ${alerts.length} per-fetch alert(s) created`);
|
||||
const { sendAlertEmail } = await import('./services/emailAlertService.ts');
|
||||
alerts.forEach((a) => sendAlertEmail(database, a).catch(() => {}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[alert] per-fetch check failed:', e);
|
||||
}
|
||||
}, SCHEDULE_MS);
|
||||
perFetchAlertTimer.unref();
|
||||
|
||||
// Register alert producers.
|
||||
import { registerProducer, runProducers } from './alerts/producers/index.ts';
|
||||
import { informedBuyProducer, informedSellProducer } from './alerts/producers/insiderProducer.ts';
|
||||
import { new13daProducer } from './alerts/producers/new13daProducer.ts';
|
||||
registerProducer(informedBuyProducer);
|
||||
registerProducer(informedSellProducer);
|
||||
registerProducer(new13daProducer);
|
||||
|
||||
// Batched alert check: run every 10 minutes for non-critical producers.
|
||||
const ALERT_BATCH_MS = 10 * 60 * 1000;
|
||||
const alertBatchTimer = setInterval(async () => {
|
||||
try {
|
||||
const alerts = await runProducers(database, 'batched');
|
||||
if (alerts.length > 0) console.log(`[alert] ${alerts.length} batch alert(s) created`);
|
||||
// Send email for each new alert.
|
||||
const { sendAlertEmail } = await import('./services/emailAlertService.ts');
|
||||
for (const alert of alerts) {
|
||||
sendAlertEmail(database, alert).catch((e) => console.error('[alert:email] send error:', e));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[alert] batch check failed:', e);
|
||||
}
|
||||
}, ALERT_BATCH_MS);
|
||||
alertBatchTimer.unref();
|
||||
|
||||
function readBody(req: IncomingMessage): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = '';
|
||||
|
||||
Reference in New Issue
Block a user