Files
vfxreview/app/api/review-sessions/route.ts
T
twotalesanimation 824695ef30
Deploy / deploy (push) Successful in 3m16s
feat(clients): reactivate an expired client review link
Expired links on the client page get a Reactivate button (7-90 days).
PATCH /api/review-sessions pushes the expiry out and turns the link back
on with the same token, so the URL the client already has works again.
Staff roles only, as for creating a link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 05:38:42 +02:00

126 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { db } from "@/lib/db";
import { addDays } from "date-fns";
import bcrypt from "bcryptjs";
export async function GET(req: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const projectId = req.nextUrl.searchParams.get("projectId");
const sessions = await db.reviewSession.findMany({
where: projectId ? { projectId } : undefined,
orderBy: { createdAt: "desc" },
include: {
project: { select: { id: true, name: true, code: true } },
},
});
return NextResponse.json({ sessions });
}
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 as string)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await req.json();
const { projectId, label, email, expiresInDays = 30, password, allowedEpisodes } = body;
if (!projectId) {
return NextResponse.json({ error: "projectId is required" }, { status: 400 });
}
const project = await db.project.findUnique({ where: { id: projectId } });
if (!project) {
return NextResponse.json({ error: "Project not found" }, { status: 404 });
}
const passwordHash =
password && typeof password === "string" && password.length > 0
? await bcrypt.hash(password, 12)
: null;
const episodeFilter =
Array.isArray(allowedEpisodes) && allowedEpisodes.length > 0
? (allowedEpisodes as string[]).filter((e) => typeof e === "string" && e.length > 0)
: [];
const reviewSession = await db.reviewSession.create({
data: {
projectId,
label: label || `Review — ${project.name}`,
email: email || null,
passwordHash,
expiresAt: addDays(new Date(), expiresInDays),
allowedEpisodes: episodeFilter,
},
});
const appUrl =
process.env.NEXT_PUBLIC_APP_URL ||
`${req.headers.get("x-forwarded-proto") ?? "https"}://${req.headers.get("host")}`;
const portalUrl = `${appUrl}/client/${reviewSession.token}`;
return NextResponse.json({ session: reviewSession, portalUrl }, { status: 201 });
}
/**
* Reactivate a review link: push its expiry `expiresInDays` from now and turn
* it back on. The token is unchanged, so a URL the client already has works
* again rather than a new one having to be sent.
*/
export async function PATCH(req: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role as string)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const id = req.nextUrl.searchParams.get("id");
if (!id) return NextResponse.json({ error: "id required" }, { status: 400 });
const body = await req.json().catch(() => ({}));
const expiresInDays = Number(body.expiresInDays ?? 30);
if (!Number.isInteger(expiresInDays) || expiresInDays < 1 || expiresInDays > 365) {
return NextResponse.json({ error: "expiresInDays must be 1–365" }, { status: 400 });
}
const existing = await db.reviewSession.findUnique({ where: { id }, select: { id: true } });
if (!existing) return NextResponse.json({ error: "Review link not found" }, { status: 404 });
const reviewSession = await db.reviewSession.update({
where: { id },
data: { expiresAt: addDays(new Date(), expiresInDays), isActive: true },
});
return NextResponse.json({ session: reviewSession });
}
export async function DELETE(req: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const id = req.nextUrl.searchParams.get("id");
if (!id) return NextResponse.json({ error: "id required" }, { status: 400 });
await db.reviewSession.update({
where: { id },
data: { isActive: false },
});
return NextResponse.json({ success: true });
}