import cron from "node-cron"; import { prisma } from "../lib/db"; import { createLogger } from "../lib/logger"; import { getAdminSettings } from "../lib/adminSettings"; const log = createLogger("CRON"); export function startCronWorkers() { // Inactivity check every 5 minutes cron.schedule("*/5 * * * *", async () => { try { await checkInactivity(); } catch (e) { log.error({ e }, "Inactivity check error"); } }); // Daily cleanup cron.schedule("0 3 * * *", async () => { try { await dailyCleanup(); } catch (e) { log.error({ e }, "Daily cleanup error"); } }); // Run an inactivity check immediately on startup so previews that went idle // while PP was down aren't left waiting for the next scheduled tick. checkInactivity().catch((e) => log.error({ e }, "Startup inactivity check error")); log.info("Cron workers started"); } async function checkInactivity() { const now = new Date(); const running = await prisma.preview.findMany({ where: { status: "RUNNING" }, include: { repoConfig: { select: { inactivityHours: true } } }, }); for (const preview of running) { const inactivityHours = preview.repoConfig.inactivityHours; const deadline = new Date(preview.lastActivityAt.getTime() + inactivityHours * 3600 * 1000); if (now >= deadline) { log.info({ previewId: preview.id, inactivityHours }, "Preview inactive, enqueuing INACTIVITY_STOP"); // Only create one pending INACTIVITY_STOP per preview const existingStop = await prisma.job.findFirst({ where: { previewId: preview.id, type: "INACTIVITY_STOP", status: { in: ["PENDING", "RUNNING"] } }, }); if (!existingStop) { await prisma.job.create({ data: { previewId: preview.id, type: "INACTIVITY_STOP", status: "PENDING", payload: {}, }, }); } } } } async function dailyCleanup() { const settings = await getAdminSettings(); const cutoff = new Date(Date.now() - settings.previewRetentionDays * 86400 * 1000); const old = await prisma.preview.findMany({ where: { status: { in: ["STOPPED", "FAILED"] }, stoppedAt: { lt: cutoff }, }, select: { id: true }, }); for (const { id } of old) { await prisma.job.deleteMany({ where: { previewId: id } }); await prisma.preview.delete({ where: { id } }); } if (old.length > 0) log.info({ count: old.length }, "Cleaned up old previews"); }