Files
vfxreview/app/api/batch-upload/presign/route.ts
T
twotalesanimation 519fe2ad33
Deploy / deploy (push) Successful in 2m53s
MOV upload update
2026-07-21 23:31:32 +02:00

58 lines
1.8 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { generateHetznerPresignedUploadUrl } from "@/lib/storage";
import { randomUUID } from "crypto";
export const maxDuration = 10;
/**
* POST /api/batch-upload/presign
* Body: { fileName: string }
*
* Returns a presigned PUT URL for uploading a high-res file directly from
* the browser to Hetzner Object Storage, together with the storage key that
* must be passed to POST /api/batch-upload/upload (action=update-highres) to
* commit the shot record after the direct upload completes.
*
* This two-phase approach avoids routing large MOV files through the app
* server / reverse proxy, removing any client_max_body_size limits.
*/
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 { fileName } = body as { fileName?: unknown };
if (!fileName || typeof fileName !== "string") {
return NextResponse.json({ error: "fileName is required" }, { status: 400 });
}
const ext = fileName.split(".").pop()?.toLowerCase();
const contentType =
ext === "mov" ? "video/quicktime" :
ext === "mp4" ? "video/mp4" :
"application/octet-stream";
const key = `highres/${randomUUID()}-${fileName}`;
try {
const presignedUrl = await generateHetznerPresignedUploadUrl(key, contentType);
return NextResponse.json({ presignedUrl, key });
} catch (err) {
console.error("[batch-upload/presign]", err);
return NextResponse.json(
{ error: "Failed to generate upload URL. Check Hetzner storage configuration." },
{ status: 500 }
);
}
}