import cron from 'node-cron'; import type { FastifyInstance } from 'fastify'; import { TRASH_RETENTION_DAYS } from '@family-wishlist/shared'; import { deleteLocalImageIfAny } from '../modules/images/storage.service.js'; async function purge(app: FastifyInstance): Promise { const cutoff = new Date(Date.now() - TRASH_RETENTION_DAYS * 24 * 60 * 60 * 1000); const victims = await app.prisma.wish.findMany({ where: { status: 'DELETED', deletedAt: { lt: cutoff } }, select: { id: true, imageUrl: true }, }); if (victims.length === 0) return 0; await Promise.all(victims.map((v) => deleteLocalImageIfAny(v.imageUrl))); const res = await app.prisma.wish.deleteMany({ where: { id: { in: victims.map((v) => v.id) } }, }); return res.count; } export function registerPurgeTrashJob(app: FastifyInstance): void { // Run daily at 03:17 (chosen to avoid common cron rush). const task = cron.schedule( '17 3 * * *', async () => { try { const count = await purge(app); if (count > 0) app.log.info({ count }, 'trash: purged expired wishes'); } catch (err) { app.log.error({ err }, 'trash: purge failed'); } }, { scheduled: true, timezone: 'UTC' }, ); app.addHook('onClose', async () => { task.stop(); }); // Also run once on startup to catch up if backend was offline for a while. setTimeout(() => { purge(app) .then((count) => { if (count > 0) app.log.info({ count }, 'trash: startup purge'); }) .catch((err) => app.log.error({ err }, 'trash: startup purge failed')); }, 5_000); }