75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
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 });
|
|
}
|