-- ===================================================================== -- TeeCup — migrasjon 007 -- Selvregistrering + utvidet spillerprofil (ADR-017) -- ===================================================================== -- Kjøres etter 001-006. Alt additivt (nullable eller default) bortsett fra -- den nye tabellen, som er helt ny. -- ===================================================================== \set ON_ERROR_STOP on -- --------------------------------------------------------------------- -- 1. Utvidet spillerprofil (ADR-017 Beslutning D) -- --------------------------------------------------------------------- ALTER TABLE player ADD COLUMN mobile text; ALTER TABLE player ADD COLUMN email text; ALTER TABLE player ADD COLUMN birth_date date; ALTER TABLE player ADD COLUMN nickname text; ALTER TABLE player ADD COLUMN country text; ALTER TABLE player ADD COLUMN club text; ALTER TABLE player ADD COLUMN club_member_number text; -- Bevisst IKKE en UNIQUE-constraint på e-post: familier deler av og til -- e-post (forelder melder på barn), og en hard unik-regel ville krasje -- akkurat den vanlige situasjonen. E-postmatching ved påmelding/innlogging -- (ADR-017 Beslutning B) er derfor et mykt, applikasjonslags-oppslag, ikke -- en databasegaranti. -- --------------------------------------------------------------------- -- 2. Påmeldingsinnstillinger på turnering (ADR-017 Beslutning C) -- --------------------------------------------------------------------- ALTER TABLE tournament ADD COLUMN registration_deadline timestamptz; ALTER TABLE tournament ADD COLUMN registration_capacity integer CHECK (registration_capacity IS NULL OR registration_capacity > 0); ALTER TABLE tournament ADD COLUMN registration_overflow_policy text NOT NULL DEFAULT 'closed' CHECK (registration_overflow_policy IN ('waitlist', 'closed')); ALTER TABLE tournament ADD COLUMN registration_requires_approval boolean NOT NULL DEFAULT false; -- --------------------------------------------------------------------- -- 3. tournament_registration (ADR-017 Beslutning C) -- -- Bevisst ATSKILT fra team_roster: en registrering betyr "vil kanskje -- spille", team_roster betyr "committed til et bestemt lag". Organisator -- forfremmer en registrering til roster via de eksisterende -- team-/roster-endepunktene -- denne tabellen påvirker ALDRI team_roster -- automatisk. -- --------------------------------------------------------------------- CREATE TABLE tournament_registration ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL, tournament_id uuid NOT NULL, player_id uuid NOT NULL, status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed', 'waitlisted', 'declined', 'withdrawn')), registered_at timestamptz NOT NULL DEFAULT now(), -- Bevis for at samtykke faktisk ble gitt ved innsending, ikke bare -- antatt (ADR-017). API-laget skal avvise innsending uten samtykke -- -- NOT NULL her er den siste linjen av forsvar, ikke den eneste. consent_given_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), FOREIGN KEY (organization_id, tournament_id) REFERENCES tournament(organization_id, id) ON DELETE CASCADE, FOREIGN KEY (organization_id, player_id) REFERENCES player(organization_id, id) ON DELETE RESTRICT, UNIQUE (organization_id, id), -- for sammensatte FK-er UNIQUE (tournament_id, player_id) -- én registrering per spiller per turnering ); CREATE INDEX ON tournament_registration (organization_id, tournament_id); -- RLS: samme org-isolasjon som resten (ADR-003). Skrevet direkte med -- app_current_org() siden denne tabellen kommer etter 005-fiksen -- ingen -- grunn til å reprodusere tomstreng-buggen bare for å rette den igjen. ALTER TABLE tournament_registration ENABLE ROW LEVEL SECURITY; ALTER TABLE tournament_registration FORCE ROW LEVEL SECURITY; CREATE POLICY org_isolation ON tournament_registration USING (organization_id = app_current_org()) WITH CHECK (organization_id = app_current_org()); GRANT SELECT, INSERT, UPDATE, DELETE ON tournament_registration TO teecup_app; -- --------------------------------------------------------------------- -- 4. public_tournament_org() -- den ENESTE broen fra en uautentisert -- forespørsel (ingen app.current_org satt ennå) til riktig org-kontekst -- (ADR-017 Beslutning A). -- -- SECURITY DEFINER: kjører med skaperens rettigheter (teeoff_admin, som -- har BYPASSRLS) slik at oppslaget kan lese `tournament` FØR RLS- -- konteksten er kjent -- det er selve poenget, ikke et hull. Eksponerer -- KUN organization_id for en gitt turnering-id, ingenting annet fra -- tournament-raden. `SET search_path = public` hindrer search_path- -- kapring (standard herding for SECURITY DEFINER-funksjoner). -- -- Bruksmønster i app-laget: slå opp org_id via denne FØRST, åpne deretter -- en vanlig org_connection(org_id) og fortsett med normal RLS-håndhevelse -- for alt det faktiske arbeidet (sjekk frist/kapasitet, sett inn -- registrering). Samme "løs kontekst-problemet FØR RLS kan håndheve -- noe"-mønster som selvrefererende org-bootstrap (app/routers/ -- organizations.py), nå for et lese-oppslag i stedet for en innsetting. -- --------------------------------------------------------------------- CREATE FUNCTION public_tournament_org(p_tournament_id uuid) RETURNS uuid LANGUAGE sql SECURITY DEFINER SET search_path = public STABLE AS $$ SELECT organization_id FROM tournament WHERE id = p_tournament_id; $$; GRANT EXECUTE ON FUNCTION public_tournament_org(uuid) TO teecup_app;