# Backend Dieses Verzeichnis enthaelt das zentrale Go-Backend fuer das Info-Board-System. ## Aufgaben - HTTP-API und serverseitige HTML-UI (Bulma) - PostgreSQL-Anbindung mit automatischen Migrationen - Session-basierte Authentifizierung und rollenbasierte Zugriffskontrolle - Medienverwaltung und Playlist-Management - Player-Status-Ingest und Diagnose - MQTT-Notifizierungen bei Playlist-Aenderungen ## Benutzerrollen - `admin_user` — Volller Zugriff auf alle Funktionen - `screen_user` — Darf Medien hochladen und Playlist bearbeiten. Display-Steuerung (An/Aus, Zeitplan, Override) - `restricted` — Darf Medien hochladen und Playlist bearbeiten. Keine Display-Steuerung. - `tenant_user` — Tenant-Operator für Self-Service-Dashboard ## Unterstruktur - `cmd/api/` — Startpunkt des Backends - `internal/app/` — App-Initialisierung und Lifecycle - `internal/config/` — Konfiguration via Umgebungsvariablen - `internal/db/` — PostgreSQL-Anbindung und Migrations-Runner - `internal/store/` — Datenbankzugriff (TenantStore, ScreenStore, MediaStore, PlaylistStore, AuthStore, ScreenScheduleStore, GlobalOverrideStore) - `internal/fileutil/` — Upload-Hilfsfunktionen (SaveUploadedFile mit Tenant-Isolation) - `internal/httpapi/` — HTTP-Routing, Middleware und Handler - `internal/httpapi/csrf.go` — Double-Submit-Cookie CSRF-Schutz - `internal/httpapi/ratelimit.go` — Rate-Limiting fuer /login (Brute-Force-Schutz) - `internal/httpapi/uploads.go` — Upload-Handler konsolidiert - `internal/httpapi/screenshot.go` — Handler fuer Player-Screenshot-Upload und Screenshot-Abruf - `internal/httpapi/screenshot_store.go` — In-Memory-Store fuer Screenshots (`ScreenshotStore`, thread-safe via `sync.RWMutex`) - `internal/httpapi/manage/` — Admin-UI und Playlist-Management-UI - `internal/httpapi/manage/csrf_helpers.go` — CSRF-Token Helpers fuer Templates (manage-Package) - `internal/httpapi/tenant/` — Tenant-Self-Service-Dashboard - `internal/httpapi/tenant/csrf_helpers.go` — CSRF-Token Helpers fuer Templates (tenant-Package, Import-Cycle-Isolation) - `internal/mqttnotifier/` — MQTT-Notifizierungen (`NotifyChanged`, `RequestScreenshot`, `SendDisplayCommand`) - `internal/scheduler/` — Display-Zeitplan-Scheduler (prueft jede Minute aktive Zeitplaene und sendet MQTT-Befehle) - `internal/reqcontext/` — Context-Keys fuer authentifizierten User ### scheduler (`internal/scheduler/scheduler.go`) Startet eine Goroutine (`scheduler.Run`), die jede Minute alle aktiven Zeitplaene aus `screen_schedules` laedt und — sofern `power_on_time` oder `power_off_time` mit der aktuellen Uhrzeit übereinstimmt — per MQTT den Befehl `display_on` bzw. `display_off` sendet. Der Scheduler wird in `internal/app/app.go` als Goroutine gestartet und laeuft bis zum Kontext-Abbruch beim Server-Shutdown. **Wochenend-Sperre:** An Samstagen und Sonntagen werden Zeitplaene ignoriert — der Reconciler sendet dann keine automatischen Ein-/Ausschalt-Kommandos. Manuelle Overrides (global oder per-Screen) wirken jedoch auch am Wochenende. ## Datenbank-Stores ### AuthStore (`internal/store/auth.go`) **Screen-User Management:** - `CreateScreenUser(ctx, tenantID, username, passwordHash)` — neuen Screen-User anlegen - `ListScreenUsers(ctx, tenantID)` — alle Screen-User eines Tenants auflisten - `DeleteUser(ctx, userID)` — User und alle zugeordneten Permissions loeschen **Authentifizierung:** - `GetUserByUsername(ctx, username)` — Nutzer per Username laden - `CreateSession(ctx, userID, ttl)` — neue Session anlegen - `GetSessionUser(ctx, sessionID)` — User zu gueltigem Session-Token laden - `DeleteSession(ctx, sessionID)` — Session loeschen (Logout) - `CleanExpiredSessions(ctx)` — abgelaufene Sessions bereinigen - `EnsureAdminUser(ctx, tenantSlug, password)` — Admin-User beim Start anlegen - `VerifyPassword(ctx, userID, password)` — Passwort gegen bcrypt-Hash pruefen ### GlobalOverrideStore (`internal/store/store.go`) Verwaltet einen systemweiten Display-Override (max. 1 Zeile in `global_override`): - `Get(ctx)` — aktuellen globalen Override laden (nil wenn keiner gesetzt) - `Upsert(ctx, type, until)` — Override setzen oder ueberschreiben (`type`: `"on"` | `"off"`) - `Delete(ctx)` — Override entfernen Der Reconciler im Scheduler wertet den globalen Override aus und wendet ihn auf alle Screens an. ### ScreenStore (`internal/store/screen.go`) **Screen-User Zugriffskontrolle:** - `GetAccessibleScreens(ctx, userID)` — alle Screens, auf die der User Zugriff hat - `HasUserScreenAccess(ctx, userID, screenID)` — prueft ob User auf Screen zugreifen darf (boolean) - `AddUserToScreen(ctx, userID, screenID)` — User zu Screen hinzufuegen (Eintrag in `user_screen_permissions`) - `RemoveUserFromScreen(ctx, userID, screenID)` — User von Screen entfernen - `GetScreenUsers(ctx, screenID)` — alle User, die auf Screen Zugriff haben ## Aktuelle Endpunkte ### Oeffentlich (keine Auth) | Methode | Pfad | Beschreibung | |---------|-------------------------------------|---------------------------------------| | GET | `/healthz` | Health-Check | | GET | `/api/v1` | API-Entrypoint | | GET | `/api/v1/meta` | Metainformationen | | POST | `/api/v1/player/status` | Status-Ingest vom Player-Agent | | POST | `/api/v1/player/screenshot` | Screenshot-Upload vom Player-Agent | | GET | `/api/v1/screens/status` | Uebersicht aller Screen-Status | | GET | `/api/v1/screens/{screenId}/status` | Einzelner Screen-Status | | DELETE | `/api/v1/screens/{screenId}/status` | Screen-Status loeschen | | GET | `/api/v1/screens/{screenId}/playlist` | Playlist fuer Player (kein Auth) | | POST | `/api/v1/screens/register` | Agent-Selbstregistrierung | | POST | `/api/v1/tools/message-wall/resolve`| Message-Wall-Aufloesungsendpunkt | | GET | `/status` | HTML-Diagnoseseite | | GET | `/status/{screenId}` | HTML-Detailseite Einzelscreen | | GET | `/uploads/{filename}` | Hochgeladene Dateien abrufen | | GET | `/static/bulma.min.css` | Statisches CSS | | GET | `/static/Sortable.min.js` | Statisches JS | | GET | `/login` | Login-Formular | | POST | `/login` | Login verarbeiten | | POST | `/logout` | Session beenden | ### Nur eingeloggte Benutzer (`RequireAuth`) | Methode | Pfad | Beschreibung | |---------|-------------------------------------------|---------------------------------------| | GET | `/manage` | Screen-Uebersicht fuer screen_user | | GET | `/manage/{screenSlug}` | Playlist-Management-UI | | POST | `/manage/{screenSlug}/upload` | Medium fuer Screen hochladen | | POST | `/manage/{screenSlug}/items` | Item zur Playlist 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 | | GET | `/api/v1/playlists/{screenId}` | Playlist mit Metadaten abrufen | | POST | `/api/v1/playlists/{playlistId}/items` | Item zur Playlist hinzufuegen (API) | | PATCH | `/api/v1/items/{itemId}` | Item aktualisieren (API) | | DELETE | `/api/v1/items/{itemId}` | Item loeschen (API) | | PUT | `/api/v1/playlists/{playlistId}/order` | Items reordnen (API) | | PATCH | `/api/v1/playlists/{playlistId}/duration` | Standard-Dauer setzen (API) | | DELETE | `/api/v1/media/{id}` | Medium loeschen (API) | | GET | `/api/v1/screens/{screenId}/screenshot` | Screenshot eines Screens abrufen | | POST | `/api/v1/screens/{screenSlug}/display` | Display ein-/ausschalten (MQTT) | | POST | `/api/v1/screens/{screenSlug}/schedule` | Display-Zeitplan speichern | | GET | `/api/v1/global-override` | Globalen Override abrufen (204 = kein aktiver Override) | | POST | `/api/v1/global-override` | Globalen Override setzen (type + until); sendet sofort MQTT | | DELETE | `/api/v1/global-override` | Globalen Override loeschen | | POST | `/api/v1/screens/{screenSlug}/override` | Per-Screen-Override setzen oder loeschen (on_until: null = loeschen) | ### Nur Admins (`RequireAuth` + `RequireAdmin`) | Methode | Pfad | Beschreibung | |---------|-------------------------------------------|---------------------------------------| | GET | `/admin` | Admin-Uebersicht | | POST | `/admin/screens/provision` | Provisionierungs-Job starten | | POST | `/admin/screens` | Neuen Screen anlegen | | POST | `/admin/screens/{screenId}/delete` | Screen loeschen | | POST | `/admin/users` | Screen-User anlegen | | POST | `/admin/users/{userID}/delete` | Screen-User loeschen | | POST | `/admin/screens/{screenID}/users` | User zu Screen hinzufuegen | | POST | `/admin/screens/{screenID}/users/{userID}/remove` | User von Screen entfernen | ### Tenant-scoped (`RequireAuth` + `RequireTenantAccess`) | Methode | Pfad | Beschreibung | |---------|---------------------------------------------------|---------------------------------| | GET | `/tenant/{tenantSlug}/dashboard` | Tenant-Self-Service-Dashboard | | POST | `/tenant/{tenantSlug}/upload` | Medium hochladen | | POST | `/tenant/{tenantSlug}/media/{mediaId}/delete` | Medium loeschen | | GET | `/api/v1/tenants/{tenantSlug}/screens` | Screens eines Tenants auflisten | | POST | `/api/v1/tenants/{tenantSlug}/screens` | Screen anlegen | | GET | `/api/v1/tenants/{tenantSlug}/media` | Medien eines Tenants auflisten | | POST | `/api/v1/tenants/{tenantSlug}/media` | Medium hochladen (API) | ## Konfiguration Alle Werte per Umgebungsvariable: | Variable | Bedeutung | Standard | |-----------------------------------|----------------------------------------------------------|---------------| | `MORZ_INFOBOARD_HTTP_ADDR` | HTTP-Listen-Adresse | `:8080` | | `MORZ_INFOBOARD_DATABASE_URL` | PostgreSQL-Connection-String | — | | `MORZ_INFOBOARD_UPLOAD_DIR` | Verzeichnis fuer hochgeladene Medien | `/tmp/morz-uploads` | | `MORZ_INFOBOARD_STATUS_STORE_PATH`| Pfad zur JSON-Persistenz-Datei fuer Status-Store | leer (in-memory) | | `MORZ_INFOBOARD_ADMIN_PASSWORD` | Passwort des initialen Admin-Users (leer = kein Anlegen) | leer | | `MORZ_INFOBOARD_DEFAULT_TENANT` | Slug des Tenants, dem der Admin zugeordnet wird | `morz` | | `MORZ_INFOBOARD_DEV_MODE` | `true` = Session-Cookie ohne Secure-Flag (nur lokal) | `false` | | `MORZ_INFOBOARD_REGISTER_SECRET` | Pre-Shared-Secret fuer POST /api/v1/screens/register | leer | | `MORZ_INFOBOARD_MQTT_BROKER` | MQTT-Broker-URL (leer = kein MQTT) | leer | | `MORZ_INFOBOARD_MQTT_USERNAME` | MQTT-Benutzername | leer | | `MORZ_INFOBOARD_MQTT_PASSWORD` | MQTT-Passwort | leer | Detailliertere Beschreibung und lokale Startbeispiele: `DEVELOPMENT.md`. ## Middleware ### `RequireScreenAccess` Middleware zur rollenbasierten Zugriffskontrolle auf Screen-Ressourcen. **Verhalten:** - Admins duerfen auf alle Screens zugreifen - Screen-User duerfen nur auf Screens zugreifen, fuer die sie in `user_screen_permissions` eingetragen sind - Tenant-User duerfen auf alle Screens ihres Tenants zugreifen - Response: `403 Forbidden` wenn keine Berechtigung **Verwendung:** - `GET /api/v1/screens/{screenId}/playlist` - `POST /manage/{screenSlug}/...` - Alle privaten Screen-Endpunkte ## Migrationen - `001_initial.sql` — initiales Schema (Tenants, Screens, Playlists, Media, etc.) - `002_auth.sql` — Auth-Tabellen (`users`, `sessions`) - `003_user_screen_permissions.sql` — Screen-User Management (`user_screen_permissions`) - `004_screen_status.sql` — Display-Zustand pro Screen (`screen_status`: screen_id, display_state, reported_at) - `005_screen_schedules.sql` — Zeitplan pro Screen (`screen_schedules`: screen_id, schedule_enabled, power_on_time, power_off_time) - `006_override.sql` — Spalte `override_on_until` in `screen_schedules` (per-Screen-Override) und Tabelle `global_override` (systemweiter Display-Override)