213 lines
6.5 KiB
TypeScript
213 lines
6.5 KiB
TypeScript
"use server";
|
|
|
|
import { auth } from "@/auth";
|
|
import { db } from "@/lib/db";
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
import { recalcShotStatus } from "@/lib/shot-status";
|
|
import { notifyNewVersionUploaded } from "@/lib/notifications";
|
|
import { slackNotifyVersionUploaded } from "@/lib/slack";
|
|
|
|
const createVersionSchema = z.object({
|
|
taskId: z.string().cuid(),
|
|
fileUrl: z.string().min(1),
|
|
fileName: z.string(),
|
|
fileSize: z.number().optional(),
|
|
mimeType: z.string().optional(),
|
|
thumbnailUrl: z.string().min(1).optional().or(z.literal("")),
|
|
fps: z.number().default(24),
|
|
duration: z.number().optional(),
|
|
frameCount: z.number().int().optional(),
|
|
width: z.number().int().optional(),
|
|
height: z.number().int().optional(),
|
|
notes: z.string().optional(),
|
|
});
|
|
|
|
export async function createVersion(data: z.infer<typeof createVersionSchema>) {
|
|
const session = await auth();
|
|
if (!session?.user) throw new Error("Unauthorized");
|
|
|
|
const parsed = createVersionSchema.parse(data);
|
|
|
|
// All versions must belong to a task
|
|
const task = await db.task.findUnique({
|
|
where: { id: parsed.taskId },
|
|
include: { project: true, shot: { select: { id: true, shotCode: true } } },
|
|
});
|
|
if (!task) throw new Error("Task not found");
|
|
|
|
const projectId = task.projectId;
|
|
const slackWebhook = task.project.slackWebhook ?? undefined;
|
|
const projectName = task.project.name;
|
|
const shotCode = task.shot?.shotCode;
|
|
|
|
// Mark all existing versions for this task as not latest
|
|
await db.version.updateMany({ where: { taskId: parsed.taskId }, data: { isLatest: false } });
|
|
|
|
// Move task to INTERNAL_REVIEW on upload
|
|
await db.task.update({
|
|
where: { id: parsed.taskId },
|
|
data: { status: "INTERNAL_REVIEW" },
|
|
});
|
|
|
|
// Recalculate shot status
|
|
if (task.shot) {
|
|
await recalcShotStatus(task.shot.id).catch(() => {});
|
|
}
|
|
|
|
// Get the next version number for this task
|
|
const lastVersion = await db.version.findFirst({
|
|
where: { taskId: parsed.taskId },
|
|
orderBy: { versionNumber: "desc" },
|
|
});
|
|
const versionNumber = (lastVersion?.versionNumber ?? 0) + 1;
|
|
|
|
// Create the new version
|
|
const version = await db.version.create({
|
|
data: {
|
|
versionNumber,
|
|
taskId: parsed.taskId,
|
|
artistId: session.user.id,
|
|
fileUrl: parsed.fileUrl,
|
|
fileName: parsed.fileName,
|
|
fileSize: parsed.fileSize ? BigInt(parsed.fileSize) : undefined,
|
|
mimeType: parsed.mimeType,
|
|
thumbnailUrl: parsed.thumbnailUrl || undefined,
|
|
fps: parsed.fps,
|
|
duration: parsed.duration,
|
|
frameCount: parsed.frameCount,
|
|
width: parsed.width,
|
|
height: parsed.height,
|
|
notes: parsed.notes,
|
|
isLatest: true,
|
|
},
|
|
});
|
|
|
|
// Send notifications
|
|
const user = await db.user.findUnique({ where: { id: session.user.id } });
|
|
const versionLabelStr = `v${String(versionNumber).padStart(3, "0")}`;
|
|
|
|
if (shotCode) {
|
|
await notifyNewVersionUploaded({
|
|
shotCode,
|
|
versionNumber,
|
|
projectId,
|
|
versionId: version.id,
|
|
artistName: user?.name ?? session.user.email ?? "Artist",
|
|
});
|
|
}
|
|
|
|
// Slack notification
|
|
if (slackWebhook && shotCode) {
|
|
const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
|
|
await slackNotifyVersionUploaded(slackWebhook, {
|
|
shotCode,
|
|
versionLabel: versionLabelStr,
|
|
artistName: user?.name ?? "Artist",
|
|
projectName,
|
|
reviewUrl: `${appUrl}/review/${version.id}`,
|
|
});
|
|
}
|
|
|
|
revalidatePath(`/projects/${projectId}`);
|
|
if (task.shot) revalidatePath(`/projects/${projectId}/shots/${task.shot.id}`);
|
|
revalidatePath(`/tasks/${parsed.taskId}`);
|
|
|
|
return { success: true, version, versionNumber };
|
|
}
|
|
|
|
/**
|
|
* Share a version with the client — marks it as client-visible,
|
|
* records who shared it, and moves the associated task to CLIENT_REVIEW.
|
|
*/
|
|
export async function shareVersionWithClient(versionId: string) {
|
|
const session = await auth();
|
|
if (!session?.user) throw new Error("Unauthorized");
|
|
if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role)) {
|
|
throw new Error("Insufficient permissions");
|
|
}
|
|
|
|
const version = await db.version.findUnique({
|
|
where: { id: versionId },
|
|
select: { id: true, taskId: true, isClientVisible: true },
|
|
});
|
|
if (!version) throw new Error("Version not found");
|
|
|
|
await db.version.update({
|
|
where: { id: versionId },
|
|
data: {
|
|
isClientVisible: true,
|
|
sharedAt: new Date(),
|
|
sharedById: session.user.id,
|
|
},
|
|
});
|
|
|
|
// Move task to CLIENT_REVIEW if it is in INTERNAL_REVIEW; recalc shot status
|
|
if (version.taskId) {
|
|
const task = await db.task.findUnique({
|
|
where: { id: version.taskId },
|
|
select: { status: true, projectId: true, shotId: true },
|
|
});
|
|
if (task && task.status === "INTERNAL_REVIEW") {
|
|
await db.task.update({
|
|
where: { id: version.taskId },
|
|
data: { status: "CLIENT_REVIEW" },
|
|
});
|
|
if (task.shotId) {
|
|
await recalcShotStatus(task.shotId).catch(() => {});
|
|
}
|
|
revalidatePath(`/tasks/${version.taskId}`);
|
|
revalidatePath(`/projects/${task.projectId}`);
|
|
}
|
|
}
|
|
|
|
revalidatePath(`/review/${versionId}`);
|
|
return { success: true };
|
|
}
|
|
|
|
export async function getVersionById(versionId: string) {
|
|
const session = await auth();
|
|
if (!session?.user) throw new Error("Unauthorized");
|
|
|
|
return db.version.findUnique({
|
|
where: { id: versionId },
|
|
include: {
|
|
task: {
|
|
include: {
|
|
project: { include: { client: true } },
|
|
shot: true,
|
|
asset: true,
|
|
versions: {
|
|
orderBy: { versionNumber: "desc" },
|
|
select: { id: true, versionNumber: true, approvalStatus: true, createdAt: true },
|
|
},
|
|
},
|
|
},
|
|
artist: { select: { id: true, name: true, email: true, image: true } },
|
|
approvals: {
|
|
include: { user: { select: { id: true, name: true, role: true } } },
|
|
orderBy: { createdAt: "desc" },
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function getVersionComments(versionId: string) {
|
|
return db.comment.findMany({
|
|
where: { versionId },
|
|
include: {
|
|
author: { select: { id: true, name: true, email: true, image: true, role: true } },
|
|
replies: {
|
|
include: {
|
|
author: { select: { id: true, name: true, email: true, image: true, role: true } },
|
|
},
|
|
orderBy: { createdAt: "asc" },
|
|
},
|
|
annotations: {
|
|
select: { id: true, frameNumber: true, drawingData: true, color: true, isVisible: true, authorId: true },
|
|
},
|
|
},
|
|
orderBy: { frameNumber: "asc" },
|
|
});
|
|
}
|