morz-infoboard/docs/SERVER-KONZEPT.md
Jesko Anschütz 097cd58c0c docs: Dokumentation validiert und korrigiert
- SCHEMA.md: screen_id in user_screen_permissions von uuid auf text korrigiert
- SCHEMA.md: Phasen-Hinweis zur screens-Tabelle hinzugefügt (6 Spalten aktuell implementiert, erweiterte Phase 2-3)
- SERVER-KONZEPT.md: RequireScreenAccess-Middleware dokumentiert inkl. Route-Gruppen und Verhaltens-Details
- server/backend/README.md: Env-Variable DATABASE_URL → MORZ_INFOBOARD_DATABASE_URL korrigiert
- DEVELOPMENT.md: Compose-Stack von "später" auf "existiert bereits" aktualisiert
- API-ENDPOINTS.md: HandlePlayerPlaylist Response um fehlende Felder ergänzt (playlist_id, media_asset_id, order_index, created_at)
- DEVELOPMENT.md: Architekturentscheidungen präzisiert (message_wall=implementiert, Kampagnen=geplant)

Co-Authored-By: Klaus <noreply@example.com>
2026-03-24 00:10:50 +01:00

421 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Info-Board Neu - Server-Konzept
## Ziel
Der Server ist die zentrale Steuer- und Verwaltungsinstanz der Plattform.
Er soll:
- Benutzer, Firmen und Screens verwalten
- Medien und Playlists bereitstellen
- globale Templates und Kampagnen steuern
- Provisionierung neuer Screens ausloesen
- Status, Screenshots und Heartbeats sammeln
- MQTT und HTTPS sauber trennen
## Grundprinzip
Der Server ist die Quelle der fachlichen Wahrheit.
Der Player bleibt trotzdem lauffaehig, wenn der Server temporaer nicht erreichbar ist.
Das bedeutet:
- Server verwaltet Konfiguration und Inhalte zentral
- Player fuehrt lokal und robust aus
- Server sendet Steuerimpulse, Player synchronisiert aktiv nach
## Hauptkomponenten
### Reverse Proxy
Aufgaben:
- TLS-Terminierung
- Routing fuer API und Web-UIs
- optionale Auth-/Header-Regeln
### Backend-API
Bevorzugte Sprache:
- `Go`
Aufgaben:
- Authentifizierung und Autorisierung
- CRUD fuer Tenants, Users, Screens, Medien, Playlists
- Verwaltung globaler Templates und Kampagnen
- Player-Sync-Endpunkte
- Speicherung von Status und Screenshots
- Start von Provisionierungsjobs
### Admin-UI
Aufgaben:
- Gesamtübersicht aller Screens
- Vorschau und Status
- Template-/Kampagnenverwaltung
- Kommandos und Provisionierung
### Tenant-UI
Aufgaben:
- Uploads und Medienverwaltung pro Firma
- Pflege der monitorbezogenen Playlist
- Vorschau des eigenen Screens
### Datenbank
Empfehlung:
- PostgreSQL
Aufgaben:
- Speicherung fachlicher Daten
- Status, Jobs, Revisionen, Zuordnungen
### MQTT-Broker
Empfehlung:
- Mosquitto
Aufgaben:
- Heartbeats
- Statusmeldungen
- Events
- Kommandos und ACKs
### Dateispeicher
Aufgaben:
- Uploads
- Screenshots
- ggf. serverseitig vorbereitete Medien
V1:
- Dateisystem ausreichend
## Fachliche Bereiche
## 1. Mandanten und Benutzer
Der Server trennt:
- globale Admins
- tenantgebundene Nutzer
Regel:
- Firmen sehen nur ihren Bereich
- Admins sehen alles
## 2. Screen-Verwaltung
Der Server kennt jeden Screen mit:
- ID
- Name
- Klasse
- Orientierung
- Rotation
- Tenant-Zuordnung
- technischem Registrierungsstatus
## 3. Medienverwaltung
Der Server verwaltet:
- Uploads
- externe Medienreferenzen
- Metadaten
- tenant- oder screenspezifische Zuordnung
## 4. Playlist-Verwaltung
Der Server verwaltet tenantbezogene Inhalte pro Screen.
Wichtige Felder:
- Reihenfolge
- Dauer
- `valid_from`
- `valid_until`
- Fehlerstrategie
- Cache-Politik
## 5. Template- und Kampagnenverwaltung
Der Server stellt den globalen Orchestrierungsbereich bereit.
Funktionen:
- Templates erstellen
- Szenen pflegen
- Zielgruppen waehlen
- Kampagnen aktivieren/deaktivieren
- Zeitfenster setzen
- Uebersteuerung sichtbar machen
## 6. Provisionierung
Der Server startet Provisionierungsjobs fuer neue Screens.
Aufgaben:
- Eingaben aus Admin-UI entgegennehmen
- Job anlegen
- Secret-Handling absichern
- Worker oder Jobrunner starten
- Fortschritt speichern
- Fehler sauber rueckmelden
## API-Bereiche
Die API soll mindestens diese Domänen haben:
- `auth`
- `tenants`
- `users`
- `screens`
- `media`
- `playlists`
- `templates`
- `campaigns`
- `player`
- `commands`
- `provisioning`
## Revisionsmodell
Der Server arbeitet mit Revisionen, damit Player effizient synchronisieren koennen.
Mindestens:
- `config_revision`
- `playlist_revision`
- `media_revision`
- `campaign_revision`
## Status- und Vorschaukonzept
Der Server speichert:
- letzten bekannten Heartbeat
- letzten Status
- letzten Screenshot
- aktuelle Inhaltsquelle pro Screen
Die Admin-UI soll damit erkennen:
- online/offline
- normaler tenantbezogener Betrieb
- globale Kampagnen-Uebersteuerung
- Fallback-Betrieb
- Fehlerzustand
## Provisionierungsjobrunner
Die Provisionierung soll nicht direkt in Web-Requests laufen.
Stattdessen:
- API legt Job an
- dedizierter Worker/Jobrunnner arbeitet ihn ab
- Fortschritt wird in DB gespeichert
Zusaetzlich fuer v1 festzulegen:
- ACK-Timeout-Handling fuer `device_commands` ueber Worker
- Secret-Handling fuer Provisionierungs-Bootstrap ueber kurzlebige Secret-Referenzen
- physische Netzposition des Workers fuer SSH-Erreichbarkeit als Betriebsparameter
## Docker-Compose-Zielbild
Sinnvolle Komponenten in `compose/`:
- `reverse-proxy`
- `backend`
- `postgres`
- `mosquitto`
- optional `worker`
## Authentifizierung
Der Server verwendet einen Session-basierten Login-Flow mit `bcrypt`-Passwort-Hashing.
### Login-Flow
1. `GET /login` rendert das Login-Formular (Bulma-Card zentriert).
2. `POST /login` prueft Username und Passwort:
- `AuthStore.GetUserByUsername` laedt den User inkl. Tenant-Slug.
- `bcrypt.CompareHashAndPassword` prueft das Passwort (Cost-Faktor 12).
- Bei Erfolg legt `AuthStore.CreateSession` eine Session an (TTL 24 Stunden).
- Das Session-Token wird als `morz_session`-Cookie gesetzt (`HttpOnly=true`, `Secure=true`).
- Im `DevMode` (`MORZ_INFOBOARD_DEV_MODE=true`) wird `Secure=false` gesetzt fuer lokalen HTTP-Betrieb.
- Weiterleitung je nach Rolle: `admin``/admin`, `tenant``/tenant/{slug}/dashboard`.
3. `POST /logout` loescht die Session in der DB und entfernt den Cookie.
### Cookie-Lebensdauer
- Standard-TTL: 24 Stunden
- Der Cookie verfaellt automatisch; die DB wird stuendlich durch `CleanExpiredSessions` bereinigt.
### Admin-User-Bootstrap
Beim Server-Start wird `EnsureAdminUser` aufgerufen, wenn `MORZ_INFOBOARD_ADMIN_PASSWORD` gesetzt ist.
Der Admin-User wird dem Tenant mit Slug `MORZ_INFOBOARD_DEFAULT_TENANT` (Standard: `morz`) zugeordnet.
Ist der User bereits vorhanden, passiert nichts. Fehler sind nicht fatal — der Server startet trotzdem.
---
## Middleware-Kette
Alle geschuetzten Routen werden durch Middleware-Funktionen in `internal/httpapi/middleware.go` abgesichert.
```
Eingehende Anfrage
RequireAuth Liest morz_session-Cookie, prueft Session via DB,
speichert *store.User im Request-Context.
→ Fehler: Redirect zu /login?next=<Pfad>
├─► RequireAdmin Prueft user.Role == "admin"
│ → Fehler: 403 Forbidden
├─► RequireTenant Prueft user.TenantSlug == {tenantSlug} aus dem URL-Pfad.
│ Access Admins duerfen immer durch.
│ → Fehler: 403 Forbidden
└─► RequireScreen Enforces per-screen access control.
Access Admins duerfen auf alle Screens zugreifen.
Screen-User brauchen expliziten Eintrag in `user_screen_permissions`.
Tenant-User duerfen auf alle Screens ihres Tenants zugreifen.
→ Fehler: 404 Not Found (Screen) oder 403 Forbidden (kein Zugriff)
```
### Route-Gruppen im Router
| Gruppe | Middleware | Beispielrouten |
|----------------|----------------------------------------------------|---------------------------------------------|
| Oeffentlich | keine | `/healthz`, `/login`, `/api/v1/screens/register` |
| Auth-only | RequireAuth | `/api/v1/items/{itemId}`, `/api/v1/media/{id}` |
| Admin-only | RequireAuth + RequireAdmin | `/admin`, `/admin/screens/...` |
| Tenant-scoped | RequireAuth + RequireTenantAccess | `/tenant/{tenantSlug}/...`, `/api/v1/tenants/{tenantSlug}/...` |
| Screen-scoped | RequireAuth + RequireScreenAccess | `/manage/{screenSlug}/...`, `/api/v1/screens/{screenId}/playlist` |
Der Hilfsfunktion `chain(middlewares...)` in `router.go` wrappet Handler von aussen nach innen.
---
## RequireScreenAccess Middleware
Die Middleware `RequireScreenAccess` erzwingt Zugriffskontrolle auf Screen-Ressourcen und wird ausschliesslich fuer Routen verwendet, deren Handler screen-spezifische Operationen durchfuehren.
**Verhalten:**
- **Admin-User**: duerfen auf alle Screens zugreifen (Bypass).
- **Screen-User**: duerfen nur auf Screens zugreifen, fuer die sie einen expliziten Eintrag in `user_screen_permissions` haben.
- **Tenant-User**: duerfen auf alle Screens ihres Tenants zugreifen (noch nicht vollstaendig implementiert).
**Implementierung:**
Die Middleware extrahiert den `screenSlug` aus dem URL-Pfad-Parameter, schlaegt den Screen in der Datenbank auf und prueft via `ScreenStore.HasUserScreenAccess()`, ob der Nutzer Zugriff hat. Admins umgehen diese Pruefung.
**Fehlerbehandlung:**
- Screen nicht gefunden: `404 Not Found`
- Kein Zugriff auf Screen: `403 Forbidden`
**Verwendung in Router:**
Die `authScreen`-Middleware-Kombination wird fuer folgende Routes verwendet:
- `GET /manage/{screenSlug}` — Playlist-Management-UI
- `POST /manage/{screenSlug}/upload` — Medium hochladen
- `POST /manage/{screenSlug}/items` — Item hinzufuegen
- `POST /manage/{screenSlug}/items/{itemId}` — Item aktualisieren
- `POST /manage/{screenSlug}/items/{itemId}/delete` — Item loeschen
- `POST /manage/{screenSlug}/reorder` — Items reordnen
- `POST /manage/{screenSlug}/media/{mediaId}/delete` — Medium loeschen
---
## Tenant-Dashboard
Das Tenant-Self-Service-Dashboard ist unter `/tenant/{tenantSlug}/dashboard` erreichbar.
### URL-Schema
| Methode | Pfad | Beschreibung |
|---------|---------------------------------------------|---------------------------|
| GET | `/tenant/{tenantSlug}/dashboard` | Dashboard rendern |
| POST | `/tenant/{tenantSlug}/upload` | Medium hochladen |
| POST | `/tenant/{tenantSlug}/media/{mediaId}/delete` | Medium loeschen |
### Tabs
- **Tab A Meine Monitore:** Zeigt Screen-Karten mit Live-Status. Der Status wird per JavaScript
aus `GET /api/v1/screens/status` geladen und alle 30 Sekunden aktualisiert.
Status-Badge: `is-success` (online), `is-danger` (offline), `is-warning` (unbekannt).
- **Tab B Mediathek:** Upload-Formular (Bild, Video, PDF oder Web-URL) und Dateiliste
mit Loeschen-Button. Nach Upload oder Loeschen Redirect mit `?tab=media&flash=uploaded/deleted`.
### Eigentuemer-Pruefung beim Loeschen
`HandleTenantDeleteMedia` prueft, dass `asset.TenantID == tenant.ID`, bevor es loescht.
Damit ist sichergestellt, dass ein Tenant keine Assets anderer Tenants loeschen kann,
selbst wenn er die `mediaId` erraten wuerde.
---
## Sicherheitsgrundsaetze
- Root-Bootstrap-Geheimnisse nur kurzlebig oder referenziert speichern
- API- und MQTT-Zugaenge getrennt behandeln
- alle Admin-Kommandos auditieren
- Tenant-Trennung strikt serverseitig erzwingen
## API-Fehlermodell
Vor Implementierungsbeginn gilt ein einheitlicher Fehlerumschlag.
Empfehlung:
```json
{
"error": {
"code": "screen_not_found",
"message": "Screen existiert nicht",
"details": null
}
}
```
## Zielstruktur im Repo
Empfohlene Unterstruktur fuer den Server:
- `server/backend/`
- `server/admin-ui/`
- `server/tenant-ui/`
- `server/worker/`
- `compose/`
## Testfaelle fuer den Server
- Tenant sieht nur eigene Daten
- Admin sieht alle Daten
- Kampagne ueberschreibt tenantbezogenen Content korrekt
- Screen-Provisionierung legt Job sauber an
- Player-Sync ueber Revisionen funktioniert
- MQTT-Kommandos werden protokolliert
- Screenshot-Upload erscheint im Admin-Dashboard