karbe/src/app/admin/organizations/actions.ts

88 lines
3.4 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";
import { auth } from "@/auth";
import { UserRole } from "@/generated/prisma/enums";
import { requireRole } from "@/lib/authorization";
import { prisma } from "@/lib/prisma";
async function audit(event: string, target: string, actor: string | null, details: unknown) {
console.log(JSON.stringify({ scope: "admin.organizations", event, target, actor, details, at: new Date().toISOString() }));
}
const slugRe = /^[a-z0-9](?:[a-z0-9-]{0,80}[a-z0-9])?$/;
const orgSchema = z.object({
name: z.string().trim().min(2).max(200),
slug: z.string().trim().regex(slugRe, "Slug invalide (a-z, 0-9, -)"),
description: z.string().trim().max(5000).optional().nullable(),
});
function parseFD(fd: FormData) {
return {
name: (fd.get("name") as string | null) ?? "",
slug: (fd.get("slug") as string | null) ?? "",
description: ((fd.get("description") as string | null) ?? "") || null,
};
}
export async function createOrganizationAction(fd: FormData) {
await requireRole([UserRole.ADMIN]);
const parsed = orgSchema.safeParse(parseFD(fd));
if (!parsed.success) {
return { ok: false as const, error: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(" · ") };
}
const session = await auth();
try {
const created = await prisma.organization.create({
data: { name: parsed.data.name, slug: parsed.data.slug, description: parsed.data.description ?? null },
});
await audit("organization.create", created.id, session?.user?.email ?? null, { slug: created.slug });
revalidatePath("/admin/organizations");
} catch (e) {
if (e instanceof Error && e.message.includes("Unique")) {
return { ok: false as const, error: "Ce slug existe déjà." };
}
return { ok: false as const, error: "Erreur lors de la création." };
}
redirect("/admin/organizations");
}
export async function updateOrganizationAction(id: string, fd: FormData) {
await requireRole([UserRole.ADMIN]);
const parsed = orgSchema.safeParse(parseFD(fd));
if (!parsed.success) {
return { ok: false as const, error: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(" · ") };
}
const session = await auth();
try {
await prisma.organization.update({
where: { id },
data: { name: parsed.data.name, slug: parsed.data.slug, description: parsed.data.description ?? null },
});
} catch (e) {
if (e instanceof Error && e.message.includes("Unique")) {
return { ok: false as const, error: "Ce slug est déjà pris." };
}
return { ok: false as const, error: "Erreur lors de la mise à jour." };
}
await audit("organization.update", id, session?.user?.email ?? null, { slug: parsed.data.slug });
revalidatePath("/admin/organizations");
revalidatePath(`/admin/organizations/${id}`);
return { ok: true as const };
}
export async function deleteOrganizationAction(id: string) {
await requireRole([UserRole.ADMIN]);
const session = await auth();
const count = await prisma.user.count({ where: { organizationId: id } });
if (count > 0) {
return { ok: false as const, error: `Impossible : ${count} membre(s) encore rattaché(s).` };
}
await prisma.organization.delete({ where: { id } });
await audit("organization.delete", id, session?.user?.email ?? null, {});
revalidatePath("/admin/organizations");
redirect("/admin/organizations");
}