Files
family_wishlist/apps/backend/src/jobs/purge-trash.ts

49 lines
1.6 KiB
TypeScript

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<number> {
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);
}