import { and, eq, lt } from 'drizzle-orm' import { db } from '../../db/client.js' import { notifications, topics } from '../../db/schema.js' import { resolveRetentionDefaultDays } from '../admin/system-settings.js' const CLEANUP_INTERVAL_MS = 60 * 60 * 1000 // 1 hour // Per-topic retentionDays overrides the global default; 0 means "forever". // See docs/phase1/03-database-model.md, "Retention cleanup". export async function runRetentionCleanup(): Promise { const topicRows = await db.select().from(topics) const retentionDefaultDays = await resolveRetentionDefaultDays() let totalDeleted = 0 for (const topic of topicRows) { const retentionDays = topic.retentionDays ?? retentionDefaultDays if (retentionDays === 0) continue const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000) const deleted = await db .delete(notifications) .where(and(eq(notifications.topicId, topic.id), lt(notifications.createdAt, cutoff))) .returning({ id: notifications.id }) totalDeleted += deleted.length } return totalDeleted } export function startRetentionCleanupJob( onError: (err: unknown) => void = console.error, ): NodeJS.Timeout { const run = () => { runRetentionCleanup().catch(onError) } run() // catch up on anything overdue from before this boot const timer = setInterval(run, CLEANUP_INTERVAL_MS) timer.unref() // never block process shutdown just to keep this alive return timer }