Migrasjonsutkastet er klart og scratch-verifisert. Skrevet til /opt/teecup/040_individual_tournaments.sql — ikke kjørt mot ekte teecup_db, kun mot en isolert scratch-database jeg satte opp og ryddet opp etterpå.
This commit is contained in:
parent
64bcc7ad2e
commit
4d568fc957
2 changed files with 221 additions and 12 deletions
204
040_individual_tournaments.sql
Normal file
204
040_individual_tournaments.sql
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
-- =====================================================================
|
||||||
|
-- TeeCup — individuelle/flerrunde-turneringer: grunnstruktur (migrasjon
|
||||||
|
-- 040, ADR-037)
|
||||||
|
-- =====================================================================
|
||||||
|
-- Realiserer ADR-037 sine fire beslutninger. Rent additivt — ingen
|
||||||
|
-- eksisterende tabell/rad endres i betydning, kun `tournament` som får
|
||||||
|
-- to nye, nullable-med-trygg-default kolonner.
|
||||||
|
--
|
||||||
|
-- Beslutning A: NY, parallell org-scopet tabellstruktur — IKKE en
|
||||||
|
-- utvidelse av de frittstående rundetabellene (round/round_participant/
|
||||||
|
-- round_hole, ADR-033). Disse nye tabellene er RLS-beskyttet på
|
||||||
|
-- organization_id, som alt annet turnering-relatert.
|
||||||
|
-- Beslutning B: SAMME `tournament`-tabell, ny `format_type`-diskriminator
|
||||||
|
-- ('team' | 'individual', default 'team' — alle eksisterende rader
|
||||||
|
-- forblir 'team' uendret). `team`/`team_roster` blir ganske enkelt
|
||||||
|
-- ubrukte for format_type='individual', håndhevet i app-laget (samme
|
||||||
|
-- mønster som ADR-011s to-lags-grense), ikke en tabellomfattende CHECK.
|
||||||
|
-- Beslutning C: Flerrunde via ny `tournament_round`-tabell (samme rolle
|
||||||
|
-- som `session` har for lagturneringer), pekende til EKSISTERENDE
|
||||||
|
-- org-scopet `course` (ingen snapshot — se ADR-037s egen begrunnelse:
|
||||||
|
-- snapshot-behovet i ADR-033 kom av at frittstående runder leser en
|
||||||
|
-- ekstern, ikke-org-kontrollert kilde, det gjelder ikke her). Ny
|
||||||
|
-- `tournament_participant` (samme rolle som `team_roster`, fryser
|
||||||
|
-- handicap_index_snapshot ved uttak — ADR-007-prinsippet).
|
||||||
|
-- Beslutning D: Rå brutto slag lagres (`tournament_round_hole`,
|
||||||
|
-- individuell ball — samme grunnform som hole_score for singles/
|
||||||
|
-- fourball), OG et ferdig utregnet resultat caches per runde per
|
||||||
|
-- deltaker (`tournament_round_score` — samme mønster som
|
||||||
|
-- match.status_text/points_side_a/b). Sammenlagt resultat over flere
|
||||||
|
-- runder summeres VED LESING (samme prinsipp som
|
||||||
|
-- tournament_standings-viewet for lagturneringer), ikke en egen
|
||||||
|
-- tredje cache-tabell.
|
||||||
|
--
|
||||||
|
-- BEVISST UTENFOR DENNE MIGRASJONEN (se ADR-037 "hva denne ADR-en IKKE
|
||||||
|
-- avgjør ennå"): de fem konkrete formatenes egne poengregler
|
||||||
|
-- (Københavner/High-low-high/Robbins/Try all/Flaggturnering) og Order of
|
||||||
|
-- Merit (sesong-sammenlagt på tvers av FLERE turneringer). Dette
|
||||||
|
-- skjemaet dekker kun rene, per-spiller-uavhengige scoringsmetoder
|
||||||
|
-- (bruttoslagspill/nettoslagspill/Stableford) — formater som krever
|
||||||
|
-- paring PÅ TVERS av spillere per hull (Københavner m.fl.) trenger sin
|
||||||
|
-- egen, senere design-runde oppå denne grunnstrukturen.
|
||||||
|
-- =====================================================================
|
||||||
|
\set ON_ERROR_STOP on
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 1. tournament: type-diskriminator + scoringsmetode (Beslutning B)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
ALTER TABLE tournament ADD COLUMN format_type text NOT NULL DEFAULT 'team'
|
||||||
|
CHECK (format_type IN ('team', 'individual'));
|
||||||
|
|
||||||
|
-- Hvilken motorfunksjon (handicap_engine.py) som regner cachet poeng fra
|
||||||
|
-- de rå slagene i tournament_round_hole. Nullable -- en individuell
|
||||||
|
-- turnering kan opprettes som 'draft' før scoringsmetoden er bestemt;
|
||||||
|
-- app-laget krever den satt før en runde kan ta imot score (samme
|
||||||
|
-- "krev X før Y virker, håndhevet i app-laget"-mønster som f.eks. hull-
|
||||||
|
-- data for egendefinerte baner andre steder i skjemaet).
|
||||||
|
ALTER TABLE tournament ADD COLUMN scoring_method text
|
||||||
|
CHECK (scoring_method IS NULL OR scoring_method IN ('stroke_gross', 'stroke_net', 'stableford'));
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 2. tournament_round: én runde/dag i en individuell turnering
|
||||||
|
-- (samme rolle som `session` har for lagturneringer)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
CREATE TABLE tournament_round (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id uuid NOT NULL,
|
||||||
|
tournament_id uuid NOT NULL,
|
||||||
|
sequence smallint NOT NULL, -- rekkefølge i turneringen (1,2,3...)
|
||||||
|
name text, -- f.eks. "Runde 1 — fredag"
|
||||||
|
hole_config hole_scope NOT NULL DEFAULT 'full_18',
|
||||||
|
course_id uuid NOT NULL, -- org-ens EGEN banedata, ingen snapshot
|
||||||
|
scheduled_at timestamptz,
|
||||||
|
tee_interval_minutes smallint CHECK (tee_interval_minutes IS NULL OR tee_interval_minutes > 0),
|
||||||
|
start_hole smallint NOT NULL DEFAULT 1 CHECK (start_hole BETWEEN 1 AND 18),
|
||||||
|
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, course_id)
|
||||||
|
REFERENCES course(organization_id, id) ON DELETE RESTRICT,
|
||||||
|
UNIQUE (organization_id, id),
|
||||||
|
UNIQUE (tournament_id, sequence)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 3. tournament_participant: spiller-pool for EN individuell turnering
|
||||||
|
-- (samme rolle som `team_roster`, men uten noe lag)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
CREATE TABLE tournament_participant (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id uuid NOT NULL,
|
||||||
|
tournament_id uuid NOT NULL,
|
||||||
|
player_id uuid NOT NULL,
|
||||||
|
handicap_index_snapshot numeric(4,1), -- fryst ved uttak, ADR-007-prinsippet
|
||||||
|
created_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),
|
||||||
|
UNIQUE (tournament_id, player_id) -- én spiller én gang i feltet
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 4. tournament_round_participant: hvilken tee + hvilket beregnet
|
||||||
|
-- handicap en deltaker har for ÉN GITT runde (banen/tee kan variere
|
||||||
|
-- runde til runde selv om spilleren er den samme -- samme rolle som
|
||||||
|
-- match_participant sin tee_id/course_handicap/playing_handicap)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
CREATE TABLE tournament_round_participant (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id uuid NOT NULL,
|
||||||
|
tournament_round_id uuid NOT NULL,
|
||||||
|
tournament_participant_id uuid NOT NULL,
|
||||||
|
tee_id uuid NOT NULL,
|
||||||
|
-- Cachede handicap-felt (kilde: motoren, ut fra indeks-snapshot + tee-rating).
|
||||||
|
course_handicap smallint,
|
||||||
|
playing_handicap smallint, -- kun relevant for scoring_method='stroke_net'/'stableford'
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
FOREIGN KEY (organization_id, tournament_round_id)
|
||||||
|
REFERENCES tournament_round(organization_id, id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (organization_id, tournament_participant_id)
|
||||||
|
REFERENCES tournament_participant(organization_id, id) ON DELETE RESTRICT,
|
||||||
|
FOREIGN KEY (organization_id, tee_id)
|
||||||
|
REFERENCES tee(organization_id, id) ON DELETE RESTRICT,
|
||||||
|
UNIQUE (organization_id, id),
|
||||||
|
UNIQUE (tournament_round_id, tournament_participant_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 5. tournament_round_hole: rå brutto slag per hull (kilde-sannhet,
|
||||||
|
-- samme prinsipp som hole_score/round_hole -- netto/Stableford er
|
||||||
|
-- ALLTID avledet, aldri lagret som kilde)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
CREATE TABLE tournament_round_hole (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id uuid NOT NULL,
|
||||||
|
tournament_round_participant_id uuid NOT NULL,
|
||||||
|
hole_number smallint NOT NULL CHECK (hole_number BETWEEN 1 AND 18),
|
||||||
|
gross_strokes smallint NOT NULL CHECK (gross_strokes BETWEEN 1 AND 20),
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
FOREIGN KEY (organization_id, tournament_round_participant_id)
|
||||||
|
REFERENCES tournament_round_participant(organization_id, id) ON DELETE CASCADE,
|
||||||
|
UNIQUE (tournament_round_participant_id, hole_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 6. tournament_round_score: FERDIG UTREGNET resultat, cachet per runde
|
||||||
|
-- per deltaker (samme mønster som match.status_text/points_side_a/b
|
||||||
|
-- -- motoren regner på nytt ved hver hull-innsending, SQL summerer
|
||||||
|
-- bare de cachede tallene ved lesing). Alle tre kolonner fylles
|
||||||
|
-- uansett scoring_method (billig å ha alle tre klare -- lar
|
||||||
|
-- leaderboardet bytte visning uten omregning).
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
CREATE TABLE tournament_round_score (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id uuid NOT NULL,
|
||||||
|
tournament_round_id uuid NOT NULL,
|
||||||
|
tournament_participant_id uuid NOT NULL,
|
||||||
|
holes_played smallint NOT NULL DEFAULT 0,
|
||||||
|
gross_total smallint,
|
||||||
|
net_total smallint,
|
||||||
|
stableford_points smallint,
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
FOREIGN KEY (organization_id, tournament_round_id)
|
||||||
|
REFERENCES tournament_round(organization_id, id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (organization_id, tournament_participant_id)
|
||||||
|
REFERENCES tournament_participant(organization_id, id) ON DELETE CASCADE,
|
||||||
|
UNIQUE (tournament_round_id, tournament_participant_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 7. Indekser (RLS-predikat og FK-oppslag, samme konvensjon som 001)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
CREATE INDEX ON tournament_round (organization_id, tournament_id);
|
||||||
|
CREATE INDEX ON tournament_participant (organization_id, tournament_id);
|
||||||
|
CREATE INDEX ON tournament_round_participant (organization_id, tournament_round_id);
|
||||||
|
CREATE INDEX ON tournament_round_participant (organization_id, tournament_participant_id);
|
||||||
|
CREATE INDEX ON tournament_round_hole (organization_id, tournament_round_participant_id);
|
||||||
|
CREATE INDEX ON tournament_round_score (organization_id, tournament_round_id);
|
||||||
|
CREATE INDEX ON tournament_round_score (organization_id, tournament_participant_id);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 8. Row-Level Security (samme org_isolation-policy-mønster som 001)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
DO $$
|
||||||
|
DECLARE t text;
|
||||||
|
BEGIN
|
||||||
|
FOREACH t IN ARRAY ARRAY[
|
||||||
|
'tournament_round', 'tournament_participant',
|
||||||
|
'tournament_round_participant', 'tournament_round_hole',
|
||||||
|
'tournament_round_score'
|
||||||
|
]
|
||||||
|
LOOP
|
||||||
|
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY;', t);
|
||||||
|
EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY;', t);
|
||||||
|
EXECUTE format($p$
|
||||||
|
CREATE POLICY org_isolation ON %I
|
||||||
|
USING (organization_id = current_setting('app.current_org', true)::uuid)
|
||||||
|
WITH CHECK (organization_id = current_setting('app.current_org', true)::uuid);
|
||||||
|
$p$, t);
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
@ -2521,7 +2521,7 @@ gjennomgang av blokk-forslaget over først):
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Venner, kategorisert deling av runder, og tiered personsøk — ✅ FASE 1 (venner-kjernen) FERDIG, ✅ deler av FASE 3 (søk+legg til medspiller, skriv for hele flighten) BYGGET 2026-07-26, fase 2 (rundevisibilitet) fortsatt kun designet
|
## Venner, kategorisert deling av runder, og tiered personsøk — ✅ HELT FERDIG, alle tre faser bygget og live (fase 1: 2026-07-25, fase 2 rundevisibilitet: 2026-07-28, fase 3 delvis: 2026-07-26)
|
||||||
|
|
||||||
Reist av brukeren samme runde som dashbord-forslaget over. Fullt design
|
Reist av brukeren samme runde som dashbord-forslaget over. Fullt design
|
||||||
skrevet som **ADR-036** i ARCHITECTURE_DECISIONS.md — se der for
|
skrevet som **ADR-036** i ARCHITECTURE_DECISIONS.md — se der for
|
||||||
|
|
@ -2553,18 +2553,23 @@ fritekst) gjør nå "samme klubb"-rangeringen i søket pålitelig — et
|
||||||
eksakt strengmatch fungerer nå, noe det ikke ville gjort med den gamle
|
eksakt strengmatch fungerer nå, noe det ikke ville gjort med den gamle
|
||||||
fritekst-versjonen.
|
fritekst-versjonen.
|
||||||
|
|
||||||
**Foreslått byggerekkefølge (IKKE bekreftet), tre uavhengig leverbare
|
**Byggerekkefølge, alle tre uavhengig leverbare faser — ✅ ALLE FERDIG:**
|
||||||
faser:**
|
1. **✅ Venner-kjernen** (vennskap, kategorisering, `/people/search`, ny
|
||||||
1. Venner-kjernen (vennskap, kategorisering, `/people/search`, ny
|
`/friends`-side) — live 2026-07-25, se "Fase 1"-underseksjon under.
|
||||||
`/friends`-side) — leverbar og nyttig helt alene.
|
2. **✅ Rundevisibilitet** (visibility-valg ved rundestart, gatede
|
||||||
2. Rundevisibilitet (visibility-valg ved rundestart, gatede
|
lese-endepunkter, sanntids-livevisning for venner/offentlighet) —
|
||||||
lese-endepunkter, sanntids-livevisning for venner/offentlighet).
|
bygget, scratch-/browserverifisert og live 2026-07-28
|
||||||
3. Ekte medspillere (ikke bare gjester) — utvider
|
(`round.visibility_mode`, migrasjon 032, `_can_view_round`/nye
|
||||||
|
`/public/rounds/*`-endepunkter, ny `/watch/[id]`-side og
|
||||||
|
`/my-friends/[id]`-profilside). Se CLAUDE.md-status 2026-07-28 for
|
||||||
|
full detalj (191 automatiserte sjekker + full nettleser-gjennomgang
|
||||||
|
med tre reelle nettleserkontekster).
|
||||||
|
3. **✅ Ekte medspillere** (ikke bare gjester) — utvider
|
||||||
`POST /rounds/{id}/participants` til å godta et søkt `user_id`.
|
`POST /rounds/{id}/participants` til å godta et søkt `user_id`.
|
||||||
**✅ DENNE DELEN BYGGET 2026-07-26** (se egen "Fase 3 (delvis)"-
|
Bygget 2026-07-26 (se egen "Fase 3 (delvis)"-underseksjon under
|
||||||
underseksjon under Fase 1) — søk+legg til + "skriv for hele
|
Fase 1) — søk+legg til + "skriv for hele flighten" er live.
|
||||||
flighten" er live. `list_rounds`/`RoundOut` sine viewer-relative
|
`list_rounds`/`RoundOut` sine viewer-relative `my_*`-felt (se under)
|
||||||
`my_*`-felt (se under) er del av denne leveransen.
|
er del av denne leveransen.
|
||||||
Sletting av runden og bane-/utslagsbytte forblir eier-eksklusivt
|
Sletting av runden og bane-/utslagsbytte forblir eier-eksklusivt
|
||||||
(bekreftet, se ADR-036).
|
(bekreftet, se ADR-036).
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue