180 lines
6.3 KiB
TypeScript
180 lines
6.3 KiB
TypeScript
"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<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" },
|
||
},
|
||
},
|
||
});
|
||
}
|