Shuffle/supabase/migrations/20261102100200_catalog.sql
cschlaefke 317634dac5
Some checks are pending
CI / flutter (push) Waiting to run
CI / supabase (push) Waiting to run
CI / supabase-tests (push) Waiting to run
chore: initial project kit
2026-11-02 12:14:09 +01:00

259 lines
11 KiB
SQL

-- R1 Katalog: games, game_variants, Begriffe (Genre, Mechanik, Kategorie) und Zuordnungen.
-- Lesen ist auch ohne Anmeldung möglich (Entscheidung O6), Schreiben nie.
create table public.games (
id uuid primary key default gen_random_uuid(),
owner_id uuid references public.profiles (id) on delete cascade,
title text not null check (char_length(title) between 1 and 200),
description text check (char_length(description) <= 5000),
year smallint check (year between 1800 and 2100),
min_players smallint check (min_players >= 1),
max_players smallint check (max_players >= 1),
min_minutes smallint check (min_minutes >= 0),
max_minutes smallint check (max_minutes >= 0),
min_age smallint check (min_age >= 0),
image_path text,
bgg_id integer,
visibility text not null default 'private' check (visibility in ('catalog', 'private')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
-- Katalogspiele gehören niemandem, private Spiele immer einem Nutzer (O5).
constraint games_owner_matches_visibility check (
(visibility = 'catalog' and owner_id is null)
or (visibility = 'private' and owner_id is not null)
),
constraint games_players_range check (max_players is null or min_players is null or max_players >= min_players),
constraint games_minutes_range check (max_minutes is null or min_minutes is null or max_minutes >= min_minutes)
);
-- Die BGG-ID ist optional. Im Katalog einmalig; private Einträge mehrerer Nutzer dürfen sie teilen.
create unique index games_bgg_id_catalog_key on public.games (bgg_id)
where visibility = 'catalog' and bgg_id is not null;
create unique index games_bgg_id_private_key on public.games (owner_id, bgg_id)
where visibility = 'private' and bgg_id is not null;
create index games_owner_id_idx on public.games (owner_id);
create index games_title_idx on public.games (lower(title));
create table public.game_variants (
id uuid primary key default gen_random_uuid(),
game_id uuid not null references public.games (id) on delete cascade,
name text not null check (char_length(name) between 1 and 200),
year smallint check (year between 1800 and 2100),
language text check (char_length(language) <= 50),
bgg_id integer,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
-- Ermöglicht den zusammengesetzten Verweis (variant_id, game_id) in Sammlung und Partien:
-- so kann eine Variante nie zu einem anderen Spiel gehören.
constraint game_variants_id_game_key unique (id, game_id)
);
create index game_variants_game_id_idx on public.game_variants (game_id);
create table public.genres (
id uuid primary key default gen_random_uuid(),
name_de text not null unique check (char_length(name_de) between 1 and 100),
name_en text not null unique check (char_length(name_en) between 1 and 100),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table public.mechanics (
id uuid primary key default gen_random_uuid(),
name_de text not null unique check (char_length(name_de) between 1 and 100),
name_en text not null unique check (char_length(name_en) between 1 and 100),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table public.categories (
id uuid primary key default gen_random_uuid(),
name_de text not null unique check (char_length(name_de) between 1 and 100),
name_en text not null unique check (char_length(name_en) between 1 and 100),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table public.game_genres (
id uuid primary key default gen_random_uuid(),
game_id uuid not null references public.games (id) on delete cascade,
genre_id uuid not null references public.genres (id) on delete cascade,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (game_id, genre_id)
);
create index game_genres_genre_id_idx on public.game_genres (genre_id);
create table public.game_mechanics (
id uuid primary key default gen_random_uuid(),
game_id uuid not null references public.games (id) on delete cascade,
mechanic_id uuid not null references public.mechanics (id) on delete cascade,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (game_id, mechanic_id)
);
create index game_mechanics_mechanic_id_idx on public.game_mechanics (mechanic_id);
create table public.game_categories (
id uuid primary key default gen_random_uuid(),
game_id uuid not null references public.games (id) on delete cascade,
category_id uuid not null references public.categories (id) on delete cascade,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (game_id, category_id)
);
create index game_categories_category_id_idx on public.game_categories (category_id);
-- updated_at-Trigger für alle Tabellen dieser Migration
create trigger set_updated_at before update on public.games
for each row execute function public.set_updated_at();
create trigger set_updated_at before update on public.game_variants
for each row execute function public.set_updated_at();
create trigger set_updated_at before update on public.genres
for each row execute function public.set_updated_at();
create trigger set_updated_at before update on public.mechanics
for each row execute function public.set_updated_at();
create trigger set_updated_at before update on public.categories
for each row execute function public.set_updated_at();
create trigger set_updated_at before update on public.game_genres
for each row execute function public.set_updated_at();
create trigger set_updated_at before update on public.game_mechanics
for each row execute function public.set_updated_at();
create trigger set_updated_at before update on public.game_categories
for each row execute function public.set_updated_at();
-- Rechte und RLS
alter table public.games enable row level security;
alter table public.game_variants enable row level security;
alter table public.genres enable row level security;
alter table public.mechanics enable row level security;
alter table public.categories enable row level security;
alter table public.game_genres enable row level security;
alter table public.game_mechanics enable row level security;
alter table public.game_categories enable row level security;
revoke all on public.games, public.game_variants, public.genres, public.mechanics,
public.categories, public.game_genres, public.game_mechanics, public.game_categories
from anon, authenticated;
grant select on public.games, public.game_variants, public.genres, public.mechanics,
public.categories, public.game_genres, public.game_mechanics, public.game_categories
to anon, authenticated;
grant insert, update, delete on public.games, public.game_variants, public.genres,
public.mechanics, public.categories, public.game_genres, public.game_mechanics,
public.game_categories to authenticated;
-- games: Katalog für alle sichtbar, private Spiele nur für den Ersteller.
create policy games_select on public.games
for select to anon, authenticated
using (visibility = 'catalog' or owner_id = (select auth.uid()));
create policy games_insert_private on public.games
for insert to authenticated
with check (visibility = 'private' and owner_id = (select auth.uid()));
create policy games_insert_admin on public.games
for insert to authenticated
with check (public.is_admin());
create policy games_update_private on public.games
for update to authenticated
using (visibility = 'private' and owner_id = (select auth.uid()))
with check (visibility = 'private' and owner_id = (select auth.uid()));
create policy games_update_admin on public.games
for update to authenticated
using (public.is_admin())
with check (public.is_admin());
create policy games_delete_private on public.games
for delete to authenticated
using (visibility = 'private' and owner_id = (select auth.uid()));
create policy games_delete_admin on public.games
for delete to authenticated
using (public.is_admin());
-- Begriffe: lesbar für alle, pflegbar nur durch Admin.
create policy genres_select on public.genres
for select to anon, authenticated using (true);
create policy genres_write_admin on public.genres
for all to authenticated
using (public.is_admin()) with check (public.is_admin());
create policy mechanics_select on public.mechanics
for select to anon, authenticated using (true);
create policy mechanics_write_admin on public.mechanics
for all to authenticated
using (public.is_admin()) with check (public.is_admin());
create policy categories_select on public.categories
for select to anon, authenticated using (true);
create policy categories_write_admin on public.categories
for all to authenticated
using (public.is_admin()) with check (public.is_admin());
-- Varianten und Zuordnungen folgen dem zugehörigen Spiel: Wer das Spiel sehen darf, sieht sie.
-- Ändern darf Admin oder der Ersteller eines privaten Spiels.
create policy game_variants_select on public.game_variants
for select to anon, authenticated
using (exists (select 1 from public.games g where g.id = game_id));
create policy game_variants_write on public.game_variants
for all to authenticated
using (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
)
with check (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
);
create policy game_genres_select on public.game_genres
for select to anon, authenticated
using (exists (select 1 from public.games g where g.id = game_id));
create policy game_genres_write on public.game_genres
for all to authenticated
using (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
)
with check (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
);
create policy game_mechanics_select on public.game_mechanics
for select to anon, authenticated
using (exists (select 1 from public.games g where g.id = game_id));
create policy game_mechanics_write on public.game_mechanics
for all to authenticated
using (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
)
with check (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
);
create policy game_categories_select on public.game_categories
for select to anon, authenticated
using (exists (select 1 from public.games g where g.id = game_id));
create policy game_categories_write on public.game_categories
for all to authenticated
using (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
)
with check (
public.is_admin()
or exists (select 1 from public.games g
where g.id = game_id and g.owner_id = (select auth.uid()))
);