morz-infoboard/docs/API-ENDPOINTS.md
Jesko Anschütz e884acf41d docs: media_assets.created_by_user_id + Berechtigungslogik dokumentiert
- SCHEMA.md: Index idx_media_assets_created_by_user_id hinzugefügt
- API-ENDPOINTS.md: GET/POST Media-Endpoints erweitert um owner_is_restricted und owner_username
- API-ENDPOINTS.md: DELETE Media-Endpoint mit Berechtigungslogik dokumentiert
  * admin_user: immer erlaubt
  * screen_user: erlaubt wenn asset.tenant_id == user.tenant_id
  * restricted: erlaubt nur wenn asset.created_by_user_id == user.id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:14:35 +01:00

34 KiB

Info-Board Neu - API-Endpoints Vollständig

Überblick

Die Backend-API unterteilt sich in mehrere Bereiche:

  • Health & Meta: System-Status und API-Informationen
  • Player Status: Status-Ingest und Diagnose vom Player
  • Screenshot API: On-Demand- und periodische Screenshots vom Player
  • Screen Management: CRUD und Registrierung von Screens
  • Playlists: Abruf und Verwaltung von Wiedergabelisten
  • Media: Upload und Verwaltung von Medien-Assets
  • Message Wall: Auflösung von Nachrichten-Wand-Anfragen
  • Admin & UI: Web-Formulare und Provisionierung
  • Provisioning: Erstinstallation neuer Screens (geplant)

Health & Meta

GET /healthz

Health-Check für Monitoring.

Response:

{
  "status": "ok",
  "service": "morz-infoboard-backend"
}

GET /api/v1

API-Entrypoint mit Tools-Übersicht.

Response:

{
  "name": "morz-infoboard-backend",
  "version": "dev",
  "tools": [
    "message-wall-resolve",
    "screen-status-list",
    "screen-status-detail",
    "player-status-ingest",
    "screen-status-delete"
  ]
}

GET /api/v1/meta

Zusätzliche Metainformationen (noch nicht spezifiziert).


Player Status (Diagnose)

Siehe separate Dokumentation in PLAYER-STATUS-HTTP.md.

Endpoints:

  • POST /api/v1/player/status — Status-Ingest vom Player-Agent
  • GET /api/v1/screens/status — Übersicht aller Screen-Status
  • GET /api/v1/screens/{screenId}/status — Einzelner Screen-Status
  • DELETE /api/v1/screens/{screenId}/status — Status löschen

POST /api/v1/player/status

Der Player-Agent sendet seinen aktuellen Status an den Server.

Request-Body:

{
  "screen_id": "info01-dev",
  "ts": "2026-03-22T16:00:00Z",
  "status": "running",
  "server_connectivity": "online",
  "server_url": "http://127.0.0.1:8080",
  "mqtt_broker": "tcp://127.0.0.1:1883",
  "heartbeat_every_seconds": 30,
  "started_at": "2026-03-22T15:59:30Z",
  "last_heartbeat_at": "2026-03-22T16:00:00Z"
}

Response:

{ "status": "accepted" }

Wenn auf dem Server ein MQTT-Broker konfiguriert ist (MORZ_INFOBOARD_MQTT_BROKER), enthält die Response zusätzlich ein mqtt-Objekt mit den Verbindungsdaten. Der Agent soll diese Konfiguration übernehmen und seine MQTT-Verbindung bei Bedarf neu aufbauen.

{
  "status": "accepted",
  "mqtt": {
    "broker": "tcp://mqtt.example.com:1883",
    "username": "agent",
    "password": "secret"
  }
}
  • mqtt — nur vorhanden, wenn ein Broker konfiguriert ist (omitempty); fehlt das Feld, bleibt die bestehende MQTT-Konfiguration des Agents unverändert
  • mqtt.broker — MQTT-Broker-URL (immer gesetzt, wenn mqtt vorhanden)
  • mqtt.username — Benutzername (nur wenn konfiguriert, omitempty)
  • mqtt.password — Passwort (nur wenn konfiguriert, omitempty)

Screen Management (JSON API)

POST /api/v1/screens/register

Agent-Selbstregistrierung — wird vom Player-Agent beim Hochfahren aufgerufen.

Der Agent upsert den Screen automatisch im Default-Tenant ("morz"), so dass alle deployt Screen automatisch im Admin-UI erscheinen.

Request-Body:

{
  "slug": "info10",
  "name": "Info10 Bildschirm",
  "orientation": "landscape"
}

Response:

{
  "id": "uuid...",
  "tenant_id": "uuid...",
  "slug": "info10",
  "name": "Info10 Bildschirm",
  "orientation": "landscape",
  "created_at": "2026-03-22T16:00:00Z",
  "updated_at": "2026-03-22T16:00:00Z"
}

Status:

  • 200 OK — Screen wurde erzeugt oder aktualisiert (upsert)
  • 400 Bad Request — Slug fehlt oder ungültig
  • 500 Internal Server Error — DB-Fehler oder Default-Tenant nicht vorhanden

