import { z } from "zod"; import { router, oeffentlich, angemeldet, mitRecht } from "@/server/trpc"; import { ROLLEN_BEZEICHNUNG, rechteVon } from "@/lib/authz"; import { datenbankErreichbar } from "@/lib/db"; import { speicherErreichbar } from "@/lib/storage"; import { geraetRouter } from "@/server/routers/stammdaten/geraet"; import { fahrzeugRouter } from "@/server/routers/stammdaten/fahrzeug"; import { standortRouter, geraeteraumRouter } from "@/server/routers/stammdaten/orte"; import { geraetetypRouter, qualifikationstypRouter } from "@/server/routers/stammdaten/katalog"; import { personRouter, qualifikationRouter } from "@/server/routers/stammdaten/person"; import { importRouter } from "@/server/routers/stammdaten/import"; /** * Wurzel-Router. * * Aufbau nach Fachbereichen, nicht nach Tabellen: wer die Geräteliste sucht, * findet sie unter geraet, nicht unter irgendeinem technischen Namen. */ const systemRouter = router({ /** Erreichbarkeit der Randsysteme. Ohne Anmeldung abrufbar. */ status: oeffentlich.query(async () => { const [datenbank, speicher] = await Promise.all([ datenbankErreichbar(), speicherErreichbar(), ]); return { datenbank, speicher, zeitpunkt: new Date(), zeitzone: "Europe/Berlin", }; }), }); const sitzungRouter = router({ /** Wer bin ich, welcher Wehr gehöre ich an, was darf ich? */ ich: angemeldet.query(async ({ ctx }) => { const person = await ctx.db((db) => db.person.findUnique({ where: { id: ctx.sitzung.personId }, select: { id: true, vorname: true, nachname: true, email: true, dienstgrad: true, mitgliedsnummer: true, letzteAnmeldung: true, organisation: { select: { id: true, name: true, kurzname: true, slug: true } }, }, }), ); return { person, rollen: ctx.sitzung.rollen, rollenBezeichnung: ctx.sitzung.rollen.map((rolle) => ROLLEN_BEZEICHNUNG[rolle]), rechte: [...rechteVon(ctx.sitzung.rollen)], anmeldeArt: ctx.sitzung.anmeldeArt, }; }), }); const organisationRouter = router({ /** Stammdaten der eigenen Wehr. */ eigene: angemeldet.query(async ({ ctx }) => ctx.db((db) => db.organisation.findUnique({ where: { id: ctx.sitzung.organisationId }, }), ), ), /** Kennzahlen für die Startseite. */ kennzahlen: angemeldet.query(async ({ ctx }) => ctx.db(async (db) => { const in90Tagen = new Date(Date.now() + 90 * 864e5); const [mitglieder, fahrzeuge, geraete, pruefungFaellig, gesperrt, qualiAblauf] = await Promise.all([ db.person.count({ where: { aktiv: true, geloeschtAm: null } }), db.fahrzeug.count({ where: { aktiv: true, geloeschtAm: null } }), db.geraet.count({ where: { geloeschtAm: null } }), db.geraet.count({ where: { status: "PRUEFUNG_FAELLIG", geloeschtAm: null } }), db.geraet.count({ where: { status: "GESPERRT", geloeschtAm: null } }), db.qualifikation.count({ where: { geloeschtAm: null, gueltigBis: { not: null, lte: in90Tagen }, person: { aktiv: true, geloeschtAm: null }, }, }), ]); return { mitglieder, fahrzeuge, geraete, pruefungFaellig, gesperrt, qualiAblauf }; }), ), }); const auditRouter = router({ /** Die letzten Einträge im Protokoll — nur mit dem entsprechenden Recht. */ letzte: mitRecht("system.auditlog") .input( z .object({ anzahl: z.number().int().min(1).max(200).default(50), entitaet: z.string().max(80).optional(), }) .optional(), ) .query(async ({ ctx, input }) => ctx.db((db) => db.auditLog.findMany({ where: input?.entitaet ? { entitaet: input.entitaet } : {}, orderBy: { zeitpunkt: "desc" }, take: input?.anzahl ?? 50, include: { akteur: { select: { id: true, vorname: true, nachname: true } }, }, }), ), ), }); export const appRouter = router({ system: systemRouter, sitzung: sitzungRouter, organisation: organisationRouter, audit: auditRouter, // Phase 1 — Stammdaten standort: standortRouter, fahrzeug: fahrzeugRouter, geraeteraum: geraeteraumRouter, geraetetyp: geraetetypRouter, geraet: geraetRouter, person: personRouter, qualifikationstyp: qualifikationstypRouter, qualifikation: qualifikationRouter, datenImport: importRouter, }); export type AppRouter = typeof appRouter;