- DB-Package mit pgxpool, Migrations-Runner und eingebetteten SQL-Dateien
- Schema: tenants, screens, media_assets, playlists, playlist_items
- Store-Layer: alle Repositories (TenantStore, ScreenStore, MediaStore, PlaylistStore)
- JSON-API: Screens, Medien, Playlist-CRUD, Player-Sync-Endpunkt
- Admin-UI (/admin): Screens anlegen, löschen, zur Playlist navigieren
- Playlist-UI (/manage/{slug}): Drag&Drop-Sortierung, Item-Bearbeitung,
Medienbibliothek, Datei-Upload (Bild/Video/PDF) und Web-URL
- Router auf RouterDeps umgestellt; manage-Routen nur wenn Stores vorhanden
- parseOptionalTime akzeptiert nun RFC3339 und datetime-local HTML-Format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
3.2 KiB
SQL
76 lines
3.2 KiB
SQL
-- 001_initial.sql
|
|
-- Basis-Schema: Tenants, Screens, Medien, Playlists
|
|
|
|
create extension if not exists pgcrypto;
|
|
|
|
create table if not exists tenants (
|
|
id text primary key default gen_random_uuid()::text,
|
|
slug text not null unique,
|
|
name text not null,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists screens (
|
|
id text primary key default gen_random_uuid()::text,
|
|
tenant_id text not null references tenants(id) on delete cascade,
|
|
slug text not null unique,
|
|
name text not null,
|
|
orientation text not null default 'landscape',
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists media_assets (
|
|
id text primary key default gen_random_uuid()::text,
|
|
tenant_id text not null references tenants(id) on delete cascade,
|
|
title text not null,
|
|
type text not null, -- image | video | pdf | web
|
|
storage_path text null, -- set for uploads
|
|
original_url text null, -- set for web/remote
|
|
mime_type text null,
|
|
size_bytes bigint null,
|
|
enabled boolean not null default true,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists playlists (
|
|
id text primary key default gen_random_uuid()::text,
|
|
tenant_id text not null references tenants(id) on delete cascade,
|
|
screen_id text 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,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (screen_id) -- one active playlist per screen (simplified)
|
|
);
|
|
|
|
create table if not exists playlist_items (
|
|
id text primary key default gen_random_uuid()::text,
|
|
playlist_id text not null references playlists(id) on delete cascade,
|
|
media_asset_id text null references media_assets(id) on delete set null,
|
|
order_index integer not null default 0,
|
|
type text not null, -- image | video | pdf | web
|
|
src text not null, -- URL or served path
|
|
title text null,
|
|
duration_seconds integer not null default 20,
|
|
valid_from timestamptz null,
|
|
valid_until timestamptz null,
|
|
enabled boolean not null default true,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists idx_screens_tenant_id on screens(tenant_id);
|
|
create index if not exists idx_media_assets_tenant_id on media_assets(tenant_id);
|
|
create index if not exists idx_playlists_screen_id on playlists(screen_id);
|
|
create index if not exists idx_playlist_items_order on playlist_items(playlist_id, order_index);
|
|
|
|
-- Schema-Versions-Tabelle
|
|
create table if not exists schema_migrations (
|
|
version integer primary key,
|
|
applied_at timestamptz not null default now()
|
|
);
|
|
|
|
-- Seed: Standard-Tenant und erster Screen (idempotent)
|
|
insert into tenants (id, slug, name)
|
|
values ('tenant-morz', 'morz', 'MORZ Schule')
|
|
on conflict (slug) do nothing;
|