@@ -0,0 +1,73 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
const IMAGE_EXTS = new Set(["jpg", "jpeg", "png", "webp", "tiff", "tif", "avif"]);
|
||||
|
||||
export interface ThumbnailPreviewItem {
|
||||
fileName: string;
|
||||
stemName: string;
|
||||
shotCode: string | null;
|
||||
shotId: string | null;
|
||||
status: "match" | "no-match";
|
||||
currentThumbnailUrl: string | null;
|
||||
}
|
||||
|
||||
function stem(fileName: string): string {
|
||||
const dot = fileName.lastIndexOf(".");
|
||||
return (dot > 0 ? fileName.slice(0, dot) : fileName).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/batch-upload/thumbnails
|
||||
* Body: { projectId: string; fileNames: string[] }
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
let body: { projectId?: string; fileNames?: string[] };
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { projectId, fileNames } = body;
|
||||
if (!projectId || !Array.isArray(fileNames)) {
|
||||
return NextResponse.json({ error: "projectId and fileNames are required" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Only image files
|
||||
const imageFiles = fileNames.filter((f) => {
|
||||
const ext = f.split(".").pop()?.toLowerCase() ?? "";
|
||||
return IMAGE_EXTS.has(ext);
|
||||
});
|
||||
|
||||
const shots = await db.shot.findMany({
|
||||
where: { projectId },
|
||||
select: { id: true, shotCode: true, thumbnailUrl: true },
|
||||
});
|
||||
|
||||
// Build a lowercase map for case-insensitive matching
|
||||
const shotMap = new Map(shots.map((s) => [s.shotCode.toLowerCase(), s]));
|
||||
|
||||
const items: ThumbnailPreviewItem[] = fileNames.map((fileName) => {
|
||||
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
||||
if (!IMAGE_EXTS.has(ext)) {
|
||||
return { fileName, stemName: stem(fileName), shotCode: null, shotId: null, status: "no-match" as const, currentThumbnailUrl: null };
|
||||
}
|
||||
const s = stem(fileName);
|
||||
const shot = shotMap.get(s) ?? null;
|
||||
return {
|
||||
fileName,
|
||||
stemName: s,
|
||||
shotCode: shot?.shotCode ?? null,
|
||||
shotId: shot?.id ?? null,
|
||||
status: shot ? "match" : "no-match",
|
||||
currentThumbnailUrl: shot?.thumbnailUrl ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json({ items });
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { uploadToHetzner } from "@/lib/storage";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
/**
|
||||
* POST /api/batch-upload/thumbnails/upload
|
||||
* FormData: { projectId, shotId, file (image) }
|
||||
*/
|
||||
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 });
|
||||
}
|
||||
|
||||
const formData = await req.formData();
|
||||
const file = formData.get("file") as File | null;
|
||||
const shotId = formData.get("shotId") as string | null;
|
||||
const projectId = formData.get("projectId") as string | null;
|
||||
|
||||
if (!file || !shotId || !projectId) {
|
||||
return NextResponse.json({ error: "file, shotId and projectId are required" }, { status: 400 });
|
||||
}
|
||||
if (!file.type.startsWith("image/")) {
|
||||
return NextResponse.json({ error: "File must be an image" }, { 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 buffer = Buffer.from(await file.arrayBuffer());
|
||||
const { key } = await uploadToHetzner(buffer, file.name, file.type, "image");
|
||||
const thumbnailUrl = `/api/files/${key}`;
|
||||
|
||||
await db.shot.update({ where: { id: shotId }, data: { thumbnailUrl } });
|
||||
revalidatePath(`/projects/${projectId}`);
|
||||
|
||||
return NextResponse.json({ success: true, thumbnailUrl });
|
||||
}
|
||||
Reference in New Issue
Block a user