import { and, eq } from 'drizzle-orm'; import { PERMISSIONS, ROLE_PERMISSIONS, type Department, type Permission, type Role, type Rank, type MdtUser, } from '@d4rk-tablet/shared'; import { db } from '../../db/client'; import { mdtRanks, mdtUsers } from '../../db/schema'; import { DEFAULT_RANKS, defaultPermissionsFor } from '../../config/rankDefaults'; const OVERRIDE_ROLES: Role[] = ['admin', 'dispatch']; function asRoles(v: unknown): Role[] { if (!Array.isArray(v)) return []; const set = new Set(['admin', 'dispatch', 'police', 'ems', 'fire']); return v.filter((r): r is Role => typeof r === 'string' && set.has(r)); } /** * Effektive Rechte eines Users: Rang-Matrix(Department, Grade) ∪ Override-Rollen. * admin → alle Rechte. DB-Rang überschreibt Code-Default. */ export async function computePermissions( department: Department | null, grade: number | null, overrideRoles: Role[], ): Promise { if (overrideRoles.includes('admin')) return [...PERMISSIONS]; const perms = new Set(); if (department && grade != null) { const [row] = await db .select() .from(mdtRanks) .where(and(eq(mdtRanks.department, department), eq(mdtRanks.grade, grade))) .limit(1); const rankPerms = row ? (row.permissions as Permission[]) : defaultPermissionsFor(department, grade); for (const p of rankPerms) perms.add(p); } for (const role of overrideRoles) { if (role === 'dispatch') for (const p of ROLE_PERMISSIONS.dispatch) perms.add(p); } return [...perms]; } /** Rechte-Matrix einer Behörde: DB-Ränge über Defaults gemerged. */ export async function getRankMatrix(department: Department): Promise { const rows = await db.select().from(mdtRanks).where(eq(mdtRanks.department, department)); const byGrade = new Map(rows.map((r) => [r.grade, r])); const grades = new Set([ ...DEFAULT_RANKS[department].map((r) => r.grade), ...rows.map((r) => r.grade), ]); return [...grades] .sort((a, b) => a - b) .map((grade) => { const dbRow = byGrade.get(grade); if (dbRow) { return { department, grade, label: dbRow.label, permissions: dbRow.permissions as Permission[], }; } const def = DEFAULT_RANKS[department].find((r) => r.grade === grade)!; return { department, grade, label: def.label, permissions: def.permissions }; }); } export async function updateRank( department: Department, grade: number, input: { label?: string; permissions: Permission[] }, ): Promise { const label = input.label ?? DEFAULT_RANKS[department].find((r) => r.grade === grade)?.label ?? `Grade ${grade}`; await db .insert(mdtRanks) .values({ department, grade, label, permissions: input.permissions }) .onDuplicateKeyUpdate({ set: { label, permissions: input.permissions } }); return { department, grade, label, permissions: input.permissions }; } // ── Mitarbeiter ── function mapUser(row: typeof mdtUsers.$inferSelect): MdtUser { return { id: row.id, citizenid: row.citizenid, discordId: row.discordId, name: row.name, callsign: row.callsign, department: row.department, grade: row.grade, roles: asRoles(row.roles), }; } export async function listUsers(): Promise { const rows = await db.select().from(mdtUsers); return rows.map(mapUser); } export async function updateUserRoles(id: number, roles: string[]): Promise { const filtered = asRoles(roles).filter((r) => OVERRIDE_ROLES.includes(r)); await db.update(mdtUsers).set({ roles: filtered }).where(eq(mdtUsers.id, id)); const [row] = await db.select().from(mdtUsers).where(eq(mdtUsers.id, id)).limit(1); return row ? mapUser(row) : null; }