GET /api/v1/tenants/{tenantSlug}/screens

Listet alle Screens eines Tenants auf.

Response:

[
  {
    "id": "uuid...",
    "tenant_id": "uuid...",
    "slug": "info10",
    "name": "Info10 Bildschirm",
    "orientation": "landscape",
    "created_at": "2026-03-22T16:00:00Z",
    "updated_at": "2026-03-22T16:00:00Z"
  },
  ...
]

Status:

  • 200 OK — Liste erfolgreich abrufen
  • 404 Not Found — Tenant nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

POST /api/v1/tenants/{tenantSlug}/screens

Erstellt einen neuen Screen für einen Tenant (Admin-API).

Request-Body:

{
  "slug": "new-screen",
  "name": "New Display",
  "orientation": "portrait"
}

Response:

{
  "id": "uuid...",
  "tenant_id": "uuid...",
  "slug": "new-screen",
  "name": "New Display",
  "orientation": "portrait",
  "created_at": "2026-03-22T16:00:00Z",
  "updated_at": "2026-03-22T16:00:00Z"
}

Status:

  • 201 Created — Screen erstellt
  • 400 Bad Request — Slug oder Name fehlt
  • 404 Not Found — Tenant nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Playlist Management (JSON API)

GET /api/v1/screens/{screenId}/playlist

Abruf der aktiven Playlist für einen Screen (Player-Sync).

Der Player ruft diesen Endpoint auf, um die aktuellen Inhalte zu laden.

Response:

{
  "playlist_id": "uuid...",
  "default_duration_seconds": 20,
  "items": [
    {
      "id": "uuid...",
      "playlist_id": "uuid...",
      "media_asset_id": null,
      "order_index": 0,
      "type": "web",
      "src": "http://example.com/page1",
      "title": "Startseite",
      "duration_seconds": 30,
      "enabled": true,
      "valid_from": null,
      "valid_until": null,
      "created_at": "2026-03-22T16:00:00Z"
    },
    {
      "id": "uuid...",
      "playlist_id": "uuid...",
      "media_asset_id": "uuid...",
      "order_index": 1,
      "type": "image",
      "src": "/uploads/banner.jpg",
      "title": "Werbebanner",
      "duration_seconds": 20,
      "enabled": true,
      "valid_from": null,
      "valid_until": null,
      "created_at": "2026-03-22T16:00:00Z"
    }
  ]
}

Wenn keine Playlist vorhanden ist, wird eine leere Liste zurückgegeben:

{
  "items": []
}

Status:

  • 200 OK — Playlist abrufen
  • 404 Not Found — Screen nicht vorhanden

GET /api/v1/playlists/{screenId}

Abrufen einer kompletten Playlist mit Metadaten.

Response:

{
  "playlist": {
    "id": "uuid...",
    "screen_id": "uuid...",
    "tenant_id": "uuid...",
    "name": "Hauptplaylist",
    "is_active": true,
    "default_duration_seconds": 20,
    "fallback_enabled": true,
    "fallback_dir": "/fallback",
    "shuffle_enabled": false,
    "created_at": "2026-03-22T16:00:00Z",
    "updated_at": "2026-03-22T16:00:00Z"
  },
  "items": [
    {
      "id": "uuid...",
      "playlist_id": "uuid...",
      "type": "web",
      "src": "http://example.com",
      "title": "Example",
      "duration_seconds": 20,
      "enabled": true,
      "created_at": "2026-03-22T16:00:00Z"
    }
  ]
}

Status:

  • 200 OK — Erfolgreich abrufen
  • 500 Internal Server Error — DB-Fehler

POST /api/v1/playlists/{playlistId}/items

Fügt ein Item zu einer Playlist hinzu.

Request-Body (Optionen A: Aus Media-Library):

{
  "media_asset_id": "uuid...",
  "title": "Optional überschriebener Titel"
}

Request-Body (Optionen B: Direkte URL):

{
  "type": "web",
  "src": "http://example.com/page",
  "title": "Example Page",
  "duration_seconds": 30,
  "valid_from": "2026-03-22T09:00:00Z",
  "valid_until": "2026-03-22T17:00:00Z"
}

Response:

{
  "id": "uuid...",
  "playlist_id": "uuid...",
  "type": "web",
  "src": "http://example.com/page",
  "title": "Example Page",
  "duration_seconds": 30,
  "enabled": true,
  "valid_from": "2026-03-22T09:00:00Z",
  "valid_until": "2026-03-22T17:00:00Z",
  "created_at": "2026-03-22T16:00:00Z"
}

Status:

  • 201 Created — Item erstellt
  • 400 Bad Request — Type oder Src fehlt; Media-Asset nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

PATCH /api/v1/items/{itemId}

Aktualisiert ein Playlist-Item (Titel, Dauer, Zeitfenster, aktiviert).

