247 lines
13 KiB
Markdown
247 lines
13 KiB
Markdown
# Lageplan - Feuerwehr Krokier-App
|
|
|
|
Digitale Lageplan-Applikation für Schweizer Feuerwehren zur Einsatzdokumentation und taktischen Lagedarstellung. Multi-Tenant SaaS mit Karte, Journal, Symbolbibliothek und Druckexport.
|
|
|
|
## Architektur-Übersicht
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Browser (Client) │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
|
|
│ │ MapView │ │ Journal │ │ Admin │ │Landing │ │
|
|
│ │(MapLibre)│ │ View │ │ Panel │ │ Page │ │
|
|
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───┬────┘ │
|
|
│ └──────────────┴─────────────┴─────────────┘ │
|
|
│ │ fetch() │
|
|
├─────────────────────────┼───────────────────────────────┤
|
|
│ Next.js 14 Server │
|
|
│ ┌──────────────────────┼──────────────────────────┐ │
|
|
│ │ API Routes (/api/*) │ │
|
|
│ │ ┌────────┐ ┌──────────┐ ┌──────────────────┐ │ │
|
|
│ │ │ Auth │ │ Projects │ │ Admin (Users, │ │ │
|
|
│ │ │ (JWT) │ │ Features │ │ Tenants, Icons) │ │ │
|
|
│ │ └───┬────┘ │ Journal │ └────────┬─────────┘ │ │
|
|
│ │ │ └────┬─────┘ │ │ │
|
|
│ │ └────────────┼─────────────────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ ┌────────────────┼────────────────────────┐ │ │
|
|
│ │ │ Prisma ORM + Tenant Guard │ │ │
|
|
│ │ └────────────────┼────────────────────────┘ │ │
|
|
│ └───────────────────┼─────────────────────────────┘ │
|
|
├───────────────────────┼─────────────────────────────────┤
|
|
│ ┌────────────┐ ┌────┴───────┐ ┌──────────────────┐ │
|
|
│ │ PostgreSQL │ │ MinIO │ │ SMTP (optional) │ │
|
|
│ │ (Prisma) │ │ (S3 Icons) │ │ (Nodemailer) │ │
|
|
│ └────────────┘ └────────────┘ └──────────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Tech Stack
|
|
|
|
| Schicht | Technologie | Zweck |
|
|
|---------|------------|-------|
|
|
| **Frontend** | Next.js 14, React 18, TypeScript | SSR + SPA |
|
|
| **UI** | TailwindCSS, shadcn/ui, Lucide Icons | Styling + Komponenten |
|
|
| **Karte** | MapLibre GL JS | OSM + Satellit Tiles |
|
|
| **Drag & Drop** | react-dnd + TouchBackend | Symbol-Platzierung (Desktop + Mobile) |
|
|
| **Datenbank** | PostgreSQL 16 + Prisma ORM | Relationale Daten |
|
|
| **Object Storage** | MinIO (S3-kompatibel) | Icon-Upload |
|
|
| **Auth** | JWT (jose) + httpOnly Cookies | Session-Management |
|
|
| **Validierung** | Zod | Input-Validierung auf API-Ebene |
|
|
| **E-Mail** | Nodemailer | Passwort-Reset, Kontaktformular |
|
|
| **PDF** | jsPDF + html2canvas | Kartenexport |
|
|
| **Container** | Docker Compose | Deployment |
|
|
|
|
## Features
|
|
|
|
### Karte
|
|
- **OSM + Satellitenansicht** — Layer-Toggle oben rechts
|
|
- **Zeichenwerkzeuge** — Punkt, Linie, Polygon, Rechteck, Kreis, Pfeil, Freihand, Text, Gefahrenzone
|
|
- **Symbolbibliothek** — 22+ FKS/BABS-konforme Feuerwehr-Symbole in 9 Kategorien
|
|
- **Drag & Drop + Tap-to-Place** — Symbole auf Karte platzieren (Desktop + Touch)
|
|
- **Symbol-Bearbeitung** — Rechtsklick: Skalierung (0.3x-4x), Rotation (0-360°)
|
|
- **Messwerkzeug** — Distanzmessung mit Höhenprofil (Open-Meteo API), Druckverlust-Berechnung für Feuerwehrschläuche
|
|
- **GPS-Standort** — Automatische Geolocation bei App-Start
|
|
- **Kartenposition persistent** — Bleibt beim Tab-Wechsel und Projekt-Erstellung erhalten
|
|
- **Offline-Tiles** — Service Worker cached OSM-Tiles
|
|
|
|
### Einsatz-Journal
|
|
- **Zeitprotokoll** — Einträge mit Zeitstempel, Was, Wer, Erledigt-Status
|
|
- **SOMA-Checkliste** — Vordefinierte Prüfpunkte (Ja/Ok Spalten), aus Templates initialisiert
|
|
- **Pendenzen** — Offene Aufgaben mit Was/Wer/Wann
|
|
- **Druckansicht** — Journal als formatiertes Dokument drucken
|
|
|
|
### Einsatz-Verwaltung
|
|
- **Erstellen/Laden/Löschen** — Über Menü → "Einsätze verwalten"
|
|
- **Auto-Save** — Alle 30 Sekunden automatisch in DB + localStorage
|
|
- **Export** — PNG, PDF (mit Metadaten-Header), GeoJSON
|
|
- **Sperren** — Projekte können gesperrt werden (nur SERVER_ADMIN kann entsperren)
|
|
|
|
### Admin-Bereich (`/admin`)
|
|
- **Benutzer** — CRUD, Rollen zuweisen, Passwort zurücksetzen
|
|
- **Tenants** — Organisationen verwalten, Mitglieder, Abo-Status
|
|
- **Symbole** — Upload (PNG/SVG/JPEG/WebP, max 5MB), Kategorien
|
|
- **Schlauchtypen** — Konfigurierbare Druckverlust-Parameter
|
|
- **System** — SMTP-Einstellungen, Kontakt-E-Mail, Test-E-Mail
|
|
|
|
### Sicherheit & Multi-Tenancy
|
|
- **4 Rollen** — SERVER_ADMIN, TENANT_ADMIN, OPERATOR, VIEWER
|
|
- **Tenant-Isolation** — Jeder Tenant sieht nur eigene Projekte/Benutzer
|
|
- **JWT httpOnly Cookies** — Kein Token im localStorage
|
|
- **Zod-Validierung** — Alle API-Inputs validiert
|
|
- **IDOR-Schutz** — Sub-Ressourcen (Journal, CheckItems) werden gegen Projekt-Zugehörigkeit geprüft
|
|
- **Passwort-Hashing** — bcrypt mit 12 Rounds
|
|
- **Reset-Token** — Kryptographisch sicher (32 Bytes), 1h Ablauf, nie in API-Response exponiert
|
|
|
|
## Projekt-Struktur
|
|
|
|
```
|
|
lageplan/
|
|
├── docker-compose.yml # PostgreSQL, MinIO, Web-App
|
|
├── Dockerfile # Multi-Stage Build (deps → builder → runner)
|
|
├── docker-entrypoint.sh # DB-Migration + Seed beim Container-Start
|
|
├── prisma/
|
|
│ ├── schema.prisma # 15 Models, 4 Enums
|
|
│ └── seed.js # Demo-Daten (Tenants, Users, Symbole, Projekt)
|
|
├── public/
|
|
│ └── sw.js # Service Worker für Offline-Tile-Caching
|
|
├── src/
|
|
│ ├── app/
|
|
│ │ ├── page.tsx # Landing Page (/)
|
|
│ │ ├── login/page.tsx # Login
|
|
│ │ ├── register/page.tsx # Registrierung (erstellt Tenant + User)
|
|
│ │ ├── reset-password/ # Passwort-Reset
|
|
│ │ ├── admin/page.tsx # Admin-Panel
|
|
│ │ ├── app/page.tsx # Haupt-App (Karte + Journal)
|
|
│ │ └── api/
|
|
│ │ ├── auth/ # login, logout, me, register, forgot/reset-password
|
|
│ │ ├── projects/ # CRUD + features, journal, export
|
|
│ │ ├── admin/ # users, tenants, icons, categories, settings
|
|
│ │ ├── icons/ # Public icon listing + image serving
|
|
│ │ ├── hose-types/ # Schlauchtyp-Konfiguration
|
|
│ │ ├── contact/ # Kontaktformular
|
|
│ │ └── tenants/ # Public tenant info by slug
|
|
│ ├── components/
|
|
│ │ ├── map/
|
|
│ │ │ └── map-view.tsx # MapLibre GL Karte (~1650 Zeilen)
|
|
│ │ ├── journal/
|
|
│ │ │ └── journal-view.tsx # Einsatz-Journal
|
|
│ │ ├── layout/
|
|
│ │ │ ├── topbar.tsx # Header mit Menü, Speichern, Einsatz-Verwaltung
|
|
│ │ │ ├── left-toolbar.tsx # Zeichenwerkzeuge
|
|
│ │ │ └── right-sidebar.tsx # Symbole + Karte/Journal Tabs
|
|
│ │ ├── dialogs/ # ProjectDialog, TextDialog, LineLabelDialog, HoseSettings
|
|
│ │ ├── providers/
|
|
│ │ │ └── auth-provider.tsx # React Context für Auth-State
|
|
│ │ └── ui/ # shadcn/ui Basis-Komponenten
|
|
│ └── lib/
|
|
│ ├── auth.ts # JWT erstellen/verifizieren, Login, Rollen-Checks
|
|
│ ├── tenant.ts # Tenant-Filter, Projekt-Zugriffsprüfung
|
|
│ ├── db.ts # Prisma Client Singleton
|
|
│ ├── minio.ts # MinIO Upload/Download/Delete
|
|
│ ├── email.ts # SMTP aus DB laden, E-Mail senden
|
|
│ ├── validations.ts # Zod Schemas (Login, Project, Feature, Icon)
|
|
│ ├── fw-symbols.ts # 22 eingebaute SVG Feuerwehr-Symbole
|
|
│ └── utils.ts # Hilfsfunktionen (cn, formatDateTime)
|
|
```
|
|
|
|
## Datenbank-Schema (Prisma)
|
|
|
|
### Kern-Models
|
|
- **Tenant** — Organisation (Feuerwehr), mit Abo-Plan, Limits, Logo
|
|
- **User** — E-Mail/Passwort, Rolle, Reset-Token
|
|
- **TenantMembership** — User ↔ Tenant Zuordnung (M:N)
|
|
- **Project** — Einsatz mit Titel, Ort, mapCenter/mapZoom, isLocked
|
|
- **Feature** — GeoJSON-Zeichnung (Geometrie + Properties als JSON)
|
|
|
|
### Journal-Models
|
|
- **JournalEntry** — Zeitprotokoll-Eintrag (time, what, who, done)
|
|
- **JournalCheckItem** — SOMA-Checkliste (label, confirmed, ok)
|
|
- **JournalPendenz** — Offene Aufgabe (what, who, whenHow, done)
|
|
- **JournalCheckTemplate** — Vorlagen für Checklisten-Punkte
|
|
|
|
### Konfigurations-Models
|
|
- **IconCategory** — Symbolkategorie (Feuer, Wasser, Gefahrstoffe, ...)
|
|
- **IconAsset** — Hochgeladenes Symbol (fileKey → MinIO)
|
|
- **HoseType** — Schlauchtyp mit Druckverlust-Parametern
|
|
- **SystemSetting** — Key-Value Store (SMTP, Kontakt-E-Mail)
|
|
|
|
### Rollen & Berechtigungen
|
|
|
|
| Aktion | SERVER_ADMIN | TENANT_ADMIN | OPERATOR | VIEWER |
|
|
|--------|:---:|:---:|:---:|:---:|
|
|
| Alle Projekte sehen | ✓ | - | - | - |
|
|
| Tenant-Projekte sehen | ✓ | ✓ | ✓ | ✓ |
|
|
| Zeichnen/Bearbeiten | ✓ | ✓ | ✓ | - |
|
|
| Projekt erstellen | ✓ | ✓ | ✓ | - |
|
|
| Projekt löschen | ✓ | ✓ | Eigene | - |
|
|
| Benutzer verwalten | Alle | Eigener Tenant | - | - |
|
|
| Tenants verwalten | ✓ | - | - | - |
|
|
| System-Einstellungen | ✓ | - | - | - |
|
|
|
|
## Schnellstart
|
|
|
|
### Voraussetzungen
|
|
- Docker & Docker Compose
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
docker compose up -d
|
|
```
|
|
|
|
App: **http://localhost:3000**
|
|
|
|
### Standard-Logins (nach Seed)
|
|
|
|
| Benutzer | E-Mail | Passwort | Rolle |
|
|
|----------|--------|----------|-------|
|
|
| Admin | admin@lageplan.local | admin123 | SERVER_ADMIN |
|
|
| Editor | editor@demo.local | editor123 | OPERATOR |
|
|
| Viewer | viewer@demo.local | viewer123 | VIEWER |
|
|
|
|
## Umgebungsvariablen
|
|
|
|
| Variable | Beschreibung | Default |
|
|
|----------|-------------|---------|
|
|
| `DATABASE_URL` | PostgreSQL Connection String | siehe .env.example |
|
|
| `NEXTAUTH_SECRET` | JWT Secret (**min. 32 Zeichen!**) | - |
|
|
| `MINIO_ENDPOINT` | MinIO Host | localhost |
|
|
| `MINIO_PORT` | MinIO API Port | 9000 |
|
|
| `MINIO_ACCESS_KEY` | MinIO Zugangsdaten | minioadmin |
|
|
| `MINIO_SECRET_KEY` | MinIO Passwort | minioadmin123 |
|
|
| `MINIO_BUCKET` | Bucket Name | lageplan-icons |
|
|
| `MINIO_PUBLIC_URL` | Öffentliche MinIO URL | http://localhost:9002 |
|
|
|
|
## Docker Services
|
|
|
|
| Service | Port | Beschreibung |
|
|
|---------|------|-------------|
|
|
| `web` | 3000 | Next.js App |
|
|
| `db` | 5432 | PostgreSQL 16 |
|
|
| `minio` | 9002 (API), 9003 (Console) | MinIO Object Storage |
|
|
|
|
## Produktion
|
|
|
|
```bash
|
|
# 1. Sichere Secrets setzen
|
|
NEXTAUTH_SECRET=$(openssl rand -base64 32)
|
|
POSTGRES_PASSWORD=$(openssl rand -base64 16)
|
|
|
|
# 2. .env anpassen
|
|
# 3. Starten
|
|
docker compose up -d
|
|
|
|
# 4. Optional: Reverse Proxy (nginx/traefik) für HTTPS
|
|
```
|
|
|
|
## Security-Hinweise
|
|
|
|
- `NEXTAUTH_SECRET` **muss** in Produktion gesetzt werden (min. 32 Zeichen)
|
|
- SMTP-Passwörter werden in der DB als `isSecret: true` markiert
|
|
- Reset-Tokens werden **nie** in API-Responses exponiert (nur in Server-Logs wenn kein SMTP)
|
|
- Alle Sub-Ressourcen-Zugriffe (Journal-Einträge, CheckItems, Pendenzen) prüfen Projekt-Zugehörigkeit
|
|
- Cookie: `httpOnly`, `secure` (in Produktion), `sameSite: lax`
|
|
- Datei-Uploads: Nur PNG/SVG/JPEG/WebP, max 5MB, UUID-Dateinamen
|