"use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { z } from "zod"; import { requireAdmin, requireUser } from "@/lib/auth/dal"; import type { FormState } from "@/lib/forms"; import { zodErrorToFormState } from "@/lib/forms"; import { isUniqueViolation } from "@/lib/services/errors"; import { changeOwnPassword, createUser, deleteUser, updateUser, } from "@/lib/services/users"; import { changePasswordFormSchema, createUserFormSchema, updateUserFormSchema, } from "@/lib/validation"; function readPermissions(formData: FormData) { return { canCreateTags: formData.get("canCreateTags"), canPublishPosts: formData.get("canPublishPosts"), canUnpublishPosts: formData.get("canUnpublishPosts"), canDeletePosts: formData.get("canDeletePosts"), canApproveComments: formData.get("canApproveComments"), }; } export async function createUserAction( _prev: FormState, formData: FormData, ): Promise { await requireAdmin(); const parsed = createUserFormSchema.safeParse({ username: formData.get("username"), password: formData.get("password"), tagIds: formData.getAll("tagIds"), permissions: readPermissions(formData), }); if (!parsed.success) return zodErrorToFormState(parsed.error); try { await createUser(parsed.data); } catch (error) { if (isUniqueViolation(error)) { return { fieldErrors: { username: ["That username is already taken."] } }; } console.error("createUserAction failed", error); return { formError: "Something went wrong while creating the account." }; } redirect("/admin/users?created=1"); } export async function updateUserAction( id: number, _prev: FormState, formData: FormData, ): Promise { await requireAdmin(); const userId = z.number().int().positive().parse(id); const parsed = updateUserFormSchema.safeParse({ password: formData.get("password"), tagIds: formData.getAll("tagIds"), permissions: readPermissions(formData), }); if (!parsed.success) return zodErrorToFormState(parsed.error); try { const user = await updateUser(userId, parsed.data); if (!user) return { formError: "That account no longer exists." }; } catch (error) { console.error("updateUserAction failed", error); return { formError: "Something went wrong while saving the account." }; } // Tag grants gate what authors can post — refresh admin pages. revalidatePath("/admin", "layout"); return { status: "success" }; } /** Any signed-in account may change its own password. */ export async function changeOwnPasswordAction( _prev: FormState, formData: FormData, ): Promise { const user = await requireUser(); const parsed = changePasswordFormSchema.safeParse({ currentPassword: formData.get("currentPassword"), newPassword: formData.get("newPassword"), }); if (!parsed.success) return zodErrorToFormState(parsed.error); try { const result = await changeOwnPassword( user.id, parsed.data.currentPassword, parsed.data.newPassword, ); if (!result.ok) return { fieldErrors: { currentPassword: [result.error] } }; } catch (error) { console.error("changeOwnPasswordAction failed", error); return { formError: "Something went wrong while changing your password." }; } return { status: "success" }; } export async function deleteUserAction(id: number): Promise { const admin = await requireAdmin(); const userId = z.number().int().positive().parse(id); // requireAdmin + the service's admin-role guard both protect the admin // account; this guards the sillier accident of deleting yourself. if (userId === admin.id) return; await deleteUser(userId); revalidatePath("/admin", "layout"); redirect("/admin/users?deleted=1"); }