Request-Body:

{
  "title": "Neuer Titel",
  "duration_seconds": 25,
  "enabled": true,
  "valid_from": "2026-03-22T09:00:00Z",
  "valid_until": "2026-03-22T17:00:00Z"
}

Status:

  • 204 No Content — Erfolgreich aktualisiert
  • 400 Bad Request — Ungültige Dauer
  • 500 Internal Server Error — DB-Fehler

DELETE /api/v1/items/{itemId}

Löscht ein Playlist-Item.

Status:

  • 204 No Content — Erfolgreich gelöscht
  • 500 Internal Server Error — DB-Fehler

PUT /api/v1/playlists/{playlistId}/order

Reordnet die Items einer Playlist anhand einer geordneten Liste von Item-IDs.

Request-Body:

[
  "item-id-1",
  "item-id-2",
  "item-id-3"
]

Status:

  • 204 No Content — Erfolgreich reordert
  • 400 Bad Request — JSON-Array erwartet
  • 500 Internal Server Error — DB-Fehler

PATCH /api/v1/playlists/{playlistId}/duration

Setzt die Standard-Dauer für neue Items einer Playlist.

Request-Body (Form-Encoded):

default_duration_seconds=25

Status:

  • 204 No Content — Erfolgreich aktualisiert
  • 400 Bad Request — Ungültige oder fehlende Dauer
  • 500 Internal Server Error — DB-Fehler

Media Management (JSON API)

GET /api/v1/tenants/{tenantSlug}/media

Listet alle Medien-Assets eines Tenants auf.

Rolle restricted: Gibt nur Medien zurück, die der eingeloggte User selbst hochgeladen hat (created_by_user_id = user.id).

Alle anderen Rollen (admin_user, screen_user): Alle Medien des Tenants. Response-Felder owner_is_restricted und owner_username sind befüllt, wenn das Medium von einem Restricted-User hochgeladen wurde.

Response:

[
  {
    "id": "uuid...",
    "tenant_id": "uuid...",
    "title": "Banner Image",
    "description": null,
    "type": "image",
    "source_kind": "upload",
    "storage_path": "/uploads/1234567890_banner.jpg",
    "original_url": null,
    "mime_type": "image/jpeg",
    "size_bytes": 102400,
    "enabled": true,
    "created_at": "2026-03-22T16:00:00Z",
    "updated_at": "2026-03-22T16:00:00Z",
    "owner_is_restricted": false,
    "owner_username": "admin"
  },
  {
    "id": "uuid...",
    "tenant_id": "uuid...",
    "title": "External Website",
    "type": "web",
    "source_kind": "remote_url",
    "original_url": "http://example.com",
    "storage_path": null,
    "enabled": true,
    "created_at": "2026-03-22T16:00:00Z",
    "owner_is_restricted": true,
    "owner_username": "teacher01"
  }
]

Wenn keine Assets vorhanden sind, wird eine leere Liste zurückgegeben.

Status:

  • 200 OK — Liste erfolgreich abrufen
  • 404 Not Found — Tenant nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

POST /api/v1/tenants/{tenantSlug}/media

Registriert ein neues Medien-Asset (Datei-Upload oder externe URL). Setzt created_by_user_id auf die ID des eingeloggten Users.

Request-Typ A: Datei-Upload (Multipart)

Content-Type: multipart/form-data

type: image (oder video, pdf)
title: Mein Bild
file: <binary data>

Request-Typ B: Externe URL (Multipart)

Content-Type: multipart/form-data

type: web
title: Externe Website
url: http://example.com

Response:

{
  "id": "uuid...",
  "tenant_id": "uuid...",
  "title": "Mein Bild",
  "type": "image",
  "source_kind": "upload",
  "storage_path": "/uploads/1234567890_mein_bild.jpg",
  "mime_type": "image/jpeg",
  "size_bytes": 102400,
  "enabled": true,
  "created_at": "2026-03-22T16:00:00Z",
  "owner_is_restricted": false,
  "owner_username": "teacher01"
}

Status:

  • 201 Created — Asset erstellt
  • 400 Bad Request — Ungültiger Type, fehlende Datei/URL, oder Request zu groß (>512 MB)
  • 404 Not Found — Tenant nicht vorhanden
  • 500 Internal Server Error — Datei-/DB-Fehler

DELETE /api/v1/media/{id}

Löscht ein Medien-Asset (und physische Datei falls lokal gespeichert).

Berechtigungen:

  • admin_user: Immer erlaubt
  • screen_user: Erlaubt, wenn asset.tenant_id == user.tenant_id
  • restricted: Erlaubt nur wenn asset.created_by_user_id == user.id (eigenes Medium)

Status:

  • 204 No Content — Erfolgreich gelöscht
  • 403 Forbidden — User hat nicht die erforderliche Berechtigung
  • 404 Not Found — Asset nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Display-Steuerung (JSON API)

