README.md, db/migrations/0001_init.sql, lib/auth/webauthn-config.ts, lib/branding.ts nannten Supabase noch in Kommentaren, die erklären, was diese Stellen ersetzen. Umformuliert, ohne den alten Anbieter beim Namen zu nennen. CHANGELOG.md bleibt unverändert – die Erwähnungen dort sind historischer Bestand (u. a. der v1.5.0-Eintrag über die Supabase-Ablösung selbst) und sollen als Zeitdokument stehen bleiben.
157 lines
5.1 KiB
PL/PgSQL
157 lines
5.1 KiB
PL/PgSQL
-- =============================================================
|
||
-- Canteen Menu – Initiales Schema (vollständig selbst gehostet)
|
||
-- 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 (Passkeys) – eigenes Schema, wird von
|
||
-- @simplewebauthn/server geprüft (lib/auth/).
|
||
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()
|
||
);
|