teecup/009_landing_pages_and_visibility.sql
Erol Haagenrud 2058d63db5 ADR-018 sin backend er ferdig, verifisert og live. Oppsummert:
Én reell feil funnet og rettet underveis, ikke antatt riktig: migrasjonen feilet først mot scratch — organization.slug har faktisk ligget i skjemaet siden migrasjon 001 ("f.eks. subdomene/URL-vennlig"), noe jeg hadde oversett og prøvde å legge til på nytt. Rettet, kjørte rent etterpå.

En viktig presisering oppdaget under bygging, ikke antatt på forhånd: RLS beskytter kun tenant-grenser (org A ser aldri org B), ikke innholds-synlighet innenfor riktig org-kontekst. Det gamle offentlige endepunktet fra forrige runde leste faktisk fullt innhold uten noen synlighetssjekk i det hele tatt — synlighet må håndheves eksplisitt i koden, noe jeg nå har gjort konsekvent på både lesing og registrering.

Fylte et implisitt hull: ADR-en beskrev synligheten, men ingen tidligere runde hadde bygget en vei for organisator til å faktisk sette disse feltene — lagt til PATCH-endepunkter for turnering og org, pluss full sponsor-CRUD.

Grundig testet: hele synlighetsmatrisen med ekte HTTP-kall — inkludert den interessante "kylling-og-egg"-konsekvensen av Beslutning D (ingen kan selv-registrere seg til en participants-synlig turnering, kun organisator kan legge til direkte — riktig, ikke en bug).

Live nå, teeoff.no upåvirket gjennom hele prosessen.
2026-07-18 09:45:11 +02:00

86 lines
3.9 KiB
PL/PgSQL

-- =====================================================================
-- TeeCup — migrasjon 009
-- Landingssider: synlighet, org-profil, sponsorer (ADR-018)
-- =====================================================================
-- Kjøres etter 001-008. Alt additivt (nullable eller trygg default).
-- =====================================================================
\set ON_ERROR_STOP on
-- ---------------------------------------------------------------------
-- 1. Turnering: synlighet + presenterende tekst (ADR-018 Beslutning A/E)
--
-- Default 'org' -- ingen eksisterende eller nyopprettet turnering blir
-- offentlig av seg selv, organisator må aktivt velge å åpne opp.
-- ---------------------------------------------------------------------
ALTER TABLE tournament ADD COLUMN visibility text NOT NULL DEFAULT 'org'
CHECK (visibility IN ('public', 'org', 'participants'));
ALTER TABLE tournament ADD COLUMN description text;
-- Inert til MinIO-runden (bevisst utsatt, se ADR-018) -- kun kolonnen,
-- ingen opplastingslogikk.
ALTER TABLE tournament ADD COLUMN hero_image_key text;
-- ---------------------------------------------------------------------
-- 2. Organisasjon: offentlig profil (ADR-018 Beslutning F)
--
-- MERK: `organization.slug` finnes ALLEREDE, helt siden migrasjon 001
-- ("f.eks. subdomene/URL-vennlig", plain UNIQUE -- Postgres sin UNIQUE
-- behandler NULL som distinkt fra NULL, så "unik når satt" er allerede
-- garantert uten en egen partiell indeks). Oppdaget ved at denne
-- migrasjonen først feilet mot en fersk scratch-database ("column slug
-- already exists") -- IKKE lagt til på nytt her, kun brukt.
-- ---------------------------------------------------------------------
ALTER TABLE organization ADD COLUMN public_profile boolean NOT NULL DEFAULT false;
ALTER TABLE organization ADD CONSTRAINT organization_slug_format
CHECK (slug IS NULL OR slug ~ '^[a-z0-9]+(-[a-z0-9]+)*$');
ALTER TABLE organization ADD CONSTRAINT organization_public_profile_requires_slug
CHECK (NOT public_profile OR slug IS NOT NULL);
-- ---------------------------------------------------------------------
-- 3. Sponsorer (ADR-018 Beslutning E) -- navn+lenke aktivt, logo inert
-- til MinIO-runden.
-- ---------------------------------------------------------------------
CREATE TABLE tournament_sponsor (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL,
tournament_id uuid NOT NULL,
name text NOT NULL,
url text,
logo_key text, -- inert til MinIO-runden
created_at timestamptz NOT NULL DEFAULT now(),
FOREIGN KEY (organization_id, tournament_id)
REFERENCES tournament(organization_id, id) ON DELETE CASCADE
);
CREATE INDEX ON tournament_sponsor (organization_id, tournament_id);
ALTER TABLE tournament_sponsor ENABLE ROW LEVEL SECURITY;
ALTER TABLE tournament_sponsor FORCE ROW LEVEL SECURITY;
CREATE POLICY org_isolation ON tournament_sponsor
USING (organization_id = app_current_org())
WITH CHECK (organization_id = app_current_org());
GRANT SELECT, INSERT, UPDATE, DELETE ON tournament_sponsor TO teecup_app;
-- ---------------------------------------------------------------------
-- 4. public_org_by_slug() -- tredje SECURITY DEFINER-bro i dette
-- prosjektet (etter public_tournament_org 007, link_player_by_email
-- 008), samme mønster: løs kontekst-problemet FØR RLS kan håndheve
-- noe. Returnerer NULL for BÅDE "finnes ikke" og "finnes, men er
-- privat" -- samme anti-enumerering som magic-link (ADR-009).
-- ---------------------------------------------------------------------
CREATE FUNCTION public_org_by_slug(p_slug text)
RETURNS uuid
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
STABLE
AS $$
SELECT id FROM organization WHERE slug = p_slug AND public_profile = true;
$$;
GRANT EXECUTE ON FUNCTION public_org_by_slug(text) TO teecup_app;