morz-infoboard/DATENMODELL.md
2026-03-22 12:46:34 +01:00

391 lines
8.5 KiB
Markdown

# Info-Board Neu - Datenmodell
## Ziel
Das Datenmodell trennt klar zwischen:
- Mandanten und Benutzern
- Bildschirmen und ihrer technischen Laufzeitinformation
- Medien und Playlists
- Steuerbefehlen und Rueckmeldungen
Das Modell ist so ausgelegt, dass:
- jede Firma nur ihren eigenen Monitor bzw. Kanal pflegt
- die Administration alle Monitore zentral sehen und steuern kann
- Offline-Betrieb der Player moeglich bleibt
- Zeitsteuerung mit `valid_from` und `valid_until` sauber abbildbar ist
## Kernentitaeten
### `tenant`
Repraesentiert eine Firma bzw. einen abgeschotteten Inhaltsbereich.
Felder:
- `id`
- `slug`
- `name`
- `active`
- `created_at`
- `updated_at`
Hinweise:
- ein Tenant kann spaeter mehrere Screens haben, auch wenn zunaechst meist 1:1 gearbeitet wird
- alle Medien und Playlists sind einem Tenant zugeordnet
### `user`
Repraesentiert einen Benutzer der Firmen- oder Admin-Oberflaeche.
Felder:
- `id`
- `tenant_id` nullable fuer globale Admins
- `username`
- `email`
- `password_hash`
- `role` (`admin`, `tenant_user`)
- `active`
- `last_login_at`
- `created_at`
- `updated_at`
Regeln:
- `tenant_user` darf nur Daten des eigenen Tenants sehen und bearbeiten
- `admin` darf alle Tenants und alle Screens verwalten
### `screen`
Repraesentiert einen physischen Monitor bzw. einen Player-Client.
Felder:
- `id`
- `tenant_id`
- `slug`
- `name`
- `description`
- `location`
- `hardware_name`
- `enabled`
- `rotation` (`0`, `90`, `180`, `270`)
- `resolution_width`
- `resolution_height`
- `fallback_dir`
- `snapshot_interval_seconds`
- `offline_overlay_enabled`
- `created_at`
- `updated_at`
Hinweise:
- der `slug` dient als technische Kennung fuer Konfiguration, API und MQTT
- `fallback_dir` beschreibt die lokale oder synchronisierte Fallback-Quelle auf dem Client
### `screen_registration`
Optionale Trennung zwischen fachlichem Screen und technischer Registrierung.
Felder:
- `id`
- `screen_id`
- `device_uuid`
- `hostname`
- `api_token_hash`
- `mqtt_client_id`
- `last_seen_at`
- `last_ip`
- `player_version`
- `os_version`
- `created_at`
- `updated_at`
Zweck:
- Wiedererkennung eines konkreten Geraets
- sichere Kommunikation mit API und Broker
- technische Inventarisierung
### `media_asset`
Repraesentiert ein verwaltetes Medium.
Felder:
- `id`
- `tenant_id`
- `screen_id` nullable
- `title`
- `description`
- `type` (`image`, `video`, `pdf`, `web`)
- `source_kind` (`upload`, `remote_url`)
- `storage_path` nullable bei reinen Web-URLs
- `original_url` nullable
- `mime_type`
- `checksum`
- `size_bytes`
- `enabled`
- `created_by_user_id`
- `created_at`
- `updated_at`
Regeln:
- `upload` bedeutet: Datei liegt im Medien-Storage
- `remote_url` bedeutet: Quelle ist extern und wird vom Player oder Server gecacht
- `screen_id` kann optional gesetzt werden, wenn ein Medium nur fuer einen Monitor gedacht ist
### `playlist`
Repraesentiert eine logische Playlist eines Screens.
Felder:
- `id`
- `tenant_id`
- `screen_id`
- `name`
- `is_active`
- `default_duration_seconds`
- `fallback_enabled`
- `fallback_dir`
- `shuffle_enabled`
- `created_at`
- `updated_at`
Regeln:
- pro Screen gibt es in v1 genau eine aktive Playlist
- spaeter koennen mehrere Playlists mit Umschaltung moeglich werden
### `playlist_item`
Repraesentiert einen einzelnen Eintrag in einer Playlist.
Felder:
- `id`
- `playlist_id`
- `screen_id`
- `order_index`
- `media_asset_id` nullable
- `type` (`image`, `video`, `pdf`, `web`, `dir`)
- `src`
- `title`
- `duration_seconds`
- `load_timeout_seconds`
- `cache_policy` (`required`, `prefer_cache`, `no_cache`)
- `on_error` (`skip`, `retry`, `fallback`)
- `retry_count`
- `valid_from` nullable
- `valid_until` nullable
- `enabled`
- `created_at`
- `updated_at`
Regeln:
- `media_asset_id` wird bei verwalteten Medien gesetzt
- `src` enthaelt den effektiven Pfad oder die URL, damit der Player auch ohne Join-Struktur arbeiten kann
- `type=dir` erlaubt definierte Verzeichnisreferenzen innerhalb der Playlist
### `playlist_item_dir_rule`
Optionale Zusatzregel fuer `type=dir`.
Felder:
- `id`
- `playlist_item_id`
- `directory_path`
- `sort_mode` (`name_asc`, `name_desc`, `mtime_asc`, `mtime_desc`, `shuffle`)
- `per_item_duration_seconds`
- `recursive`
- `file_filter` nullable
Zweck:
- sauberere Modellierung fuer Verzeichnisbasierte Fallbacks oder Einschuebe
### `screen_status`
Repraesentiert den letzten bekannten Laufzeitstatus eines Players.
Felder:
- `screen_id`
- `online`
- `server_connected`
- `mqtt_connected`
- `last_heartbeat_at`
- `last_sync_at`
- `current_playlist_id`
- `current_playlist_item_id` nullable
- `current_item_type` nullable
- `current_item_label` nullable
- `current_item_started_at` nullable
- `current_item_duration_seconds` nullable
- `cache_state` (`ok`, `stale`, `missing`, `error`)
- `overlay_state` (`online`, `degraded`, `offline`)
- `error_code` nullable
- `error_message` nullable
- `player_version`
- `uptime_seconds`
- `free_disk_bytes` nullable
- `temperature_celsius` nullable
- `updated_at`
Hinweise:
- `screen_status` ist eine verdichtete Sicht fuer Dashboard und Ueberwachung
- historische Verlaeufe koennen spaeter separat gespeichert werden
### `screen_snapshot`
Repraesentiert eine Vorschauaufnahme des aktuellen Bildschirminhalts.
Felder:
- `id`
- `screen_id`
- `captured_at`
- `storage_path`
- `width`
- `height`
- `mime_type`
- `source` (`scheduled`, `item_change`, `manual`)
Regeln:
- in der UI wird typischerweise nur der letzte Snapshot angezeigt
- aeltere Snapshots koennen nach Zeit oder Anzahl aufgeraeumt werden
### `device_command`
Repraesentiert einen an einen Screen gesendeten Befehl.
Felder:
- `id`
- `screen_id`
- `command_type` (`reload`, `restart_player`, `reboot`, `display_on`, `display_off`, `refresh_snapshot`, `clear_cache`)
- `payload_json`
- `requested_by_user_id`
- `requested_at`
- `delivery_state` (`queued`, `sent`, `acknowledged`, `failed`, `expired`)
- `delivered_at` nullable
- `acknowledged_at` nullable
- `result_code` nullable
- `result_message` nullable
Zweck:
- nachvollziehbare Fernsteuerung mit Audit-Trail
### `sync_state`
Repraesentiert den Synchronisationsstand eines Screens.
Felder:
- `screen_id`
- `config_revision`
- `playlist_revision`
- `media_revision`
- `last_successful_sync_at`
- `last_failed_sync_at` nullable
- `last_error_message` nullable
Zweck:
- Erkennung, ob ein Player auf dem aktuellen Stand ist
## Beziehungen
- ein `tenant` hat viele `users`
- ein `tenant` hat viele `screens`
- ein `tenant` hat viele `media_assets`
- ein `screen` hat genau eine aktive `playlist` in v1
- eine `playlist` hat viele `playlist_items`
- ein `screen` hat genau einen aktuellen `screen_status`
- ein `screen` hat viele `screen_snapshots`
- ein `screen` hat viele `device_commands`
- ein `screen` hat genau einen aktuellen `sync_state`
## Zugriffsregeln
### Tenant-User
Ein `tenant_user` darf:
- nur den eigenen `tenant` sehen
- nur den eigenen `screen` bzw. die dem Tenant zugeordneten Screens sehen
- nur eigene Medien hochladen und pflegen
- nur eigene Playlists bearbeiten
- nur die Vorschau des eigenen Screens sehen
Ein `tenant_user` darf nicht:
- andere Tenants sehen
- globale Steuerkommandos senden
- andere Screens administrieren
### Admin
Ein `admin` darf:
- alle Tenants, Screens, Medien und Playlists sehen
- globale Steuerbefehle senden
- Vorschau und Status aller Screens sehen
- neue Screens und Benutzer anlegen
## Revisions- und Caching-Modell
Damit Offline-Betrieb einfach bleibt, arbeitet der Player nicht gegen beliebige Einzelobjekte, sondern gegen Revisionen.
Sinnvoll sind mindestens:
- `playlist_revision`
- `media_revision`
- `config_revision`
Der Player kann damit erkennen:
- ob neue Konfiguration vorliegt
- ob Medien nachgeladen werden muessen
- ob die lokale Kopie noch gueltig ist
## Beispiel fuer aktive Playlist-Auswertung
Ein `playlist_item` ist aktiv, wenn:
- `enabled = true`
- `valid_from` leer oder in der Vergangenheit liegt
- `valid_until` leer oder in der Zukunft liegt
Wenn kein aktives Item existiert:
- greift die globale Fallback-Regel des Screens oder der Playlist
## Beispiel fuer Admin-Steuerung
Wenn ein Admin `reload` ausloest:
1. Ein `device_command` wird gespeichert
2. der Befehl wird per MQTT an den Screen signalisiert
3. der Player fuehrt den Reload aus
4. der Player sendet ein ACK
5. `device_command.delivery_state` wird aktualisiert
## Offene spaetere Erweiterungen
- mehrere Screens pro Tenant in der Firmenoberflaeche
- Vorlagen und globale Playlists
- Historie der Statusdaten als Zeitreihe
- Geplante Kampagnen mit Prioritaeten
- serverseitige Konvertierung oder Transkodierung von Medien