Forme das Datenmodell als Schemaentwurf aus
This commit is contained in:
parent
3dc82bf4f7
commit
ae183d399e
2 changed files with 542 additions and 0 deletions
|
|
@ -13,6 +13,7 @@ Die Trennung von `/srv/docker/infoboard-netboot` ist sinnvoll, damit:
|
|||
- Architekturplan: `PLAN.md`
|
||||
- Umsetzungs-Todo: `TODO.md`
|
||||
- Datenmodell: `DATENMODELL.md`
|
||||
- Schema-Entwurf: `docs/SCHEMA.md`
|
||||
- API- und MQTT-Vertrag: `API-MQTT-VERTRAG.md`
|
||||
- Technologieentscheidungen: `TECH-STACK.md`
|
||||
- Template-/Kampagnenkonzept: `docs/TEMPLATE-KONZEPT.md`
|
||||
|
|
|
|||
541
docs/SCHEMA.md
Normal file
541
docs/SCHEMA.md
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
# Info-Board Neu - Schema-Entwurf
|
||||
|
||||
## Ziel
|
||||
|
||||
Dieses Dokument bringt das fachliche Datenmodell in eine konkrete, relationale Form.
|
||||
|
||||
Es ist noch kein finales Migrationsskript, aber bereits so strukturiert, dass daraus spaeter direkt PostgreSQL-Tabellen und Migrationen abgeleitet werden koennen.
|
||||
|
||||
## Grundannahmen
|
||||
|
||||
- Ziel-Datenbank: PostgreSQL
|
||||
- IDs als UUID oder technisch gleichwertige stabile Primärschluessel
|
||||
- Zeitstempel in UTC
|
||||
- Status- und Typwerte zunaechst als textuelle Enum-Werte oder PostgreSQL-Enums
|
||||
|
||||
## Konventionen
|
||||
|
||||
- Primärschluessel: `id`
|
||||
- Fremdschluessel: `<bezug>_id`
|
||||
- Zeitstempel: `created_at`, `updated_at`
|
||||
- nullable Felder nur dort, wo fachlich wirklich noetig
|
||||
|
||||
## Zentrale Tabellen
|
||||
|
||||
### `tenants`
|
||||
|
||||
Zweck:
|
||||
|
||||
- abgeschottete Firmen- oder Inhaltsbereiche
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
slug text not null unique
|
||||
name text not null
|
||||
active boolean not null default true
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
### `users`
|
||||
|
||||
Zweck:
|
||||
|
||||
- Admin- und Tenant-Benutzer
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
tenant_id uuid null references tenants(id) on delete set null
|
||||
username text not null unique
|
||||
email text not null unique
|
||||
password_hash text not null
|
||||
role text not null
|
||||
active boolean not null default true
|
||||
last_login_at timestamptz null
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
Regeln:
|
||||
|
||||
- `role` in v1: `admin`, `tenant_user`
|
||||
|
||||
### `screen_groups`
|
||||
|
||||
Zweck:
|
||||
|
||||
- logische Gruppen wie `wall-all` oder `vertretungsplan-all`
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
slug text not null unique
|
||||
name text not null
|
||||
description text null
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
### `screens`
|
||||
|
||||
Zweck:
|
||||
|
||||
- physische Displays bzw. Player-Geraete
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
tenant_id uuid null references tenants(id) on delete set null
|
||||
slug text not null unique
|
||||
name text not null
|
||||
description text null
|
||||
location text null
|
||||
hardware_name text null
|
||||
screen_class text not null
|
||||
enabled boolean not null default true
|
||||
orientation text not null
|
||||
rotation integer not null default 0
|
||||
resolution_width integer null
|
||||
resolution_height integer null
|
||||
fallback_dir text not null
|
||||
snapshot_interval_seconds integer not null default 60
|
||||
offline_overlay_enabled boolean not null default true
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
Regeln:
|
||||
|
||||
- `orientation` in v1: `portrait`, `landscape`
|
||||
- `rotation` in v1: `0`, `90`, `180`, `270`
|
||||
- `screen_class` in v1 z. B. `info_wall_display`, `single_info_display`, `vertretungsplan_display`
|
||||
|
||||
### `screen_group_members`
|
||||
|
||||
Zweck:
|
||||
|
||||
- Zuordnung von Screens zu Gruppen
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
screen_group_id uuid not null references screen_groups(id) on delete cascade
|
||||
screen_id uuid not null references screens(id) on delete cascade
|
||||
created_at timestamptz not null
|
||||
```
|
||||
|
||||
Unique:
|
||||
|
||||
```sql
|
||||
unique (screen_group_id, screen_id)
|
||||
```
|
||||
|
||||
### `screen_registrations`
|
||||
|
||||
Zweck:
|
||||
|
||||
- technische Registrierung und Wiedererkennung eines Geraets
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
screen_id uuid not null unique references screens(id) on delete cascade
|
||||
device_uuid text not null unique
|
||||
hostname text null
|
||||
api_token_hash text not null
|
||||
mqtt_client_id text not null unique
|
||||
last_seen_at timestamptz null
|
||||
last_ip inet null
|
||||
player_version text null
|
||||
os_version text null
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
### `provisioning_jobs`
|
||||
|
||||
Zweck:
|
||||
|
||||
- technische Erstinstallation und Re-Provisionierung von Screens
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
screen_id uuid not null references screens(id) on delete cascade
|
||||
requested_by_user_id uuid not null references users(id) on delete restrict
|
||||
target_ip inet not null
|
||||
target_port integer not null default 22
|
||||
remote_user text not null default 'root'
|
||||
auth_mode text not null
|
||||
provided_secret_ref text null
|
||||
ssh_key_fingerprint text null
|
||||
status text not null
|
||||
stage text not null
|
||||
log_excerpt text null
|
||||
error_message text null
|
||||
started_at timestamptz null
|
||||
finished_at timestamptz null
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
Regeln:
|
||||
|
||||
- Passwort nicht direkt in dieser Tabelle speichern
|
||||
- `status` in v1: `queued`, `running`, `succeeded`, `failed`, `cancelled`
|
||||
|
||||
### `media_assets`
|
||||
|
||||
Zweck:
|
||||
|
||||
- verwaltete Medien und Web-Referenzen
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
tenant_id uuid not null references tenants(id) on delete cascade
|
||||
screen_id uuid null references screens(id) on delete set null
|
||||
title text not null
|
||||
description text null
|
||||
type text not null
|
||||
source_kind text not null
|
||||
storage_path text null
|
||||
original_url text null
|
||||
mime_type text null
|
||||
checksum text null
|
||||
size_bytes bigint null
|
||||
enabled boolean not null default true
|
||||
created_by_user_id uuid not null references users(id) on delete restrict
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
Regeln:
|
||||
|
||||
- `type` in v1: `image`, `video`, `pdf`, `web`
|
||||
- `source_kind` in v1: `upload`, `remote_url`
|
||||
|
||||
### `playlists`
|
||||
|
||||
Zweck:
|
||||
|
||||
- tenantbezogene Hauptplaylist eines Screens
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
tenant_id uuid not null references tenants(id) on delete cascade
|
||||
screen_id uuid not null references screens(id) on delete cascade
|
||||
name text not null
|
||||
is_active boolean not null default true
|
||||
default_duration_seconds integer not null default 20
|
||||
fallback_enabled boolean not null default true
|
||||
fallback_dir text not null
|
||||
shuffle_enabled boolean not null default false
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
Unique:
|
||||
|
||||
```sql
|
||||
unique (screen_id, is_active) where is_active = true
|
||||
```
|
||||
|
||||
### `playlist_items`
|
||||
|
||||
Zweck:
|
||||
|
||||
- Elemente einer tenantbezogenen Playlist
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
playlist_id uuid not null references playlists(id) on delete cascade
|
||||
screen_id uuid not null references screens(id) on delete cascade
|
||||
media_asset_id uuid null references media_assets(id) on delete set null
|
||||
order_index integer not null
|
||||
type text not null
|
||||
src text not null
|
||||
title text null
|
||||
duration_seconds integer not null
|
||||
load_timeout_seconds integer not null default 15
|
||||
cache_policy text not null default 'prefer_cache'
|
||||
on_error text not null default 'skip'
|
||||
retry_count integer not null default 0
|
||||
valid_from timestamptz null
|
||||
valid_until timestamptz null
|
||||
enabled boolean not null default true
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
### `playlist_item_dir_rules`
|
||||
|
||||
Zweck:
|
||||
|
||||
- Zusatzregeln fuer `dir`-Items
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
playlist_item_id uuid not null unique references playlist_items(id) on delete cascade
|
||||
directory_path text not null
|
||||
sort_mode text not null default 'name_asc'
|
||||
per_item_duration_seconds integer not null default 20
|
||||
recursive boolean not null default false
|
||||
file_filter text null
|
||||
```
|
||||
|
||||
## Kampagnen- und Template-Tabellen
|
||||
|
||||
### `display_templates`
|
||||
|
||||
Zweck:
|
||||
|
||||
- globale Templates fuer adminseitige Uebersteuerungen
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
slug text not null unique
|
||||
name text not null
|
||||
description text null
|
||||
template_type text not null
|
||||
enabled boolean not null default true
|
||||
created_by_user_id uuid not null references users(id) on delete restrict
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
### `template_scenes`
|
||||
|
||||
Zweck:
|
||||
|
||||
- konkrete Szenen eines Templates fuer Screens, Gruppen oder Slots
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
display_template_id uuid not null references display_templates(id) on delete cascade
|
||||
screen_id uuid null references screens(id) on delete cascade
|
||||
screen_group_id uuid null references screen_groups(id) on delete cascade
|
||||
screen_slot text null
|
||||
orientation text null
|
||||
type text not null
|
||||
src text not null
|
||||
duration_seconds integer not null default 20
|
||||
load_timeout_seconds integer not null default 15
|
||||
cache_policy text not null default 'prefer_cache'
|
||||
on_error text not null default 'skip'
|
||||
layout_json jsonb not null default '{}'::jsonb
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
Hinweis:
|
||||
|
||||
- `orientation` erlaubt z. B. getrennte Portrait-/Landscape-Szenen innerhalb einer Kampagne
|
||||
|
||||
### `campaigns`
|
||||
|
||||
Zweck:
|
||||
|
||||
- aktivierbare Instanzen globaler Templates
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
display_template_id uuid not null references display_templates(id) on delete cascade
|
||||
name text not null
|
||||
description text null
|
||||
priority integer not null default 100
|
||||
active boolean not null default false
|
||||
valid_from timestamptz null
|
||||
valid_until timestamptz null
|
||||
override_mode text not null default 'replace_tenant_content'
|
||||
created_by_user_id uuid not null references users(id) on delete restrict
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
### `template_assignments`
|
||||
|
||||
Zweck:
|
||||
|
||||
- explizite Zielzuordnung einer Kampagne
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
campaign_id uuid not null references campaigns(id) on delete cascade
|
||||
screen_id uuid not null references screens(id) on delete cascade
|
||||
enabled boolean not null default true
|
||||
created_at timestamptz not null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
Unique:
|
||||
|
||||
```sql
|
||||
unique (campaign_id, screen_id)
|
||||
```
|
||||
|
||||
## Laufzeit- und Betriebsdaten
|
||||
|
||||
### `screen_status`
|
||||
|
||||
Zweck:
|
||||
|
||||
- aktueller verdichteter Laufzeitstatus pro Screen
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
screen_id uuid primary key references screens(id) on delete cascade
|
||||
online boolean not null default false
|
||||
server_connected boolean not null default false
|
||||
mqtt_connected boolean not null default false
|
||||
last_heartbeat_at timestamptz null
|
||||
last_sync_at timestamptz null
|
||||
current_playlist_id uuid null references playlists(id) on delete set null
|
||||
current_playlist_item_id uuid null references playlist_items(id) on delete set null
|
||||
current_content_source text null
|
||||
current_campaign_id uuid null references campaigns(id) on delete set null
|
||||
current_item_type text null
|
||||
current_item_label text null
|
||||
current_item_started_at timestamptz null
|
||||
current_item_duration_seconds integer null
|
||||
cache_state text not null default 'ok'
|
||||
overlay_state text not null default 'online'
|
||||
error_code text null
|
||||
error_message text null
|
||||
player_version text null
|
||||
uptime_seconds bigint null
|
||||
free_disk_bytes bigint null
|
||||
temperature_celsius numeric(5,2) null
|
||||
updated_at timestamptz not null
|
||||
```
|
||||
|
||||
### `screen_snapshots`
|
||||
|
||||
Zweck:
|
||||
|
||||
- Screenshots fuer Vorschau und Diagnose
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
screen_id uuid not null references screens(id) on delete cascade
|
||||
captured_at timestamptz not null
|
||||
storage_path text not null
|
||||
width integer null
|
||||
height integer null
|
||||
mime_type text not null
|
||||
source text not null
|
||||
```
|
||||
|
||||
### `device_commands`
|
||||
|
||||
Zweck:
|
||||
|
||||
- nachvollziehbare Fernsteuerbefehle an Screens
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
id uuid primary key
|
||||
screen_id uuid not null references screens(id) on delete cascade
|
||||
command_type text not null
|
||||
payload_json jsonb not null default '{}'::jsonb
|
||||
requested_by_user_id uuid not null references users(id) on delete restrict
|
||||
requested_at timestamptz not null
|
||||
delivery_state text not null
|
||||
delivered_at timestamptz null
|
||||
acknowledged_at timestamptz null
|
||||
result_code text null
|
||||
result_message text null
|
||||
```
|
||||
|
||||
### `sync_state`
|
||||
|
||||
Zweck:
|
||||
|
||||
- letzter bekannter Synchronisationsstand pro Screen
|
||||
|
||||
Spalten:
|
||||
|
||||
```sql
|
||||
screen_id uuid primary key references screens(id) on delete cascade
|
||||
config_revision bigint not null default 0
|
||||
playlist_revision bigint not null default 0
|
||||
media_revision bigint not null default 0
|
||||
campaign_revision bigint not null default 0
|
||||
last_successful_sync_at timestamptz null
|
||||
last_failed_sync_at timestamptz null
|
||||
last_error_message text null
|
||||
```
|
||||
|
||||
## Wichtige Indizes
|
||||
|
||||
Empfohlen mindestens:
|
||||
|
||||
```sql
|
||||
create index idx_screens_tenant_id on screens(tenant_id);
|
||||
create index idx_media_assets_tenant_id on media_assets(tenant_id);
|
||||
create index idx_playlists_screen_id on playlists(screen_id);
|
||||
create index idx_playlist_items_playlist_id_order on playlist_items(playlist_id, order_index);
|
||||
create index idx_campaigns_active_validity on campaigns(active, valid_from, valid_until);
|
||||
create index idx_template_assignments_screen_id on template_assignments(screen_id);
|
||||
create index idx_provisioning_jobs_screen_id on provisioning_jobs(screen_id);
|
||||
create index idx_provisioning_jobs_status on provisioning_jobs(status);
|
||||
create index idx_screen_snapshots_screen_id_captured_at on screen_snapshots(screen_id, captured_at desc);
|
||||
create index idx_device_commands_screen_id_requested_at on device_commands(screen_id, requested_at desc);
|
||||
```
|
||||
|
||||
## Prioritaetslogik in relationaler Form
|
||||
|
||||
Ein Screen zeigt in dieser Reihenfolge:
|
||||
|
||||
1. aktive Kampagne mit passender Assignment-Zuordnung
|
||||
2. aktive tenantbezogene Playlist-Eintraege
|
||||
3. Fallback-Verzeichnis
|
||||
|
||||
Eine Kampagne ist aktiv, wenn:
|
||||
|
||||
- `campaigns.active = true`
|
||||
- `valid_from is null or valid_from <= now()`
|
||||
- `valid_until is null or valid_until > now()`
|
||||
- `template_assignments.enabled = true`
|
||||
|
||||
Ein Playlist-Item ist aktiv, wenn:
|
||||
|
||||
- `enabled = true`
|
||||
- `valid_from is null or valid_from <= now()`
|
||||
- `valid_until is null or valid_until > now()`
|
||||
|
||||
## Offene spaetere Erweiterungen
|
||||
|
||||
- Historientabellen fuer Heartbeats und Status als Zeitreihe
|
||||
- Versionierung von Playlists und Templates
|
||||
- Medienkonvertierung oder serverseitige Derivate
|
||||
- mehrere aktive Kampagnen mit Kollisionsregeln
|
||||
- Slot-Topologien fuer unterschiedlich grosse Wandsysteme
|
||||
Loading…
Add table
Reference in a new issue