Beide Endpunkte erfordern RequireAuth + RequireScreenAccess (authScreen-Middleware).

POST /api/v1/screens/{screenSlug}/display

Sendet einen MQTT-Befehl zum Ein- oder Ausschalten des physischen Displays.

Auth: Erforderlich (Bearer-Token oder Session-Cookie). Screen-Zugriff erforderlich. Rollen: Nur admin_user und screen_user. Benutzer mit Rolle restricted erhalten 403 Forbidden.

Path-Parameter:

  • screenSlug — Slug des Screens

Request-Body:

{"state": "on"}

oder

{"state": "off"}

Response: 204 No Content

Fehler:

  • 400 Bad Requeststate ist nicht "on" oder "off", oder ungültiges JSON
  • 403 Forbidden — Benutzer hat nicht die erforderliche Rolle (restricted-Benutzer dürfen Display nicht steuern)
  • 502 Bad Gateway — MQTT-Publish fehlgeschlagen

POST /api/v1/screens/{screenSlug}/schedule

Speichert den Zeitplan für das automatische Ein-/Ausschalten eines Displays.

Auth: Erforderlich. Screen-Zugriff erforderlich. Rollen: Nur admin_user und screen_user. Benutzer mit Rolle restricted erhalten 403 Forbidden.

Path-Parameter:

  • screenSlug — Slug des Screens

Request-Body:

{
  "schedule_enabled": true,
  "power_on_time": "06:00",
  "power_off_time": "22:00"
}

Zeitangaben müssen im Format HH:MM (24h) oder als leerer String "" übergeben werden. Der Scheduler prüft jede Minute, ob die aktuelle Uhrzeit mit power_on_time oder power_off_time übereinstimmt, und sendet dann den entsprechenden MQTT-Befehl.

Response: 204 No Content

Fehler:

  • 400 Bad Request — Zeitformat ungültig (nicht HH:MM), oder ungültiges JSON
  • 403 Forbidden — Benutzer hat nicht die erforderliche Rolle (restricted-Benutzer dürfen Zeitplan nicht ändern)
  • 404 Not Found — Screen nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Globaler Override

Methode Pfad Auth Beschreibung
GET /api/v1/global-override authUser Aktiven Override abrufen (204 wenn keiner aktiv)
POST /api/v1/global-override authUser Override setzen + sofort MQTT an alle Screens
DELETE /api/v1/global-override authUser Override aufheben

GET /api/v1/global-override

Ruft den aktuell aktiven globalen Override ab.

Response: 200 OK (wenn aktiv)

{"type":"off","until":"2026-04-05T18:00:00+02:00","set_at":"2026-03-27T15:30:00+02:00"}

Response: 204 No Content (wenn kein Override aktiv)


POST /api/v1/global-override

Setzt einen globalen Override und sendet sofort MQTT-Befehle an alle Screens.

Rollen: Nur admin_user und screen_user. Benutzer mit Rolle restricted erhalten 403 Forbidden.

Request-Body:

{"type":"off","until":"2026-04-05T18:00:00+02:00"}

Response: 200 OK

{"type":"off","until":"2026-04-05T18:00:00+02:00","set_at":"2026-03-27T15:30:00+02:00"}

Fehler:

  • 400 Bad Requesttype nicht "on"/"off", oder ungültiges Zeitformat
  • 403 Forbidden — Benutzer hat nicht die erforderliche Rolle (restricted-Benutzer dürfen Override nicht setzen)
  • 500 Internal Server Error — DB-Fehler

DELETE /api/v1/global-override

Hebt den aktuellen globalen Override auf.

Rollen: Nur admin_user und screen_user. Benutzer mit Rolle restricted erhalten 403 Forbidden.

Response: 204 No Content

Fehler:

  • 403 Forbidden — Benutzer hat nicht die erforderliche Rolle (restricted-Benutzer dürfen Override nicht löschen)
  • 500 Internal Server Error — DB-Fehler

Per-Screen Override

Methode Pfad Auth Beschreibung
POST /api/v1/screens/{screenSlug}/override authScreen Per-Screen "Einschalten bis" setzen oder löschen

POST /api/v1/screens/{screenSlug}/override

Setzt oder löscht den per-Screen "Einschalten bis"-Override. Mit diesem Override bleibt ein Monitor bis zu dem angegebenen Zeitpunkt eingeschaltet, selbst wenn der globale Schedule "aus" vorsieht.

Auth: Erforderlich. Screen-Zugriff erforderlich. Rollen: Nur admin_user und screen_user. Benutzer mit Rolle restricted erhalten 403 Forbidden.

Path-Parameter:

  • screenSlug — Slug des Screens

Request-Body:

{"on_until":"2026-04-05T18:00:00+02:00"}

Um den Override zu löschen, on_until auf null setzen:

{"on_until":null}

Response: 204 No Content

