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

144 lines
4.9 KiB
TypeScript

"use server";
import { auth } from "@/auth";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { Role } from "@prisma/client";
import bcrypt from "bcryptjs";
const MANAGED_BY_ADMIN_ONLY: Role[] = [Role.ADMIN];
function requireAdmin(role: string) {
if (role !== "ADMIN") throw new Error("Admin access required");
}
// ── Create User ──────────────────────────────────────────────────────────────
const createUserSchema = z.object({
name: z.string().max(100).optional(),
email: z.string().email("Invalid email"),
password: z.string().min(8, "Password must be at least 8 characters"),
role: z.nativeEnum(Role),
});
export async function createUser(data: z.infer<typeof createUserSchema>) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
requireAdmin(session.user.role as string);
const parsed = createUserSchema.parse(data);
const existing = await db.user.findUnique({ where: { email: parsed.email } });
if (existing) throw new Error("A user with that email already exists");
const passwordHash = await bcrypt.hash(parsed.password, 12);
const user = await db.user.create({
data: {
name: parsed.name || null,
email: parsed.email,
passwordHash,
role: parsed.role,
isActive: true,
mustChangePassword: true,
},
});
revalidatePath("/users");
return { success: true, userId: user.id };
}
// ── Update User ──────────────────────────────────────────────────────────────
const updateUserSchema = z.object({
userId: z.string().cuid(),
name: z.string().max(100).optional(),
role: z.nativeEnum(Role),
isActive: z.boolean(),
newPassword: z.string().min(8, "Password must be at least 8 characters").optional().or(z.literal("")),
});
export async function updateUser(data: z.infer<typeof updateUserSchema>) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
requireAdmin(session.user.role as string);
const parsed = updateUserSchema.parse(data);
// Prevent demoting or deactivating yourself
if (parsed.userId === session.user.id) {
if (!parsed.isActive) throw new Error("You cannot deactivate your own account");
if (parsed.role !== "ADMIN") throw new Error("You cannot change your own role");
}
const updateData: Record<string, unknown> = {
name: parsed.name || null,
role: parsed.role,
isActive: parsed.isActive,
};
if (parsed.newPassword) {
updateData.passwordHash = await bcrypt.hash(parsed.newPassword, 12);
updateData.mustChangePassword = true;
}
await db.user.update({ where: { id: parsed.userId }, data: updateData });
revalidatePath("/users");
return { success: true };
}
// ── Change Own Password ───────────────────────────────────────────────────────
const changeOwnPasswordSchema = z.object({
currentPassword: z.string().min(1, "Current password is required"),
newPassword: z.string().min(8, "New password must be at least 8 characters"),
});
export async function changeOwnPassword(data: z.infer<typeof changeOwnPasswordSchema>) {
const session = await auth();
if (!session?.user?.id) throw new Error("Unauthorized");
const parsed = changeOwnPasswordSchema.parse(data);
const user = await db.user.findUnique({
where: { id: session.user.id },
select: { passwordHash: true },
});
if (!user?.passwordHash) throw new Error("No password set on this account");
const isValid = await bcrypt.compare(parsed.currentPassword, user.passwordHash);
if (!isValid) throw new Error("Current password is incorrect");
if (parsed.currentPassword === parsed.newPassword) {
throw new Error("New password must be different from the current password");
}
const passwordHash = await bcrypt.hash(parsed.newPassword, 12);
await db.user.update({
where: { id: session.user.id },
data: { passwordHash, mustChangePassword: false },
});
revalidatePath("/settings");
return { success: true };
}
// ── Delete User ───────────────────────────────────────────────────────────────
export async function deleteUser(userId: string) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
requireAdmin(session.user.role as string);
if (userId === session.user.id) throw new Error("You cannot delete your own account");
await db.user.delete({ where: { id: userId } });
revalidatePath("/users");
return { success: true };
}