Initial commit

This commit is contained in:
twotalesanimation
2026-05-19 22:20:29 +02:00
commit 0fbe856dce
173 changed files with 38316 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
"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" },
},
},
});
}