121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
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;
|
||
}
|