Fehler:

  • 400 Bad Request — Ungültiges Zeitformat oder ungültiges JSON
  • 403 Forbidden — Benutzer hat nicht die erforderliche Rolle (restricted-Benutzer dürfen Override nicht setzen)
  • 404 Not Found — Screen nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Message Wall

POST /api/v1/tools/message-wall/resolve

Spezialendpoint zur Auflösung von Nachrichten-Wand-Anfragen (noch in Entwicklung).


Authentifizierung (Web-Formulare)

Alle Auth-Routen erfordern keine vorherige Authentifizierung.

Benutzerrollen

Das System unterscheidet folgende Rollen:

  • admin_user — Volller Zugriff auf alle Funktionen, inkl. Benutzerverwaltung und Display-Steuerung
  • screen_user — Darf Medien hochladen, Playlists bearbeiten und Displays steuern (An/Aus, Zeitplan, Override)
  • restricted — Darf Medien hochladen und Playlist bearbeiten. Keine Display-Steuerung (An/Aus, Zeitplan, Override). Betroffene Endpunkte antworten mit 403 Forbidden.
  • tenant_user — Tenant-Operator für Self-Service-Dashboard (Medienupload, Tenantmenü)

GET /

Root-Redirect auf /login.

  • Anfragen auf exakt / werden per 303 See Other zu /login weitergeleitet.
  • Anfragen auf unbekannte Pfade (z. B. /irgendwas) geben 404 Not Found zurück (404-Guard).

Status:

  • 303 See Other — Weiterleitung zu /login
  • 404 Not Found — Pfad existiert nicht

GET /login

Zeigt das Login-Formular.

  • Wenn ein gueltiges morz_session-Cookie vorhanden ist, wird direkt zum jeweiligen Dashboard weitergeleitet (/admin fuer Admins, /tenant/{slug}/dashboard fuer Tenant-User).

Response: HTML-Seite mit Benutzername/Passwort-Formular und optionaler Flash-Message.


POST /login

Verarbeitet die Login-Eingabe.

Request (Form-Encoded):

username=admin&password=geheim&csrf_token=<token>

Das CSRF-Token muss als verstecktes Formularfeld csrf_token mitgesendet werden. Der Token wird beim GET /login als Cookie morz_csrf gesetzt und in den Template-Daten als {{.CSRFToken}} bereitgestellt.

Verhalten:

  • Passwort wird per bcrypt.CompareHashAndPassword geprueft
  • Bei Erfolg wird ein morz_session-Cookie gesetzt (HttpOnly, Secure, 24h TTL)
  • Weiterleitung je nach Rolle: admin/admin, tenant/tenant/{slug}/dashboard
  • Bei Fehler: Rueckkehr zur Login-Seite mit Flash-Message

Status:

  • 303 See Other — Erfolg, Weiterleitung
  • 303 See Other — Fehler, Rueckkehr zur Login-Seite mit ?msg=

POST /logout

Meldet den aktuellen Benutzer ab.

Request (Form-Encoded):

csrf_token=<token>

Das CSRF-Token muss als verstecktes Formularfeld csrf_token mitgesendet werden. Der aktuelle Token-Wert wird beim GET-Aufruf der aufrufenden Seite als {{.CSRFToken}} in die Template-Daten eingebettet und als Cookie morz_csrf gesetzt.

Verhalten:

  • Session wird in der DB geloescht (DeleteSession)
  • Cookie wird mit MaxAge=-1 geloescht
  • Weiterleitung zu /login

Status:

  • 303 See Other
  • 403 Forbidden — CSRF-Token fehlt oder ungueltig

Tenant Self-Service Dashboard (Web-Formulare)

Alle Tenant-Routen erfordern RequireAuth + RequireTenantAccess. Admins koennen auf jeden Tenant zugreifen; Tenant-User nur auf ihren eigenen.

GET /tenant/{tenantSlug}/dashboard

Zeigt das Tenant-Self-Service-Dashboard.

Tabs:

  • Tab A "Meine Monitore" — Screen-Karten mit Live-Status (via JS-Fetch aus /api/v1/screens/status)
  • Tab B "Mediathek" — Upload-Formular und Dateiliste

Query-Parameter:

  • tab=media — oeffnet direkt Tab B (z. B. nach Upload-Redirect)
  • flash=uploaded / flash=deleted — zeigt Erfolgs-Flash-Message

Response: HTML-Seite.


POST /tenant/{tenantSlug}/upload

Laedt ein Medium fuer den Tenant hoch.

Request (Multipart Form):

type: image (oder video, pdf)
title: Mein Bild
file: <binary data>

oder fuer eine Web-URL:

type: web
title: Externe Website
url: http://example.com

Verhalten:

  • Datei wird in MORZ_INFOBOARD_UPLOAD_DIR gespeichert
  • MIME-Typ wird aus dem Upload-Header abgeleitet
  • Max. Upload-Groesse: 512 MB

