Files
d4rk_tablet/backend/api/src/modules/admin/admin.service.ts
T
2026-07-16 09:35:32 +02:00

121 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string>(['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<Permission[]> {
if (overrideRoles.includes('admin')) return [...PERMISSIONS];
const perms = new Set<Permission>();
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<Rank[]> {
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<number>([
...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<Rank> {
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<MdtUser[]> {
const rows = await db.select().from(mdtUsers);
return rows.map(mapUser);
}
export async function updateUserRoles(id: number, roles: string[]): Promise<MdtUser | null> {
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;
}