"use server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import { ProjectStatus } from "@prisma/client"; const createProjectSchema = z.object({ name: z.string().min(1).max(100), code: z.string().min(1).max(20).toUpperCase(), showId: z.string().min(1).max(10).regex(/^[A-Z0-9_]+$/i, "1–10 chars, letters/numbers/underscores").toUpperCase(), projectType: z.enum(["STANDARD", "EPISODIC"]).default("STANDARD"), description: z.string().optional(), clientId: z.string().cuid().optional(), dueDate: z.string().optional(), deadline: z.union([z.string(), z.date()]).optional(), startDate: z.string().optional(), slackWebhook: z.string().url().optional().or(z.literal("")), slackChannel: z.string().optional(), }); export async function createProject(data: z.infer) { const session = await auth(); if (!session?.user) throw new Error("Unauthorized"); if (!["ADMIN", "PRODUCER"].includes(session.user.role)) { throw new Error("Insufficient permissions"); } const parsed = createProjectSchema.parse(data); // Verify the session user still exists in the DB (guards against stale JWT after a DB reset) const dbUser = await db.user.findUnique({ where: { id: session.user.id }, select: { id: true }, }); if (!dbUser) throw new Error("Session expired — please sign out and sign back in."); const project = await db.project.create({ data: { name: parsed.name, code: parsed.code, showId: parsed.showId, projectType: parsed.projectType, description: parsed.description, clientId: parsed.clientId || undefined, producerId: session.user.id, dueDate: parsed.dueDate ? new Date(parsed.dueDate) : parsed.deadline ? new Date(parsed.deadline) : undefined, startDate: parsed.startDate ? new Date(parsed.startDate) : undefined, slackWebhook: parsed.slackWebhook || undefined, slackChannel: parsed.slackChannel || undefined, }, include: { client: true }, }); revalidatePath("/projects"); return { success: true, project }; } const updateProjectSchema = z.object({ id: z.string().cuid(), name: z.string().min(1).max(100).optional(), code: z.string().min(1).max(20).optional(), showId: z.string().min(1).max(10).regex(/^[A-Z0-9_]+$/i).toUpperCase().optional(), projectType: z.enum(["STANDARD", "EPISODIC"]).optional(), description: z.string().optional().nullable(), status: z.nativeEnum(ProjectStatus).optional(), clientId: z.string().cuid().optional().nullable(), producerId: z.string().cuid().optional().nullable(), supervisorId: z.string().cuid().optional().nullable(), dueDate: z.string().optional().nullable(), startDate: z.string().optional().nullable(), slackWebhook: z.string().url().optional().or(z.literal("")).nullable(), slackChannel: z.string().optional().nullable(), }); export async function updateProject(data: z.infer) { 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 { id, ...rest } = updateProjectSchema.parse(data); const updated = await db.project.update({ where: { id }, data: { ...(rest.name !== undefined && { name: rest.name }), ...(rest.code !== undefined && { code: rest.code.toUpperCase() }), ...(rest.showId !== undefined && { showId: rest.showId }), ...(rest.projectType !== undefined && { projectType: rest.projectType }), ...(rest.description !== undefined && { description: rest.description ?? undefined }), ...(rest.status !== undefined && { status: rest.status }), ...(rest.clientId !== undefined && { clientId: rest.clientId }), ...(rest.producerId !== undefined && { producerId: rest.producerId }), ...(rest.supervisorId !== undefined && { supervisorId: rest.supervisorId }), ...(rest.dueDate !== undefined && { dueDate: rest.dueDate ? new Date(rest.dueDate) : null }), ...(rest.startDate !== undefined && { startDate: rest.startDate ? new Date(rest.startDate) : null }), ...(rest.slackWebhook !== undefined && { slackWebhook: rest.slackWebhook || null }), ...(rest.slackChannel !== undefined && { slackChannel: rest.slackChannel || null }), }, }); revalidatePath(`/projects/${id}`); revalidatePath("/projects"); return { success: true, project: updated }; } export async function updateProjectStatus( projectId: string, status: ProjectStatus ) { const session = await auth(); if (!session?.user) throw new Error("Unauthorized"); if (!["ADMIN", "PRODUCER"].includes(session.user.role)) { throw new Error("Insufficient permissions"); } await db.project.update({ where: { id: projectId }, data: { status } }); revalidatePath(`/projects/${projectId}`); return { success: true }; } export async function getProjects() { const session = await auth(); if (!session?.user) throw new Error("Unauthorized"); // Clients only see their assigned projects if (session.user.role === "CLIENT") { const access = await db.clientAccess.findMany({ where: { userId: session.user.id }, select: { clientId: true }, }); const clientIds = access.map((a) => a.clientId); return db.project.findMany({ where: { clientId: { in: clientIds }, status: { not: "ARCHIVED" } }, include: { client: true, _count: { select: { shots: true } }, }, orderBy: { updatedAt: "desc" }, }); } return db.project.findMany({ include: { client: true, producer: { select: { id: true, name: true } }, _count: { select: { shots: true } }, }, orderBy: { updatedAt: "desc" }, }); } export async function getProjectById(id: string) { const session = await auth(); if (!session?.user) throw new Error("Unauthorized"); return db.project.findUnique({ where: { id }, include: { client: true, producer: { select: { id: true, name: true, email: true } }, supervisor: { select: { id: true, name: true, email: true } }, shots: { include: { artist: { select: { id: true, name: true, image: true } }, _count: { select: { versions: true } }, }, orderBy: { shotCode: "asc" }, }, }, }); }