Status:

  • 303 See Other/tenant/{slug}/dashboard?tab=media&flash=uploaded
  • 400 Bad Request — fehlender Typ oder Datei
  • 404 Not Found — Tenant nicht vorhanden

POST /tenant/{tenantSlug}/media/{mediaId}/delete

Loescht ein Medien-Asset des Tenants.

Verhalten:

  • Eigentuemer-Pruefung: asset.TenantID muss mit dem Tenant uebereinstimmen
  • Physische Datei wird geloescht sofern vorhanden

Status:

  • 303 See Other/tenant/{slug}/dashboard?tab=media&flash=deleted
  • 403 Forbidden — Asset gehoert nicht diesem Tenant
  • 404 Not Found — Tenant oder Asset nicht vorhanden

Admin UI (Web-Formulare)

GET /admin

Administrations-Dashboard mit Übersicht aller Screens und Tenants.

Rückgabe: HTML-Seite mit:

  • Liste aller Screens
  • Status-Information
  • Provisioning-Formulare
  • Screen-Verwaltung

POST /admin/screens/provision

Startet einen Provisionierungs-Job für einen neuen oder bestehenden Screen.

Request-Body (Form-Encoded oder JSON):

{
  "screen_id": "uuid...",
  "target_ip": "192.168.1.100",
  "target_port": 22,
  "remote_user": "root",
  "auth_mode": "password",
  "provided_secret_ref": "secret-key-123"
}

Status:

  • 200 OK — Job erfolgreich erstellt
  • 400 Bad Request — Fehlende oder ungültige Parameter
  • 404 Not Found — Screen nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Hinweis: Der eigentliche Provisionierungs-Job läuft asynchron über einen Worker ab.


POST /admin/screens

Erstellt einen neuen Screen über das Admin-Formular.

Request-Body (Form-Encoded):

slug=new-screen&name=Neuer+Bildschirm&orientation=landscape

Status:

  • 200 OK oder 201 Created — Screen erstellt
  • 400 Bad Request — Fehlende Parameter
  • 500 Internal Server Error — DB-Fehler

Rückleitung zur Admin-Seite.


POST /admin/screens/{screenId}/delete

Löscht einen Screen.

Status:

  • 200 OK — Screen gelöscht
  • 404 Not Found — Screen nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zur Admin-Seite.


Screen-User Management (Admin)

POST /admin/users

Erstellt einen neuen Screen-User für einen Tenant (Admin-Formular).

Request-Body (Form-Encoded):

username=screenuser1&password=geheim

Verhalten:

  • Neuer User mit role = 'screen_user' wird angelegt
  • Passwort wird per bcrypt gehasht
  • User wird dem aktuellen Tenant zugeordnet

Status:

  • 200 OK oder 201 Created — Screen-User erstellt
  • 400 Bad Request — Fehlende oder ungültige Parameter, Username bereits vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zur Admin-Seite.


POST /admin/users/{userID}/delete

Löscht einen Screen-User und alle zugeordneten Screen-Permissions.

Verhalten:

  • User mit Rolle screen_user wird gelöscht
  • Alle Einträge in user_screen_permissions für diesen User werden gelöscht

Status:

  • 200 OK — Screen-User gelöscht
  • 404 Not Found — User nicht vorhanden oder falscher Typ
  • 500 Internal Server Error — DB-Fehler

Rückleitung zur Admin-Seite.


POST /admin/screens/{screenID}/users

Fügt einen Screen-User zu einem Screen hinzu.

Request-Body (Form-Encoded):

user_id=<userID>

Verhalten:

  • Eintrag in user_screen_permissions wird erstellt
  • User muss vom Typ screen_user sein
  • Unique-Constraint verhindert Duplikate

Status:

  • 200 OK — User zu Screen hinzugefügt
  • 400 Bad Request — Fehlende Parameter, User bereits hinzugefügt
  • 404 Not Found — Screen oder User nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zur Admin-Seite oder zum Screen-Detail.


POST /admin/screens/{screenID}/users/{userID}/remove

Entfernt einen Screen-User von einem Screen.

Verhalten:

  • Eintrag in user_screen_permissions wird gelöscht
  • User behält seine Existenz; nur die Permission wird entfernt

Status:

  • 200 OK — User von Screen entfernt
  • 404 Not Found — Screen, User oder Permission nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zur Admin-Seite oder zum Screen-Detail.


Playlist Management UI (Web-Formulare)

GET /manage

Übersichtsseite für eingeloggte Benutzer.

Auth: RequireAuth.

Verhalten:

  • Admins und Tenant-User werden direkt zu ihrer Standard-Ansicht weitergeleitet.
  • Screen-User mit genau einem zugeordneten Screen werden direkt zu GET /manage/{screenSlug} weitergeleitet.
  • Screen-User mit mehreren zugeordneten Screens erhalten eine Übersichtsseite mit Links zu den einzelnen Screens.

Response: HTML-Seite oder Redirect (303 See Other).


GET /manage/{screenSlug}

