- ScreenScheduleStore.Upsert: override_on_until aus INSERT und ON CONFLICT entfernt — verhindert stillen Datenverlust beim Speichern eines Zeitplans. SetOverrideOnUntil bleibt alleinig zuständig für diese Spalte. - README.md: GlobalOverrideStore, vier neue API-Routen, Wochenend-Sperre und Migration 006_override.sql dokumentiert. - override.go: Auth-Scope-Kommentar über HandleSetGlobalOverride ergänzt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| cmd/api | ||
| internal | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
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
Unterstruktur
cmd/api/— Startpunkt des Backendsinternal/app/— App-Initialisierung und Lifecycleinternal/config/— Konfiguration via Umgebungsvariableninternal/db/— PostgreSQL-Anbindung und Migrations-Runnerinternal/store/— Datenbankzugriff (TenantStore, ScreenStore, MediaStore, PlaylistStore, AuthStore, ScreenScheduleStore, GlobalOverrideStore)internal/fileutil/— Upload-Hilfsfunktionen (SaveUploadedFile mit Tenant-Isolation)internal/httpapi/— HTTP-Routing, Middleware und Handlerinternal/httpapi/csrf.go— Double-Submit-Cookie CSRF-Schutzinternal/httpapi/ratelimit.go— Rate-Limiting fuer /login (Brute-Force-Schutz)internal/httpapi/uploads.go— Upload-Handler konsolidiertinternal/httpapi/screenshot.go— Handler fuer Player-Screenshot-Upload und Screenshot-Abrufinternal/httpapi/screenshot_store.go— In-Memory-Store fuer Screenshots (ScreenshotStore, thread-safe viasync.RWMutex)internal/httpapi/manage/— Admin-UI und Playlist-Management-UIinternal/httpapi/manage/csrf_helpers.go— CSRF-Token Helpers fuer Templates (manage-Package)internal/httpapi/tenant/— Tenant-Self-Service-Dashboardinternal/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 anlegenListScreenUsers(ctx, tenantID)— alle Screen-User eines Tenants auflistenDeleteUser(ctx, userID)— User und alle zugeordneten Permissions loeschen
Authentifizierung:
GetUserByUsername(ctx, username)— Nutzer per Username ladenCreateSession(ctx, userID, ttl)— neue Session anlegenGetSessionUser(ctx, sessionID)— User zu gueltigem Session-Token ladenDeleteSession(ctx, sessionID)— Session loeschen (Logout)CleanExpiredSessions(ctx)— abgelaufene Sessions bereinigenEnsureAdminUser(ctx, tenantSlug, password)— Admin-User beim Start anlegenVerifyPassword(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 hatHasUserScreenAccess(ctx, userID, screenID)— prueft ob User auf Screen zugreifen darf (boolean)AddUserToScreen(ctx, userID, screenID)— User zu Screen hinzufuegen (Eintrag inuser_screen_permissions)RemoveUserFromScreen(ctx, userID, screenID)— User von Screen entfernenGetScreenUsers(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_permissionseingetragen sind - Tenant-User duerfen auf alle Screens ihres Tenants zugreifen
- Response:
403 Forbiddenwenn keine Berechtigung
Verwendung:
GET /api/v1/screens/{screenId}/playlistPOST /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— Spalteoverride_on_untilinscreen_schedules(per-Screen-Override) und Tabelleglobal_override(systemweiter Display-Override)