import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { parseFilename, stripVersionFromTitle, findMatchingShotCode, } from "@/lib/batch-upload-parser"; export type PreviewItemStatus = | "new-version" // task found, same version prefix → just add a version | "rename-and-upload" // task found but different version → rename task + add version | "create-task" // no matching task → use/rename fallback "Comp" task or create new | "update-highres" // .mov file → replace the shot's high-res file | "no-shot" // shot code not found in this project | "unsupported"; // file extension is not .mp4 or .mov export interface PreviewItem { fileName: string; status: PreviewItemStatus; shotId: string | null; shotCode: string | null; /** For .mp4: ID of the directly matched task, if found */ taskId: string | null; /** For .mp4: current title of the matched / fallback task */ currentTaskTitle: string | null; /** For .mp4: desired task title after upload (filename without extension) */ newTaskTitle: string; /** For .mov: filename of the existing high-res file that will be replaced */ currentHighResFilename: string | null; /** For create-task: ID of a "Comp" task to rename & use */ fallbackTaskId: string | null; fallbackTaskTitle: string | null; } /** * POST /api/batch-upload/preview * Body: { projectId: string; fileNames: string[] } * * Returns a preview of what action will be taken for each file, without * actually uploading anything. */ export async function POST(req: NextRequest) { const session = await auth(); if (!session?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); } const { projectId, fileNames } = body as { projectId?: string; fileNames?: unknown; }; if (!projectId || !Array.isArray(fileNames) || fileNames.length === 0) { return NextResponse.json( { error: "projectId and fileNames[] are required" }, { status: 400 } ); } // Load all shots for this project with their tasks const shots = await db.shot.findMany({ where: { projectId }, select: { id: true, shotCode: true, highResFilename: true, tasks: { select: { id: true, title: true, type: true }, orderBy: { sortOrder: "asc" }, }, }, }); const shotCodes = shots.map((s) => s.shotCode); const items: PreviewItem[] = fileNames.map((fileName: string) => { const parsed = parseFilename(fileName); if (parsed.type === "other") { return { fileName, status: "unsupported" as const, shotId: null, shotCode: null, taskId: null, currentTaskTitle: null, newTaskTitle: "", currentHighResFilename: null, fallbackTaskId: null, fallbackTaskTitle: null, }; } // Find the shot whose code is a prefix of this file's task name base const matchedCode = findMatchingShotCode(parsed.taskNameBase, shotCodes); const matchedShot = matchedCode ? shots.find((s) => s.shotCode === matchedCode) ?? null : null; if (!matchedShot) { return { fileName, status: "no-shot" as const, shotId: null, shotCode: null, taskId: null, currentTaskTitle: null, newTaskTitle: "", currentHighResFilename: null, fallbackTaskId: null, fallbackTaskTitle: null, }; } // ── .mov → update-highres ───────────────────────────────────────────── if (parsed.type === "mov") { return { fileName, status: "update-highres" as const, shotId: matchedShot.id, shotCode: matchedShot.shotCode, taskId: null, currentTaskTitle: null, newTaskTitle: "", currentHighResFilename: matchedShot.highResFilename ?? null, fallbackTaskId: null, fallbackTaskTitle: null, }; } // ── .mp4 → find matching task ───────────────────────────────────────── const desiredTitle = parsed.taskNameWithVersion; // full name without ext // Match by stripping version from existing task titles const matchedTask = matchedShot.tasks.find( (t) => stripVersionFromTitle(t.title) === parsed.taskNameBase ); if (matchedTask) { const sameTitle = matchedTask.title === desiredTitle; return { fileName, status: (sameTitle ? "new-version" : "rename-and-upload") as PreviewItemStatus, shotId: matchedShot.id, shotCode: matchedShot.shotCode, taskId: matchedTask.id, currentTaskTitle: matchedTask.title, newTaskTitle: desiredTitle, currentHighResFilename: null, fallbackTaskId: null, fallbackTaskTitle: null, }; } // No direct match – look for a generic "Comp" or "comp" task, or any COMP type const compTask = matchedShot.tasks.find( (t) => t.title.toLowerCase() === "comp" || t.type === "COMP" ); return { fileName, status: "create-task" as const, shotId: matchedShot.id, shotCode: matchedShot.shotCode, taskId: null, currentTaskTitle: null, newTaskTitle: desiredTitle, currentHighResFilename: null, fallbackTaskId: compTask?.id ?? null, fallbackTaskTitle: compTask?.title ?? null, }; }); return NextResponse.json({ items }); }