import pool from '@/lib/db' const MASKING_DAYS = parseInt(process.env.DATA_MASKING_DAYS || '30') const NEVER_ACTIVATED_DAYS = parseInt(process.env.NEVER_ACTIVATED_DAYS || '90') export interface CleanupStats { investors_masked: number sessions_deleted: number audit_ips_anonymized: number audit_details_redacted: number magic_links_deleted: number } export async function runDataCleanup(): Promise { const stats: CleanupStats = { investors_masked: 0, sessions_deleted: 0, audit_ips_anonymized: 0, audit_details_redacted: 0, magic_links_deleted: 0, } const activeCutoff = new Date(Date.now() - MASKING_DAYS * 24 * 60 * 60 * 1000) const neverActivatedCutoff = new Date(Date.now() - NEVER_ACTIVATED_DAYS * 24 * 60 * 60 * 1000) // 1. Mask investors inactive for MASKING_DAYS const { rows: maskedActive } = await pool.query<{ id: string }>( `UPDATE pitch_investors SET email = 'anon.' || id::text, name = NULL, company = NULL, status = 'revoked', data_masked_at = NOW(), updated_at = NOW() WHERE last_login_at IS NOT NULL AND last_login_at < $1 AND data_masked_at IS NULL RETURNING id`, [activeCutoff], ) // 2. Mask investors invited but never activated after NEVER_ACTIVATED_DAYS const { rows: maskedNever } = await pool.query<{ id: string }>( `UPDATE pitch_investors SET email = 'anon.' || id::text, name = NULL, company = NULL, status = 'revoked', data_masked_at = NOW(), updated_at = NOW() WHERE last_login_at IS NULL AND created_at < $1 AND data_masked_at IS NULL RETURNING id`, [neverActivatedCutoff], ) const allMaskedIds = [...maskedActive, ...maskedNever].map((r) => r.id) stats.investors_masked = allMaskedIds.length if (allMaskedIds.length > 0) { // 3. Revoke sessions for newly masked investors await pool.query( `UPDATE pitch_sessions SET revoked = true WHERE investor_id = ANY($1::uuid[]) AND NOT revoked`, [allMaskedIds], ) // 4. Redact email field from audit log details for masked investors const { rowCount } = await pool.query( `UPDATE pitch_audit_logs SET details = details - 'email' WHERE investor_id = ANY($1::uuid[]) AND details ? 'email'`, [allMaskedIds], ) stats.audit_details_redacted = rowCount ?? 0 } // 5. Delete sessions older than MASKING_DAYS (expired, contain IP/UA) const { rowCount: sessionsDeleted } = await pool.query( `DELETE FROM pitch_sessions WHERE created_at < $1`, [activeCutoff], ) stats.sessions_deleted = sessionsDeleted ?? 0 // 6. Anonymize IP addresses in audit logs older than MASKING_DAYS const { rowCount: logsAnonymized } = await pool.query( `UPDATE pitch_audit_logs SET ip_address = NULL WHERE created_at < $1 AND ip_address IS NOT NULL`, [activeCutoff], ) stats.audit_ips_anonymized = logsAnonymized ?? 0 // 7. Delete magic links older than MASKING_DAYS const { rowCount: linksDeleted } = await pool.query( `DELETE FROM pitch_magic_links WHERE created_at < $1`, [activeCutoff], ) stats.magic_links_deleted = linksDeleted ?? 0 return stats }