From f9e045ebed3f2d21c871875fdb0596bcf9cece13 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 8 Jun 2026 14:11:50 +0200 Subject: [PATCH] Add FlorianNetz design specification Login-only mutual-aid platform for Austrian volunteer fire brigades to list vehicles/equipment, searchable by other brigades and sorted by fastest-arriving (drive-time ETA). Next.js + PostgreSQL/Drizzle + Auth.js (Authentik OIDC + local argon2id), dynamic admin-curated Merkmal system, self-hosted OSRM/Nominatim, Docker Compose behind external Traefik. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 5 + .../specs/2026-06-08-floriannetz-design.md | 140 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 .gitignore create mode 100644 docs/superpowers/specs/2026-06-08-floriannetz-design.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b857d59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.superpowers/ +.DS_Store +node_modules/ +.env +.env.local diff --git a/docs/superpowers/specs/2026-06-08-floriannetz-design.md b/docs/superpowers/specs/2026-06-08-floriannetz-design.md new file mode 100644 index 0000000..f2f27aa --- /dev/null +++ b/docs/superpowers/specs/2026-06-08-floriannetz-design.md @@ -0,0 +1,140 @@ +# FlorianNetz — Design-Spezifikation + +**Datum:** 2026-06-08 +**Status:** Entwurf (zur Freigabe) +**Sprache der Anwendung:** Deutsch (österreichischer Feuerwehr-Standard, ÖBFV) + +--- + +## 1. Zweck + +FlorianNetz ist eine **ausschließlich für authentifizierte Benutzer zugängliche** Web-Plattform, auf der österreichische Freiwillige Feuerwehren ihre **Fahrzeuge** und **Geräte/Ausrüstung** erfassen. Andere Wehren können diese durchsuchen, um für die überörtliche Hilfe die **am schnellsten eintreffende** Ressource zu finden. + +**Oberstes Prinzip:** Kein anonymer Zugriff — *kein einziger* Seiten- oder API-Endpunkt ist ohne gültige Sitzung erreichbar (default-deny, serverseitig erzwungen). + +### Nicht-Ziele (vorerst) +- Kein In-App-Ausleih-/Anfrage-Workflow (nur Kontaktaufnahme out-of-band; für später vorgesehen, Datenmodell offen halten). +- Keine 2-Faktor-Authentifizierung in v1 (später leicht ergänzbar). +- Keine Kopplung mit `feuerwehr_dashboard` in v1 (Architektur hält die spätere Kopplung offen). +- Kein Mehrmandanten-SaaS — eine selbst gehostete Instanz mit vielen Wehren. + +--- + +## 2. Rollen & Zugriff + +| Rolle | Login über | Berechtigungen | +|---|---|---| +| **Platform-Admin** | Authentik SSO (OIDC) | Verwaltet die Taxonomie (Merkmal-Katalog, Fahrzeug-Vorlagen inkl. zugeordneter Merkmale & Auswahloptionen, Geräte-Kategorien); gibt von Wehren vorgeschlagene Merkmale frei; legt Wehren an und stellt den ersten Wehr-Admin-Login bereit; sieht Audit-Log | +| **Wehr-Admin** | App-Konto (erster Login vom Platform-Admin bereitgestellt) | Verwaltet das eigene Wehr-Profil, eigene Fahrzeuge & Geräte (Werte, Zuordnung, Status); **legt weitere Wehr-Admins und Lese-Benutzer der eigenen Wehr selbst an** | +| **Lese-Benutzer** | App-Konto (vom Wehr-Admin angelegt) | Durchsucht und sieht alle Wehren-Daten inkl. Kontaktinfo; **keine** Bearbeitung | + +- Zwei Authentifizierungswege, **eine** einheitliche Sitzung mit `role` + `brigadeId`. +- Autorisierung serverseitig: Schreibzugriffe sind auf die eigene Wehr beschränkt; Lesezugriff über alle Wehren (offenes Verzeichnis). +- **Login-Seite zeigt ausschließlich den Login** — keine weiteren Inhalte. + +--- + +## 3. Informationsarchitektur + +- **Startseite (nach Login):** eine globale Suche mit prominentem „Meinen Standort verwenden" (GPS). Tabs: **Fahrzeuge · Geräte · Wehren**. +- **Pro Tab:** Namens-/Funkrufnamen-Suche + **dynamisch aus dem Merkmal-Katalog erzeugte Filter** (typabhängig: Schieberegler / Dropdown / Ja-Nein-Schalter) + Status-Filter („nur einsatzbereit"). +- **Trefferliste:** sortiert nach **Eintreffzeit (Fahrzeit-ETA)**; jede Zeile zeigt Wehr, Eckdaten, ETA/Entfernung, Status und **📞 Kontakt** (Telefonnummer der Wehr). +- **Detailseiten:** + - *Fahrzeug:* Eckdaten-Raster (aus Merkmalwerten) · **Beladung** als verlinkte Geräteliste · Wehr-Karte · expliziter Button **„Wehr kontaktieren"**. + - *Gerät:* Merkmale · zugeordnetes Fahrzeug **oder** „im Gerätehaus" · Wehr-Karte · Kontakt. + - *Wehr:* Profil (Adresse, Kontakt, Funkrufnamen-Schema), eigener Fuhrpark, Kontakt. + +### Design / Corporate Identity +- Richtung **„Amtlich"**: seriös, behördlich, ruhig. Navy-Primärfarbe, Signalrot als Akzent/Signal, Serifen-Überschriften, viel Weißraum, tabellarische Ziffern. +- Logo **„Netzknoten"**: vernetzte Wehren-Knoten um einen roten Mittelpunkt (Florian + Netz). +- Palette: Navy `#1B3A5B`, Signalrot `#E2231A`, Anthrazit `#1A2530`, Nebelgrau `#F6F8FA`, Grün/bereit `#1F8F5A`, Bernstein/Wartung `#B5460F`. +- Typografie: Serif-Display (Source Serif / Georgia) für Überschriften, humanistische Sans (Inter / System-UI) für UI, Tabular-Nums für Datenspalten. +- Bewusst **kein** generischer „KI-Look" (keine Verlaufsflächen, nicht alles abgerundet, keine Emoji-Dekoration). + +--- + +## 4. Dynamisches Merkmal-System (Kernstück) + +Ein starres Spaltenschema skaliert nicht — die Vielfalt an Fahrzeugen/Geräten ist zu groß. Stattdessen typisierte, vom Admin gepflegte Merkmale. + +- **Merkmal-Katalog** (Admin): jedes Merkmal hat Name, Typ (`Zahl` / `Auswahl` / `Ja-Nein` / `Text`), optional Einheit; bei `Auswahl` definiert der Admin die erlaubten Optionen. +- **Fahrzeug-Vorlagen** (Admin): definieren die österreichischen Standard-Bezeichnungen (z. B. KLFA, LFA, LFB-A, TLFA 2000/4000, RLFA, SRF, DLK 23/12, WLFA, MTF, KDOF) und **pro Vorlage, welche Merkmale gelten** sowie deren Optionen/Vorgabewerte. Inklusive Standard-Beladung. +- **Geräte-Kategorien** (Admin): je Kategorie ein eigener Merkmal-Satz. +- **Anlegen durch die Wehr:** Vorlage wählen (füllt Merkmale & Beladung vor, alles editierbar) **oder** „Eigenes Fahrzeug/Gerät" frei aus dem Katalog zusammenstellen. +- **Hybrid-Governance:** Ein von einer Wehr neu angelegtes Merkmal ist **sofort** am eigenen Objekt nutzbar und auf dessen Detailseite sichtbar; es wird **erst nach Admin-Freigabe** (prüfen / zusammenführen) zu einem **globalen Suchfilter**. Hält die Suche über alle Wehren konsistent. +- **Suche:** Filter werden dynamisch aus dem Katalog erzeugt; der Typ bestimmt das Eingabeelement und die Abfrage (Range / Gleichheit / Boolean). + +--- + +## 5. Geografie & „am schnellsten eintreffend" + +- Jede Wehr hat Koordinaten (aus der Adresse geokodiert). +- Suchstandort = Geräte-GPS **oder** eingegebene Adresse (geokodiert). +- Treffer werden nach **Fahrzeit-ETA** von der jeweils besitzenden Wehr zum Suchstandort sortiert. +- **Selbst gehostet** zur Wahrung der Privatsphäre (keine Standortdaten an Dritte): + - **OSRM** für Fahrzeit/Routing (Österreich-OSM-Extrakt), als Distanz-/Zeit-Matrix. + - **Nominatim** (oder Photon) für Geokodierung (Österreich-Extrakt). + - **Fallback:** Luftlinie (Haversine), falls der Routing-Dienst nicht erreichbar ist — klar gekennzeichnet. +- Optionale **MapLibre-GL**-Kartenansicht (Liste bleibt primär). + +--- + +## 6. Datenmodell (PostgreSQL, indikativ) + +> Typisierte Merkmalwerte in einer eigenen Wert-Tabelle (EAV mit typisierten Spalten) für saubere Range-/Enum-/Boolean-Indizes. + +- **brigades**: `id, name, art (FF), strasse, plz, ort, bundesland, lat, lng, funkrufname_schema, telefon, email, wehrführer, aktiv, erstellt_am` +- **users**: `id, brigade_id (NULL für Platform-Admin), rolle (platform_admin | wehr_admin | wehr_read), auth_typ (authentik | local), email, name, passwort_hash (NULL bei authentik), aktiv, erstellt_von, erstellt_am` +- **merkmale** (Attributdefinitionen): `id, name, typ (number|enum|boolean|text), einheit, geltungsbereich (vehicle|equipment|both), status (active|proposed), vorgeschlagen_von_brigade_id, erstellt_am` +- **merkmal_optionen** (für `enum`): `id, merkmal_id, wert, label, reihenfolge` +- **vehicle_templates** (Vorlagen): `id, code (z. B. "TLFA 4000"), name, beschreibung` +- **vehicle_template_merkmale**: `template_id, merkmal_id, vorgabewert (NULL), pflicht (bool), reihenfolge` +- **equipment_categories**: `id, name` +- **equipment_category_merkmale**: `category_id, merkmal_id, reihenfolge` +- **vehicles**: `id, brigade_id, template_id (NULL bei Eigenbau), name, funkrufname, status (einsatzbereit|wartung|ausser_dienst), notiz, erstellt_am` +- **equipment**: `id, brigade_id, category_id, vehicle_id (NULL = im Gerätehaus), name, status, erstellt_am` +- **merkmal_values**: `id, merkmal_id, entity_typ (vehicle|equipment), entity_id, value_num, value_text, value_bool` — Indizes: `(merkmal_id, value_num)`, `(merkmal_id, value_bool)`, `(merkmal_id, value_text)`; sowie `(entity_typ, entity_id)` +- **audit_log**: `id, actor_user_id, aktion, ziel_typ, ziel_id, details (jsonb), zeitpunkt` + +--- + +## 7. Tech-Stack + +- **Next.js (App Router, TypeScript)** — Full-Stack (Server Components + Route Handlers / Server Actions), wenig Boilerplate. +- **PostgreSQL** + **Drizzle ORM** — typsicher und SQL-nah; besser geeignet als Prisma für die dynamischen Merkmal-Abfragen (Range/Enum-Filter). +- **Auth.js (NextAuth v5)** — Authentik-OIDC-Provider (Platform-Admins) + Credentials-Provider mit **argon2id** (Wehr-Konten); **default-deny Middleware** auf allen Routen; einheitliche Session mit `role` + `brigadeId`. +- **Tailwind CSS + Radix-UI-Primitives**, eigens auf die „Amtlich"/Netzknoten-Identität gethemt (volle Kontrolle über den eigenständigen Look; bewusst nicht MUI). +- **Zod** (Validierung), **MapLibre GL** (Karte), **OSRM + Nominatim** (Docker, Österreich-Extrakt). +- **Tests:** **Playwright** (E2E, insbesondere Auth-Gating-Tests) + **Vitest** (Units). + +--- + +## 8. Sicherheit + +- **Default-deny** serverseitig auf allen Seiten **und** API-Routen; keine reine Client-Absicherung. +- Rollen- + Wehr-Scoping bei **jedem** Schreibzugriff serverseitig erzwungen. +- **argon2id**-Passwort-Hashing; sichere Cookies (`httpOnly`, `secure`, `sameSite`); CSRF-Schutz; Rate-Limiting am Login; Security-Header. +- **Audit-Log** für Admin- und Bereitstellungsaktionen (Wehr/Benutzer anlegen, Merkmal-Freigaben). +- 2FA für Wehr-Konten als spätere, einfache Erweiterung vorgesehen. + +--- + +## 9. Deployment + +- **Docker Compose**: Container für `app` (Next.js), `postgres`, `osrm`, `nominatim`. +- **Reverse Proxy:** vorhandener **Traefik** (extern). Kein gebündelter Proxy. Der App-Container wird per **Traefik-Labels** (Router/Service-Port, TLS, ggf. Middlewares) am externen Traefik-Netzwerk angebunden. +- Die App muss `X-Forwarded-Proto`/`X-Forwarded-Host` von Traefik vertrauen, damit sichere Cookies und Auth.js-Callback-URLs korrekt funktionieren; vertrauenswürdige Hosts konfigurieren. +- **Authentik:** Redirect-URI der App in Authentik registrieren; Client-ID/-Secret + Issuer-URL via Umgebungsvariablen. + +--- + +## 10. Offene Eingaben / Abhängigkeiten + +- **Österreichische Standarddokumente** (Standard-Fahrzeugbezeichnungen + Beladelisten) zur Befüllung des Merkmal-Katalogs und der Vorlagen — abzulegen unter `florian-netz/docs/reference/`. Blockiert das Design nicht, aber die Seed-Daten. +- **Bundesland** (für Funkrufnamen-Schema und ggf. regionale Bezeichnungen) — noch zu bestätigen. + +--- + +## 11. Zukünftige Kopplung mit `feuerwehr_dashboard` + +In v1 eigenständig, aber kopplungsfreundlich: gemeinsamer **Authentik**-Identitätsanbieter, kompatible **PostgreSQL**- und **Docker/Traefik**-Konventionen. Eine spätere Integration (geteilte Identitäten, Querverweise) bleibt dadurch unkompliziert.