-- ============================================================= -- Canteen Menu – Initiales Schema (selbst gehostet, ohne Supabase) -- Rollen: admin (Benutzerverwaltung, Branding) / staff (Kantine) -- Öffentliche Speisekarte: lesbar ohne Login -- ============================================================= -- ---------- Benutzer & Authentifizierung ---------- create table users ( id uuid primary key default gen_random_uuid(), email text not null unique, password_hash text not null, full_name text not null default '', role text not null default 'staff' check (role in ('admin', 'staff')), password_auth_enabled boolean not null default true, created_at timestamptz not null default now() ); -- Angemeldete Sitzungen. Es wird nur der Hash des Tokens gespeichert – -- der Rohwert im Cookie ist damit auch bei einem DB-Zugriff allein nicht -- verwendbar. create table sessions ( token_hash text primary key, user_id uuid not null references users (id) on delete cascade, created_at timestamptz not null default now(), expires_at timestamptz not null, last_seen_at timestamptz not null default now() ); create index sessions_user_id_idx on sessions (user_id); -- WebAuthn-Credentials (ersetzt Supabase's internes, für die App bisher -- unsichtbares Passkey-Schema). create table passkey_credentials ( id uuid primary key default gen_random_uuid(), user_id uuid not null references users (id) on delete cascade, credential_id text not null unique, public_key bytea not null, counter bigint not null default 0, transports text[] not null default '{}', name text, created_at timestamptz not null default now(), last_used_at timestamptz ); create index passkey_credentials_user_id_idx on passkey_credentials (user_id); -- Kurzlebige Challenges für laufende WebAuthn-Zeremonien (Registrierung -- oder Login). user_id ist beim Login-Flow anfangs null (Discoverable- -- Credential-Ansatz: die Identität steht erst nach der Antwort fest). create table webauthn_challenges ( id uuid primary key default gen_random_uuid(), user_id uuid references users (id) on delete cascade, challenge text not null, type text not null check (type in ('registration', 'authentication')), expires_at timestamptz not null, created_at timestamptz not null default now() ); -- ---------- Essenszeiten der Speisekarte ---------- create table sections ( key text primary key default (gen_random_uuid())::text, label text not null, label_en text, label_pl text, time_range text not null, sort int not null ); -- ---------- Allergene & Zusatzstoffe ---------- create table allergens ( code text primary key, name text not null, name_en text, name_pl text, sort int not null default 0 ); create table additives ( code text primary key, name text not null, name_en text, name_pl text, sort int not null default 0 ); -- ---------- Gerichte ---------- create table dishes ( id uuid primary key default gen_random_uuid(), name text not null, name_en text, name_pl text, price numeric(6, 2) not null default 0, allergens text[] not null default '{}', additives text[] not null default '{}', created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create or replace function touch_updated_at() returns trigger language plpgsql as $$ begin new.updated_at := now(); return new; end; $$; create trigger dishes_touch_updated_at before update on dishes for each row execute function touch_updated_at(); -- ---------- Zuweisung Gericht -> Tag/Essenszeit ---------- create table menu_entries ( id uuid primary key default gen_random_uuid(), menu_date date not null, section_key text not null references sections (key) on delete cascade, dish_id uuid not null references dishes (id) on delete cascade, position int not null default 0, created_at timestamptz not null default now(), unique (menu_date, section_key, dish_id) ); create index menu_entries_date_idx on menu_entries (menu_date); -- ---------- Einstellungen (Farben, Logo, Favicon) – genau eine Zeile ---------- create table settings ( id int primary key check (id = 1), colors jsonb not null default '{}'::jsonb, logo_path text, favicon_path text, updated_at timestamptz not null default now() ); insert into settings (id, colors) values ( 1, '{ "primary": "#003388", "primaryDark": "#0d315c", "accent": "#ff6900", "headerText": "#ffffff", "rowBg": "#e3e4f1", "rowBgAlt": "#f4f4fa", "addBtn": "#6da54c", "deleteBtn": "#e2371d" }'::jsonb ); create trigger settings_touch_updated_at before update on settings for each row execute function touch_updated_at(); -- ---------- Rezeptvorschläge ---------- create table dish_suggestions ( id uuid primary key default gen_random_uuid(), name text not null, description text, created_at timestamptz not null default now() );