High res uploads
Deploy / deploy (push) Successful in 3m16s

This commit is contained in:
twotalesanimation
2026-06-25 09:56:41 +02:00
parent d2e686335f
commit b683976cc6
9 changed files with 364 additions and 2 deletions
@@ -0,0 +1,37 @@
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { validateReviewToken } from "@/lib/review-auth";
import { generateHetznerDownloadUrl } from "@/lib/storage";
/**
* GET /api/client/[token]/shots/[shotId]/highres/download
* Validates the client review token then returns a presigned Hetzner download URL.
*/
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ token: string; shotId: string }> }
) {
const { token, shotId } = await params;
const result = await validateReviewToken(token, req);
if (result.type !== "ok") {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { highResKey: true, highResFilename: true, projectId: true },
});
// Ensure the shot belongs to the review session's project
if (!shot || shot.projectId !== result.session.projectId) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
if (!shot.highResKey || !shot.highResFilename) {
return NextResponse.json({ error: "No high-res file available" }, { status: 404 });
}
const url = await generateHetznerDownloadUrl(shot.highResKey, shot.highResFilename);
return NextResponse.json({ url });
}
@@ -81,6 +81,15 @@ export async function GET(
const serializedVersion = {
...version,
fileSize: version.fileSize?.toString() ?? null,
// Expose only whether a high-res file exists, never the storage key
shot: version.shot
? {
...version.shot,
hasHighRes: !!version.shot.highResKey,
highResFilename: version.shot.highResFilename ?? null,
highResKey: undefined,
}
: null,
};
return NextResponse.json({ version: serializedVersion, comments });
@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { db } from "@/lib/db";
import { generateHetznerDownloadUrl } from "@/lib/storage";
/** GET /api/shots/[shotId]/highres/download — returns a short-lived presigned download URL */
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ shotId: string }> }
) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { shotId } = await params;
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { highResKey: true, highResFilename: true },
});
if (!shot?.highResKey || !shot?.highResFilename) {
return NextResponse.json({ error: "No high-res file available" }, { status: 404 });
}
const url = await generateHetznerDownloadUrl(shot.highResKey, shot.highResFilename);
return NextResponse.json({ url });
}
+85
View File
@@ -0,0 +1,85 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { db } from "@/lib/db";
import { uploadToHetzner, deleteFromHetzner } from "@/lib/storage";
export const maxDuration = 120;
/** POST /api/shots/[shotId]/highres — upload a high-res file to Hetzner */
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ shotId: string }> }
) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { shotId } = await params;
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { id: true, highResKey: true },
});
if (!shot) {
return NextResponse.json({ error: "Shot not found" }, { status: 404 });
}
const formData = await req.formData();
const file = formData.get("file") as File | null;
if (!file) {
return NextResponse.json({ error: "No file provided" }, { status: 400 });
}
if (!file.type.startsWith("video/")) {
return NextResponse.json({ error: "Only video files are accepted" }, { status: 400 });
}
// Remove previous high-res file if one exists
if (shot.highResKey) {
await deleteFromHetzner(shot.highResKey).catch(() => {});
}
const buffer = Buffer.from(await file.arrayBuffer());
const { key } = await uploadToHetzner(buffer, file.name, file.type, "highres");
await db.shot.update({
where: { id: shotId },
data: { highResKey: key, highResFilename: file.name },
});
return NextResponse.json({ success: true, filename: file.name });
}
/** DELETE /api/shots/[shotId]/highres — remove the high-res file */
export async function DELETE(
req: NextRequest,
{ params }: { params: Promise<{ shotId: string }> }
) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { shotId } = await params;
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { id: true, highResKey: true },
});
if (!shot) {
return NextResponse.json({ error: "Shot not found" }, { status: 404 });
}
if (shot.highResKey) {
await deleteFromHetzner(shot.highResKey).catch(() => {});
}
await db.shot.update({
where: { id: shotId },
data: { highResKey: null, highResFilename: null },
});
return NextResponse.json({ success: true });
}