/* ===================================================================== routes/roles.js — Rollen erstellen/umbenennen/verwalten. 1:1 portiert aus cloudflare-worker/src/routes/roles.js. Die Owner-Rolle existiert absichtlich NICHT in dieser Tabelle — sie ist fest an POSTFACH_CODE gebunden und kann hier weder erstellt, verändert noch gelöscht werden (siehe Anforderungsdokument: "Meine persönliche Hauptadministrator-Rolle bleibt davon ausgeschlossen"). ===================================================================== */ import { db } from "../db.js"; import { generateId, nowIso } from "../lib/crypto.js"; import { isValidPermissionKey, hasPermission } from "../lib/permissions.js"; import { logAction } from "../lib/audit.js"; import { json } from "../lib/http.js"; function canManageRoles(session) { if (!session) return false; if (session.isOwner) return true; return ( hasPermission(session.permissions, session.overrides, "TEAM_CREATE_ROLE") || hasPermission(session.permissions, session.overrides, "TEAM_EDIT_PERMISSIONS") ); } function canViewRoles(session) { if (!session) return false; if (session.isOwner) return true; return ( canManageRoles(session) || hasPermission(session.permissions, session.overrides, "TEAM_ASSIGN_ROLE") || hasPermission(session.permissions, session.overrides, "TEAM_VIEW") ); } function sanitizePermissions(list) { if (!Array.isArray(list)) return []; return [...new Set(list.filter((k) => typeof k === "string" && isValidPermissionKey(k)))]; } export async function listRoles(req, res, session) { if (!canViewRoles(session)) return json(res, { ok: false, error: "Keine Berechtigung." }, 403); const rows = db.prepare(`SELECT * FROM roles ORDER BY created_at ASC`).all(); const roles = rows.map((r) => ({ ...r, permissions: JSON.parse(r.permissions || "[]") })); return json(res, { ok: true, roles }); } export async function createRole(req, res, session) { if (!canManageRoles(session)) return json(res, { ok: false, error: "Keine Berechtigung." }, 403); const body = req.body || {}; const name = String(body.name || "").trim().slice(0, 60); if (!name) return json(res, { ok: false, error: "Name fehlt." }, 400); const permissions = sanitizePermissions(body.permissions); const id = generateId(); db.prepare(`INSERT INTO roles (id, name, permissions, created_at) VALUES (?, ?, ?, ?)`).run(id, name, JSON.stringify(permissions), nowIso()); logAction(session.actor, "role.create", id, { name, permissions }); return json(res, { ok: true, role: { id, name, permissions } }); } export async function updateRole(req, res, session) { if (!canManageRoles(session)) return json(res, { ok: false, error: "Keine Berechtigung." }, 403); const body = req.body || {}; const id = String(body.id || ""); if (!id) return json(res, { ok: false, error: "id fehlt." }, 400); const existing = db.prepare(`SELECT * FROM roles WHERE id = ?`).get(id); if (!existing) return json(res, { ok: false, error: "Rolle nicht gefunden." }, 404); const name = body.name !== undefined ? String(body.name).trim().slice(0, 60) || existing.name : existing.name; const permissions = body.permissions !== undefined ? sanitizePermissions(body.permissions) : JSON.parse(existing.permissions || "[]"); db.prepare(`UPDATE roles SET name = ?, permissions = ? WHERE id = ?`).run(name, JSON.stringify(permissions), id); logAction(session.actor, "role.update", id, { name, permissions }); return json(res, { ok: true }); } export async function deleteRole(req, res, session) { if (!canManageRoles(session)) return json(res, { ok: false, error: "Keine Berechtigung." }, 403); const body = req.body || {}; const id = String(body.id || ""); if (!id) return json(res, { ok: false, error: "id fehlt." }, 400); const inUse = db.prepare(`SELECT COUNT(*) as n FROM users WHERE role_id = ?`).get(id); if (inUse && inUse.n > 0) { return json(res, { ok: false, error: "Rolle ist noch Personen zugewiesen — erst umziehen." }, 400); } db.prepare(`DELETE FROM roles WHERE id = ?`).run(id); logAction(session.actor, "role.delete", id, null); return json(res, { ok: true }); }