# 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 ### MQTT-Topics (implementiert) | Topic | Publisher | Subscriber | Beschreibung | |----------------------------------------------|------------|---------------|---------------------------------------------------| | `signage/screen/{slug}/playlist-changed` | Backend | Player-Agent | Benachrichtigung bei Playlist-Aenderung; Backend debounced 2 s | | `signage/screen/{slug}/screenshot-request` | Backend | Player-Agent | Fordert sofortigen On-Demand-Screenshot an | Der Backend-`Notifier` (`internal/mqttnotifier/notifier.go`) veroeffentlicht beide Topics. Der Player-`Subscriber` (`player/agent/internal/mqttsubscriber/subscriber.go`) abonniert beide Topics fuer den eigenen Screen-Slug. Auf ein `screenshot-request`-Signal ruft der Agent `Screenshotter.TakeAndSendOnce(ctx)` auf und laedt das Bild direkt per `POST /api/v1/player/screenshot` hoch. ### 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 (In-Memory, nicht persistiert) - aktuelle Inhaltsquelle pro Screen ### Screenshot-Flow 1. Der Player-Agent sendet periodisch (Intervall: `MORZ_INFOBOARD_SCREENSHOT_EVERY`) einen Screenshot per `POST /api/v1/player/screenshot` (Multipart, kein Auth). 2. Alternativ kann das Backend per MQTT-Topic `signage/screen/{slug}/screenshot-request` einen On-Demand-Screenshot anfordern (`Notifier.RequestScreenshot(slug)`). Der Player-Agent empfaengt das Signal und ruft `Screenshotter.TakeAndSendOnce(ctx)` auf. 3. Das Backend speichert den Screenshot im `ScreenshotStore` (In-Memory, keyed by `screen_id`). Pro Screen wird nur der jeweils neueste Screenshot gehalten. 4. Eingeloggte Benutzer koennen den Screenshot unter `GET /api/v1/screens/{screenId}/screenshot` abrufen (`authOnly`). Der Response-Header enthaelt den vom Player gemeldeten MIME-Typ sowie `Cache-Control: no-store`. 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= │ ├─► 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