49 lines
1.6 KiB
TypeScript
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);
|
|
}
|