Verwaltungs-UI für die Playlist eines Screens.

Rückgabe: HTML-Seite mit:

  • Liste der Medien-Assets
  • Playlist-Editor
  • Upload-Formular
  • Item-Verwaltung

POST /manage/{screenSlug}/upload

Datei-Upload über das Manage-Formular.

Request (Multipart):

type: image (oder video, pdf)
title: Neues Bild
file: <binary data>

Status:

  • 201 Created — Asset erfolgreich hochgeladen
  • 404 Not Found — Screen nicht vorhanden
  • 500 Internal Server Error — Fehler

Rückleitung zum Manage-Formular.


POST /manage/{screenSlug}/items

Fügt ein Item zur Playlist hinzu (Formular).

Request (Form-Encoded):

media_asset_id=uuid...&duration_seconds=25
oder
type=web&src=http://example.com&duration_seconds=30

Status:

  • 201 Created — Item erstellt
  • 404 Not Found — Screen nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zum Manage-Formular.


POST /manage/{screenSlug}/items/{itemId}

Aktualisiert ein Item (Formular).

Request (Form-Encoded):

title=Neuer+Titel&duration_seconds=25&enabled=on

Status:

  • 200 OK oder 204 No Content — Erfolgreich aktualisiert
  • 404 Not Found — Item nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zum Manage-Formular.


POST /manage/{screenSlug}/items/{itemId}/delete

Löscht ein Item (Formular).

Status:

  • 204 No Content — Erfolgreich gelöscht
  • 404 Not Found — Item nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zum Manage-Formular.


POST /manage/{screenSlug}/reorder

Reordert Items (Formular).

Request (Form-Encoded mit Array-Syntax):

items=item-id-1&items=item-id-2&items=item-id-3

Status:

  • 204 No Content — Erfolgreich reordert
  • 500 Internal Server Error — DB-Fehler

Rückleitung zum Manage-Formular.


POST /manage/{screenSlug}/media/{mediaId}/delete

Löscht ein Medien-Asset (Formular).

Status:

  • 204 No Content — Erfolgreich gelöscht
  • 404 Not Found — Asset nicht vorhanden
  • 500 Internal Server Error — DB-Fehler

Rückleitung zum Manage-Formular.


Datei-Serving

GET /uploads/{filename}

Stellt hochgeladene Medien-Dateien bereit.

Query-Parameter:

  • Keine

Status:

  • 200 OK — Datei gefunden
  • 404 Not Found — Datei nicht vorhanden

Diagnostic Pages (HTML)

GET /status

HTML-Diagnoseseite für den Browser mit Übersicht aller Screen-Status.

  • Automatisches Refresh alle 15 Sekunden
  • Shortcut-Links für Filter
  • Filterformular (wie JSON-Read-Pfad)
  • Direkte JSON-Detail-Links pro Screen

Query-Parameter: Dieselben wie GET /api/v1/screens/status

  • q — Screen-ID Substring-Suche
  • derived_stateonline, degraded, offline
  • server_connectivityonline, degraded, offline, unknown
  • staletrue, false
  • updated_since — RFC3339-Zeitstempel
  • limit — positive Ganzzahl

GET /status/{screenId}

HTML-Detailseite für einen einzelnen Screen.

Zeigt:

  • Screen-Information
  • Derived State
  • Player Status
  • Connectivity
  • Freshness und Timestamps
  • Endpunkte und Links

Player Local UI (Agent)

GET /player

Zeigt die lokale Player-UI (HTML) auf dem Gerät.

Rückgabe: HTML-Seite mit:

  • Splash-Screen
  • Systeminformationen-Overlay
  • Verbindungsstatus-Punkt
  • Basis für Playlist-Anzeige

GET /api/now-playing

JSON-API des Player-Agents (lokal).

Response:

{
  "playlist": [
    {
      "src": "http://backend.local/api/v1/screens/info10/playlist",
      "type": "web",
      "title": "Startseite",
      "duration_seconds": 30
    }
  ],
  "status": "running",
  "connectivity": "online"
}

GET /api/sysinfo

Systeminformationen des Player-Agents (lokal).

Response:

{
  "items": [
    {
      "label": "Hostname",
      "value": "infoboard-01"
    },
    {
      "label": "Uptime",
      "value": "5d 2h 30m"
    }
  ]
}

Fehlerbehandlung

Alle JSON-Responses folgen diesem Fehlermodell (falls implementiert):

{
  "error": {
    "code": "error_code_here",
    "message": "Human-readable error message",
    "details": null
  }
}

Typische HTTP-Status:

  • 200 OK — Erfolgreiche Anfrage (Read)
  • 201 Created — Ressource erstellt
  • 204 No Content — Erfolgreiche Anfrage ohne Response-Body
  • 400 Bad Request — Eingabe-Validierung fehlgeschlagen
  • 404 Not Found — Ressource nicht vorhanden
  • 500 Internal Server Error — Server-Fehler

Screenshot API

