morz-infoboard/docs/SERVER-KONZEPT.md
Jesko Anschütz b73da77835 feat(screens): Screen-Übersicht mit On-Demand-Screenshots für Multi-Screen-User
- GET /manage: neue Übersichtsseite mit Bulma-Karten für screen_user mit ≥2 Screens
- handleScreenUserRedirect leitet bei ≥2 Screens auf /manage statt auf ersten Screen
- On-Demand-Screenshot-Flow via MQTT:
  - Backend publiziert signage/screen/{slug}/screenshot-request beim Seitenaufruf
  - Player-Agent empfängt Topic, ruft TakeAndSendOnce() auf
  - Player POST /api/v1/player/screenshot → Backend speichert in ScreenshotStore (RAM)
  - GET /api/v1/screens/{screenId}/screenshot liefert gespeichertes Bild (authOnly)
- ScreenshotStore: In-Memory, thread-safe, kein Persistenz-Overhead
- JS-Retry nach 4s in Templates (Screenshot braucht 1-3s für MQTT-Roundtrip)
- manageTmpl zeigt Screenshot-Thumbnail beim Einzelscreen-Aufruf
- Doku: neue Endpoints, MQTT-Topics, Screenshot-Flow in SERVER-KONZEPT.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 14:27:10 +01:00

13 KiB
Raw Permalink Blame History

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.
  • 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:

{
  "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