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) <noreply@anthropic.com>
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.superpowers/
|
||||
.DS_Store
|
||||
node_modules/
|
||||
.env
|
||||
.env.local
|
||||
140
docs/superpowers/specs/2026-06-08-floriannetz-design.md
Normal file
140
docs/superpowers/specs/2026-06-08-floriannetz-design.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user