import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { revalidatePath } from "next/cache"; export const maxDuration = 30; /** * POST /api/batch-upload/plates/upload * Body: { projectId, shotId, key, fileName, fileSize?, label? } * * Commits a footage plate whose bytes were already uploaded straight to * Hetzner via POST /api/upload/presign, so large .mov plates never pass * through this server or the reverse proxy. */ export async function POST(req: NextRequest) { const session = await auth(); if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role)) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); } const { projectId, shotId, key, fileName, fileSize, label } = body as { projectId?: unknown; shotId?: unknown; key?: unknown; fileName?: unknown; fileSize?: unknown; label?: unknown; }; if ( typeof projectId !== "string" || typeof shotId !== "string" || typeof key !== "string" || typeof fileName !== "string" || !projectId || !shotId || !key || !fileName ) { return NextResponse.json( { error: "projectId, shotId, key and fileName are required" }, { status: 400 } ); } // The key must be one this app handed out via /api/upload/presign. if (!key.startsWith("videos/")) { return NextResponse.json({ error: "Invalid storage key" }, { status: 400 }); } const shot = await db.shot.findFirst({ where: { id: shotId, projectId }, select: { id: true }, }); if (!shot) return NextResponse.json({ error: "Shot not found" }, { status: 404 }); const last = await db.footagePlate.findFirst({ where: { shotId }, orderBy: { sortOrder: "desc" }, select: { sortOrder: true }, }); const size = typeof fileSize === "number" && Number.isFinite(fileSize) && fileSize > 0 ? BigInt(Math.round(fileSize)) : null; const plate = await db.footagePlate.create({ data: { shotId, fileUrl: `/api/files/${key}`, fileKey: key, fileName, fileSize: size, label: typeof label === "string" ? label.trim().slice(0, 100) : "", sortOrder: (last?.sortOrder ?? -1) + 1, }, }); revalidatePath(`/projects/${projectId}`); revalidatePath(`/projects/${projectId}/shots/${shotId}`); return NextResponse.json({ success: true, plateId: plate.id, label: plate.label, }); }