'use server' import { revalidatePath } from 'next/cache' import { z } from 'zod' import { db } from '@/lib/db' import { requirePermissionForAction } from '@/lib/auth/current' import { writeAudit } from '@/lib/audit/write' import { PERMISSIONS } from '@/lib/auth/permissions' import { invalidateAll } from '@/lib/auth/effective' import { de } from '@/lib/i18n/de' const schema = z.object({ discordRoleId: z.string().min(1), clearanceLevel: z.coerce.number().int().min(0).max(5), grantsMembership: z.boolean(), category: z.enum(['MEMBERSHIP', 'RANK', 'CLEARANCE', 'UNSORTED']), permissions: z.array(z.enum(PERMISSIONS)), }) export type SaveResult = { ok: true } | { ok: false; error: string } /** * Speichert die Zuordnung einer Discord-Rolle. * * Die Prüfung findet hier statt, nicht in der Oberfläche: eine Server Action * ist ein Endpunkt und wird direkt aufrufbar, egal was das UI versteckt. * * Der Trigger `role_mapping_notify` löst danach `pg_notify` aus, worauf jeder * Prozess seinen Rechte-Cache verwirft. `invalidateAll` deckt zusätzlich den * eigenen Prozess sofort ab, ohne auf die Runde über die Datenbank zu warten. */ export async function saveRoleMapping(input: unknown): Promise { const user = await requirePermissionForAction('roles.manage') const parsed = schema.safeParse(input) if (!parsed.success) { return { ok: false, error: de.roleAdmin.invalidInput } } const { discordRoleId, clearanceLevel, grantsMembership, category, permissions } = parsed.data const existing = await db.roleMapping.findUnique({ where: { discordRoleId } }) if (!existing) return { ok: false, error: de.roleAdmin.roleGone } const neue = [...new Set(permissions)].sort() // Wer Rollen ändert, ändert die Rechte anderer. Genau dafür gibt es das // Protokoll (harte Regel 3) - mit dem Vorher, sonst steht dort nur, dass // jemand etwas geändert hat, aber nicht was. await db.$transaction(async (tx) => { await tx.roleMapping.update({ where: { discordRoleId }, data: { clearanceLevel, grantsMembership, category, permissions: neue }, }) await writeAudit(tx, { actorId: user.id, action: 'roles.update', targetType: 'RoleMapping', targetId: discordRoleId, clearanceAtTime: user.permissions.clearanceLevel, meta: { label: existing.label, from: { clearanceLevel: existing.clearanceLevel, grantsMembership: existing.grantsMembership, permissions: existing.permissions, }, to: { clearanceLevel, grantsMembership, permissions: neue }, }, }) }) invalidateAll() revalidatePath('/admin/rollen') revalidatePath('/uebersicht') return { ok: true } }