POST /api/v1/player/screenshot

Vom Player-Agent aufgerufener Endpoint zum Hochladen eines Screenshots.

Auth: Keine.

Request: multipart/form-data, max. 3 MB.

Feld Typ Pflicht Beschreibung
screen_id string ja Interne Screen-ID (entspricht dem Slug des Players)
screenshot Datei ja Screenshot-Datei (JPEG oder PNG)

Der MIME-Typ wird aus dem Content-Type-Header des Datei-Parts übernommen. Fehlt er, wird image/png angenommen.

Der Screenshot wird im In-Memory-ScreenshotStore gespeichert (nicht persistiert, kein Filesystem-Zugriff).

Response:

  • 200 OK — Screenshot gespeichert (kein Body)
  • 400 Bad Requestscreen_id fehlt, screenshot-Feld fehlt, oder Multipart-Parsing fehlgeschlagen
  • 500 Internal Server Error — Lesefehler

GET /api/v1/screens/{screenId}/screenshot

Ruft den zuletzt hochgeladenen Screenshot eines Screens ab.

Auth: RequireAuth (eingeloggter Benutzer).

Path-Parameter:

  • screenId — Screen-ID (wie beim Upload übergeben)

Response:

  • 200 OK — Raw-Image-Daten mit korrektem Content-Type (z. B. image/jpeg), Cache-Control: no-store
  • 404 Not Found — kein Screenshot für diese Screen-ID vorhanden

Änderungshistorie

  • 2026-03-24 (Update): MQTT-Konfiguration in POST /api/v1/player/status Response dokumentiert (Doris / Doku-Review)
    • Response enthält jetzt optionales mqtt-Objekt mit broker, username, password (alle omitempty wenn leer)
    • Feld wird nur gesendet wenn MORZ_INFOBOARD_MQTT_BROKER konfiguriert ist
    • Agent übernimmt die Konfiguration und reconnectet MQTT bei Änderung
  • 2026-03-24 (Update): Screenshot-Endpoints implementiert und dokumentiert (Doris / Doku-Review)
    • POST /api/v1/player/screenshot — war als "In Vorbereitung" markiert, ist jetzt vollständig implementiert; Abschnitt komplett neu verfasst
    • GET /api/v1/screens/{screenId}/screenshot — neuer Endpoint, authOnly, liefert Raw-Image aus In-Memory-Store
    • GET /manage — neue Übersichtsseite für screen_user mit mehreren Screens, authOnly
  • 2026-03-24 (Update): CSRF-Pflichtfelder in POST /login und POST /logout dokumentiert (Doris / Doku-Review)
    • POST /login und POST /logout erfordern csrf_token als Hidden-Field (Double-Submit-Cookie-Pattern)
    • Hinweis auf morz_csrf-Cookie und {{.CSRFToken}}-Template-Variable ergaenzt
  • 2026-03-24 (Update): Root-Redirect dokumentiert (Doris / Doku-Review)
    • GET / — Redirect 303 auf /login, 404-Guard für unbekannte Pfade
  • 2026-03-23 (Update): Screen-User Management Endpoints (Doris / Doku-Review)
    • POST /admin/users — Screen-User anlegen
    • POST /admin/users/{userID}/delete — Screen-User löschen
    • POST /admin/screens/{screenID}/users — User zu Screen hinzufügen
    • POST /admin/screens/{screenID}/users/{userID}/remove — User von Screen entfernen
  • 2026-03-23 (Update): Security-Enhancements und Upload-Konsolidierung (Doris / Doku-Review)
    • CSRF-Schutz (Double-Submit-Cookie) in internal/httpapi/csrf.go
    • Rate-Limiting für /login in internal/httpapi/ratelimit.go
    • Upload-Logik konsolidiert in internal/fileutil/fileutil.go und internal/httpapi/uploads.go
    • Neue Env-Variable MORZ_INFOBOARD_REGISTER_SECRET dokumentiert
    • Screenshot-Modul im Agent vorbereitet mit MORZ_INFOBOARD_SCREENSHOT_EVERY
  • 2026-03-23 (Update): Auth- und Tenant-Dashboard-Endpoints ergaenzt (Doris / Doku-Review)
    • GET /login, POST /login, POST /logout dokumentiert
    • GET /tenant/{tenantSlug}/dashboard dokumentiert
    • POST /tenant/{tenantSlug}/upload dokumentiert
    • POST /tenant/{tenantSlug}/media/{mediaId}/delete dokumentiert
  • 2026-03-23: Initiale Dokumentation aller HTTP-Endpoints basierend auf Code-Review
    • Alle Screen-Management-Endpoints dokumentiert
    • Alle Playlist-Management-Endpoints dokumentiert
    • Alle Media-Management-Endpoints dokumentiert
    • Admin-UI und Manage-UI-Endpoints dokumentiert
    • Player-Status-Diagnostik dokumentiert
    • Player-Local-API dokumentiert