import { NextRequest, NextResponse } from "next/server"; import { runCron } from "@/lib/cron-runner"; import { prisma } from "@/lib/prisma"; import { deleteTripImage, listTripImageNames, tripImageMtime, TRIP_IMAGE_URL_PREFIX, } from "@/lib/trip-image-storage"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; /** File yang lebih tua dari ini & tak direferensikan DB dianggap yatim. */ const ORPHAN_AGE_MS = 24 * 60 * 60 * 1000; /** * Cron — hapus file gambar trip yatim. * * Form create-trip multi-step mengunggah foto SEBELUM trip tersimpan; kalau * user menutup form di tengah jalan, file menggantung di disk tanpa pernah * jadi `TripImage`. Sweep ini menghapus file >24 jam yang tidak direferensikan * `TripImage` mana pun. Idempotent — aman dijalankan berulang. * * Trigger: lihat docs/CRON_SETUP.md. Header wajib `Authorization: Bearer ${CRON_SECRET}`. */ export async function GET(req: NextRequest) { const secret = process.env.CRON_SECRET; if (!secret) { console.error("[cron/cleanup-trip-images] CRON_SECRET tidak di-set"); return NextResponse.json( { error: "Server misconfigured" }, { status: 500 } ); } if (req.headers.get("authorization") !== `Bearer ${secret}`) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const outcome = await runCron("cleanup-trip-images", async () => { const names = await listTripImageNames(); if (names.length === 0) return { scanned: 0, deleted: 0 }; const referenced = await prisma.tripImage.findMany({ where: { url: { startsWith: TRIP_IMAGE_URL_PREFIX } }, select: { url: true }, }); const referencedNames = new Set( referenced.map((r) => r.url.slice(TRIP_IMAGE_URL_PREFIX.length)) ); const now = Date.now(); let deleted = 0; for (const name of names) { if (referencedNames.has(name)) continue; const mtime = await tripImageMtime(name); // File baru di-upload tapi trip belum tersimpan → beri tenggang 24 jam. if (!mtime || now - mtime.getTime() < ORPHAN_AGE_MS) continue; await deleteTripImage(name); deleted++; } return { scanned: names.length, deleted }; }); if (!outcome.ok) { console.error("[cron/cleanup-trip-images] gagal", outcome.error); return NextResponse.json( { error: "Gagal menjalankan cleanup" }, { status: 500 } ); } console.log("[cron/cleanup-trip-images] selesai", outcome.payload); return NextResponse.json({ ok: true, ...outcome.payload }); }