Files
twotalesanimation 0fbe856dce Initial commit
2026-05-19 22:20:29 +02:00

180 lines
6.3 KiB
TypeScript
Raw Permalink 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.
"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, "110 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<typeof createProjectSchema>) {
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<typeof updateProjectSchema>) {
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" },
},
},
});
}