Files
twotalesanimation 13d47315dd
Deploy / deploy (push) Successful in 2m38s
download link updated
2026-06-25 18:29:58 +02:00

115 lines
3.8 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { validateReviewToken } from "@/lib/review-auth";
/** GET /api/client/[token]/versions/[versionId] — returns version + comments for client portal */
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ token: string; versionId: string }> }
) {
const { token, versionId } = await params;
const result = await validateReviewToken(token, req);
if (result.type === "requiresPassword") {
return NextResponse.json({ requiresPassword: true }, { status: 401 });
}
if (result.type === "invalid") {
return NextResponse.json({ error: "Invalid or expired review link" }, { status: 403 });
}
const session = result.session;
const version = await db.version.findUnique({
where: { id: versionId },
include: {
shot: {
include: {
project: { select: { id: true, name: true, code: true } },
versions: {
orderBy: { versionNumber: "desc" },
select: {
id: true,
versionNumber: true,
approvalStatus: true,
isLatest: true,
fps: true,
duration: true,
createdAt: true,
},
},
},
},
task: {
include: {
project: { select: { id: true, name: true, code: true } },
shot: { select: { id: true, shotCode: true, highResKey: true, highResFilename: true } },
asset: { select: { assetCode: true, name: true } },
},
},
artist: { select: { id: true, name: true, image: true, email: true } },
approvals: {
orderBy: { createdAt: "desc" },
include: { user: { select: { id: true, name: true } } },
},
},
});
// Resolve project: from shot or from task
const projectId = version?.shot?.projectId ?? version?.task?.projectId;
if (!version || projectId !== session.projectId) {
return NextResponse.json({ error: "Version not found" }, { status: 404 });
}
// For task-only versions (no shot), require explicit client sharing
if (!version.shot && !version.isClientVisible) {
return NextResponse.json({ error: "Version not found" }, { status: 404 });
}
const comments = await db.comment.findMany({
where: { versionId },
orderBy: { frameNumber: "asc" },
include: {
author: { select: { id: true, name: true, image: true, email: true } },
replies: {
orderBy: { createdAt: "asc" },
include: {
author: { select: { id: true, name: true, image: true, email: true } },
},
},
},
});
// Resolve the shot that has the high-res file (comes from version.shot OR version.task.shot)
const highResShot = version.shot ?? (version.task as any)?.shot ?? null;
const serializedVersion = {
...version,
fileSize: version.fileSize?.toString() ?? null,
// Top-level high-res fields so the client works for both shot and task versions
hasHighRes: !!(highResShot?.highResKey),
highResShotId: highResShot?.highResKey ? (highResShot.id ?? null) : null,
highResFilename: highResShot?.highResFilename ?? null,
// Expose only whether a high-res file exists on the shot relation, never the storage key
shot: version.shot
? {
...version.shot,
hasHighRes: !!version.shot.highResKey,
highResFilename: version.shot.highResFilename ?? null,
highResKey: undefined,
}
: null,
task: version.task
? {
...version.task,
shot: (version.task as any)?.shot
? {
...((version.task as any).shot),
highResKey: undefined, // strip storage key
}
: null,
}
: null,
};
return NextResponse.json({ version: serializedVersion, comments });
}