// Enforces the data-retention windows promised in the Privacy Policy. Run daily: // npx tsx scripts/retention-sweep.ts // // Windows enforced: // - sessions: expired → deleted // - password resets / email verifications: consumed or expired → deleted // - login attempts (email, IP): 90 days // - tool usage (IP): 90 days // - contact messages: 24 months // - audit log: 24 months import path from 'node:path'; import { fileURLToPath } from 'node:url'; import dotenv from 'dotenv'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); dotenv.config({ path: path.resolve(__dirname, '../.env') }); import { isNotNull, lt, or } from 'drizzle-orm'; import { getDb, getPool, sessions, passwordResets, emailVerifications, loginAttempts, contactMessages, toolUsage, auditLog, } from '@lawdesk/db'; function daysAgo(n: number): Date { return new Date(Date.now() - n * 24 * 60 * 60 * 1000); } async function main() { const db = getDb(); const now = new Date(); const report: Array<[string, number]> = []; const run = async (label: string, fn: () => Promise<{ rowCount?: number | null }>) => { const res = await fn(); report.push([label, res.rowCount ?? 0]); }; await run('expired sessions', () => db.delete(sessions).where(lt(sessions.expiresAt, now))); await run('consumed/expired password resets', () => db .delete(passwordResets) .where(or(isNotNull(passwordResets.consumedAt), lt(passwordResets.expiresAt, now))), ); await run('consumed/expired email verifications', () => db .delete(emailVerifications) .where(or(isNotNull(emailVerifications.consumedAt), lt(emailVerifications.expiresAt, now))), ); await run('login attempts > 90 days', () => db.delete(loginAttempts).where(lt(loginAttempts.attemptedAt, daysAgo(90))), ); await run('tool usage > 90 days', () => db.delete(toolUsage).where(lt(toolUsage.createdAt, daysAgo(90))), ); await run('contact messages > 24 months', () => db.delete(contactMessages).where(lt(contactMessages.createdAt, daysAgo(730))), ); await run('audit log > 24 months', () => db.delete(auditLog).where(lt(auditLog.createdAt, daysAgo(730))), ); console.log('Retention sweep complete:'); for (const [label, count] of report) console.log(` ${label}: ${count} row(s) deleted`); await getPool().end(); } main().catch((err) => { console.error(err); process.exit(1); });