Struktur & Stack¶
Verzeichnisstruktur¶
app/
(public)/ Öffentliche Statusseite, Historie, Service-Detail, Badge,
Rechtstexte, Abonnement-Bestätigung, Abmeldung, My data
admin/
login/ Anmeldeseite (ohne Guard)
(dashboard)/ Geschützte Admin-Seiten mit Navigations-Layout
sla-reports/ SLA-Reports; Route Handler [id]/pdf, [id]/xlsx und adhoc
api/
status/ Öffentliche Status-API (JSON)
health/ Infrastruktur-Health-Check
outage/ Statusänderungs-Routen für API-Tokens
incidents/ Incident per API-Token anlegen
maintenances/ Wartungsfenster per API-Token anlegen
api-docs/ Swagger UI für das OpenAPI-Dokument
badge.svg/ Dynamisches SVG-Statusbadge
feed.xml/ RSS-Feed der Incidents
components/ UI der Statusseite und Admin-Bausteine
components/ui/ shadcn/ui-Komponenten (Base UI)
instrumentation.ts Startprüfung beim Serverstart (SESSION_SECRET)
lib/
actions/ Server Actions (liefern ActionResult<T>)
api/ JSON-Antwort-Helfer für API-Routen
audit/ Schreiben und Kapseln des Audit-Logs
auth/ Passwörter, Sessions, Rollen-Guards, API-Token-Prüfung, Rate-Limiting
crypto/ AES-256-GCM für Geheimnisse
db/
schema.ts Drizzle-Schema und Enum-Konstanten (Quelle der Domänentypen)
queries/ Datenbankzugriffe
seed.ts Demodaten
domain/ Fachlogik (Status, Uptime, SLA, Zeitzonen, Rollen)
email/ E-Mail-Templates, Transporte, Processor
env/ Prüfung von Umgebungsvariablen beim Start
export/ SLA-Export: Laden des Kontexts, Excel-Mappe, Antwort, Texte en/de
notifications/ Dispatch, Queues, Scheduler, Uptime-Checker
pdf/ Erzeugung der SLA-Report-PDFs
push/ sms/ webhooks/ Kanalspezifische Transporte, Payload-Builder, Processor
validations/ Zod-Schemas
viewer-timezone.ts Zeitzonen-Umschalter der Besucher (Logik und Store)
i18n/ next-intl-Konfiguration
messages/ en.json und de.json
scripts/ Worker, Migrationen, Backup/Restore, Seeding, Hilfsskripte
drizzle/ SQL-Migrationen und drizzle-kit-Metadaten
tests/ Unit-Tests, Integrationstests, Factories
e2e/ Playwright-Specs
public/ Statische Dateien, openapi.json, Service Worker für Push
Schichtenregeln¶
Der Code folgt einer festen Abhängigkeitsrichtung:
- Seiten und Routen (Server Components, Route Handler) lesen Daten und rendern. Sie rufen Queries oder Domänenfunktionen auf.
- Server Actions (
lib/actions/) verarbeiten Formulare. Sie prüfen die Rolle, validieren die Eingabe mit Zod, führen die Query in einer Transaktion aus und rufenrevalidatePathauf. - Queries (
lib/db/queries/) bündeln die Datenbankzugriffe. - Domäne (
lib/domain/) enthält überwiegend reine Funktionen ohne I/O, zum Beispiel Uptime-Berechnung, SLA-Fenster, Schweregrade und Zeitzonenumrechnung. Sie lassen sich ohne Datenbank testen.
Wenige Stellen greifen bewusst direkt auf die Datenbank zu: applyComponentStatusChange (lib/domain/component-status.ts) öffnet die Transaktion für Statusänderungen, der E-Mail-Processor (lib/email/processor.ts) schreibt den Versandstatus, und /api/health setzt ein SELECT 1 ab.
Eine typische Formularübermittlung sieht so aus:
Formular → Server Action → Rollenprüfung (getCurrentUser + ROLES_CAN_*)
→ Zod-Validierung → Query in Transaktion → revalidatePath
Datenfluss der öffentlichen Seite¶
Seite (Server Component) → getStatusPageData() → Queries → PostgreSQL
getStatusPageData() bündelt alle Queries der öffentlichen Seite. Die Seite selbst bleibt eine dünne Renderschicht.
Konventionen¶
- Eingaben aus Formularen und APIs gelten als nicht vertrauenswürdig und werden immer mit Zod geprüft.
- Logging läuft über den eingebauten Logger
lib/log.ts: strukturiertes JSON in Produktion, lesbare Ausgabe in der Entwicklung.console.*wird nur in Error-Boundaries und CLI-Skripten verwendet. - Audit-Einträge schreibt
logAudit, das nie eine Exception wirft. Ein fehlgeschlagener Audit-Schreibvorgang darf die Fachaktion nicht scheitern lassen. - Zeiten werden als
timestamptzgespeichert. Die Anzeige folgtsettings.timezoneundsettings.display_timezone, Wandzeit-Eingaben werden sommerzeitsicher umgerechnet (lib/domain/timezone.ts). Im öffentlichen Portal können Besucher auf ihre Browser-Zone umschalten. Die Wahl liegt im Browser (localStoragetz-mode), der Server rendert immer in der App-Zone. - Datumsformatierung ohne explizite Locale und Zeitzone (argumentloses
toLocale*) ist verboten, weil sie Hydration-Fehler erzeugt. Ein Unit-Test prüft das fürapp/,components/undlib/.