first commit

This commit is contained in:
2026-07-16 09:35:32 +02:00
commit dd0746e2e6
179 changed files with 25679 additions and 0 deletions
@@ -0,0 +1,120 @@
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;
}