"use server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { hasPermission } from "@/lib/permissions"; import { z } from "zod"; import bcrypt from "bcryptjs"; import { revalidatePath } from "next/cache"; import type { Role } from "@prisma/client"; import { ROLE_PREFIX, nextId } from "@/lib/id-generators"; type ActionResult = { ok: true } | { error: string }; const userSchema = z.object({ name: z.string().min(1, "Name is required"), email: z.string().email("Invalid email"), role: z.enum(["TECHNICAL", "MANNING", "ACCOUNTS", "MANAGER", "SUPERUSER", "AUDITOR", "ADMIN"]), password: z.string().min(8, "Password must be at least 8 characters").optional(), }); export async function createUser(formData: FormData): Promise { const session = await auth(); if (!session?.user || !hasPermission(session.user.role, "manage_users")) { return { error: "Unauthorized" }; } const parsed = userSchema.safeParse({ name: formData.get("name"), email: formData.get("email"), role: formData.get("role"), password: formData.get("password") || undefined, }); if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" }; const data = parsed.data; if (!data.password) return { error: "Password is required for new users" }; const exists = await db.user.findFirst({ where: { email: data.email }, }); if (exists) return { error: "A user with that email already exists" }; // Auto-generate employeeId based on role prefix const prefix = ROLE_PREFIX[data.role]; const existingIds = await db.user.findMany({ where: { role: data.role as Role }, select: { employeeId: true }, }); const employeeId = nextId(prefix, existingIds.map((u) => u.employeeId)); const passwordHash = await bcrypt.hash(data.password, 12); await db.user.create({ data: { employeeId, name: data.name, email: data.email, role: data.role as Role, passwordHash, }, }); revalidatePath("/admin/users"); return { ok: true }; } export async function updateUser(formData: FormData): Promise { const session = await auth(); if (!session?.user || !hasPermission(session.user.role, "manage_users")) { return { error: "Unauthorized" }; } const id = formData.get("id") as string; if (!id) return { error: "User ID is required" }; const parsed = userSchema.safeParse({ name: formData.get("name"), email: formData.get("email"), role: formData.get("role"), password: formData.get("password") || undefined, }); if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" }; const data = parsed.data; const conflict = await db.user.findFirst({ where: { AND: [ { id: { not: id } }, { email: data.email }, ], }, }); if (conflict) return { error: "Another user already has that email" }; const updateData: Parameters[0]["data"] = { name: data.name, email: data.email, role: data.role as Role, }; if (data.password) { updateData.passwordHash = await bcrypt.hash(data.password, 12); } await db.user.update({ where: { id }, data: updateData }); revalidatePath("/admin/users"); return { ok: true }; } export async function deleteUser(id: string): Promise { const session = await auth(); if (!session?.user || !hasPermission(session.user.role, "manage_users")) return { error: "Unauthorized" }; if (id === session.user.id) return { error: "Cannot delete your own account." }; const inUse = await db.purchaseOrder.findFirst({ where: { submitterId: id } }); if (inUse) return { error: "Cannot delete: user has submitted purchase orders. Deactivate them instead." }; await db.$transaction(async (tx) => { await tx.notification.deleteMany({ where: { userId: id } }); await tx.user.delete({ where: { id } }); }); revalidatePath("/admin/users"); return { ok: true }; } export async function toggleUserActive(userId: string): Promise { const session = await auth(); if (!session?.user || !hasPermission(session.user.role, "manage_users")) { return { error: "Unauthorized" }; } if (userId === session.user.id) return { error: "You cannot deactivate your own account" }; const user = await db.user.findUnique({ where: { id: userId }, select: { isActive: true } }); if (!user) return { error: "User not found" }; await db.user.update({ where: { id: userId }, data: { isActive: !user.isActive } }); revalidatePath("/admin/users"); return { ok: true }; }