import { NextRequest, NextResponse } from "next/server"; import { randomUUID } from "crypto"; import { isExtAuthorized } from "@/lib/render-pipeline/ext-auth"; import { toErrorResponse, PipelineError } from "@/lib/render-pipeline/errors"; import { db } from "@/lib/db"; import { generateHetznerPresignedUploadUrl, sanitizeFileName } from "@/lib/storage"; // ── POST /api/ext/render/jobs/{jobId}/artifact-presign (E20) ───────────────── // // Presigned upload URL for worker artifacts. Kinds map to the existing object // storage folder layout: preview → videos/, thumbnail → image/, // metadata/log → renders/. const KIND_FOLDERS: Record = { preview: "videos", thumbnail: "image", metadata: "renders", log: "renders", }; export async function POST( req: NextRequest, { params }: { params: Promise<{ jobId: string }> } ) { if (!isExtAuthorized(req)) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const { jobId } = await params; let body: { machineId?: string; kind?: string; fileName?: string; contentType?: string }; try { body = await req.json(); } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); } try { if (!body.machineId) throw new PipelineError(400, "machineId is required"); if (!body.fileName) throw new PipelineError(400, "fileName is required"); if (!body.contentType) throw new PipelineError(400, "contentType is required"); const folder = KIND_FOLDERS[body.kind ?? ""]; if (!folder) { throw new PipelineError(422, `kind must be one of: ${Object.keys(KIND_FOLDERS).join(", ")}`); } const job = await db.renderJob.findUnique({ where: { id: jobId }, select: { machineId: true } }); if (!job) throw new PipelineError(404, "Render job not found"); if (job.machineId !== body.machineId) { throw new PipelineError(409, "Job is not held by this machine (lease reassigned?)"); } const key = `${folder}/${randomUUID()}-${sanitizeFileName(body.fileName)}`; const presignedUrl = await generateHetznerPresignedUploadUrl(key, body.contentType); return NextResponse.json({ presignedUrl, key, url: `/api/files/${key}` }); } catch (err) { const { body: errBody, status } = toErrorResponse(err); return NextResponse.json(errBody, { status }); } }