- TS: pino + pino-http; every HTTP request log includes traceId from W3C traceparent header (generated if absent); forwarded to ml/serving on all /score, /generate, /reward, and /api/ml proxy calls - Python: structlog JSON; FastAPI middleware binds trace_id via contextvars so every log line within a request carries it - Sentry: optional SENTRY_DSN init in both runtimes (no-op if unset) - Replace all console.* calls across services/api with pino logger - Update tests to spy on logger instead of console Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
57 lines
1.7 KiB
TypeScript
57 lines
1.7 KiB
TypeScript
/**
|
|
* Todoist background sync scheduler (Issue #22).
|
|
*
|
|
* Periodically fetches Todoist tasks for every user with an active token so
|
|
* that signals are fresh before the next /recommend call. The on-demand fetch
|
|
* in TodoistSignalSource remains as the freshness-critical fallback — if a user
|
|
* hits /recommend and the cache is stale, it re-fetches inline.
|
|
*
|
|
* Interval: TODOIST_SYNC_INTERVAL_MS (default 15 min).
|
|
*/
|
|
|
|
import { db } from '../db/index.js';
|
|
import { integrationTokens } from '../db/schema.js';
|
|
import { eq } from 'drizzle-orm';
|
|
import { todoistSource } from './todoist.js';
|
|
import { logger } from '../logger.js';
|
|
|
|
const DEFAULT_INTERVAL_MS = 15 * 60 * 1000;
|
|
|
|
export function startTodoistSyncScheduler(intervalMs = DEFAULT_INTERVAL_MS): NodeJS.Timeout {
|
|
async function syncAll(): Promise<void> {
|
|
let users: { userId: string }[] = [];
|
|
try {
|
|
users = await db
|
|
.select({ userId: integrationTokens.userId })
|
|
.from(integrationTokens)
|
|
.where(eq(integrationTokens.tokenStatus, 'active'));
|
|
} catch (err: any) {
|
|
logger.error({ err }, 'scheduler: failed to query users');
|
|
return;
|
|
}
|
|
|
|
if (!users.length) return;
|
|
|
|
const results = await Promise.allSettled(
|
|
users.map((u) => todoistSource.fetchSignals(u.userId)),
|
|
);
|
|
|
|
let ok = 0;
|
|
let failed = 0;
|
|
for (const r of results) {
|
|
if (r.status === 'fulfilled') ok++;
|
|
else { failed++; logger.error({ err: r.reason }, 'scheduler: sync error'); }
|
|
}
|
|
|
|
logger.info({ ok, failed, total: users.length }, 'scheduler: todoist sync');
|
|
}
|
|
|
|
// Run once shortly after startup, then on interval
|
|
const delay = setTimeout(() => {
|
|
syncAll();
|
|
setInterval(syncAll, intervalMs);
|
|
}, 10_000);
|
|
|
|
return delay;
|
|
}
|