Compare commits
No commits in common. "4f90fb05dc1bf56f90fabb8fa97a0bd101254423" and "1b5362201edd27c00b7a5f0a5dcf74d3ca715dde" have entirely different histories.
4f90fb05dc
...
1b5362201e
5 changed files with 3 additions and 191 deletions
|
|
@ -13,9 +13,7 @@
|
||||||
"Bash(docker cp *)",
|
"Bash(docker cp *)",
|
||||||
"Bash(tee /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/schema_run.log)",
|
"Bash(tee /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/schema_run.log)",
|
||||||
"Bash(echo \"EXIT:$?\")",
|
"Bash(echo \"EXIT:$?\")",
|
||||||
"Read(//tmp/**)",
|
"Read(//tmp/**)"
|
||||||
"Bash(git add *)",
|
|
||||||
"Bash(git commit *)"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
.env
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
58
CLAUDE.md
58
CLAUDE.md
|
|
@ -1,58 +0,0 @@
|
||||||
# CLAUDE.md — arbeidsinstruks for TeeCup
|
|
||||||
|
|
||||||
Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber.
|
|
||||||
|
|
||||||
## Autoritative kilder (les før du gjør noe)
|
|
||||||
- `ARCHITECTURE_DECISIONS.md` — hva som er bestemt og hvorfor (ADR-001…013). Fasit.
|
|
||||||
- `FEATURE_BACKLOG.md` — hva som gjenstår, hva som er utsatt, hva som mangler.
|
|
||||||
- Endres en beslutning: legg til en ny ADR, ikke slett historikk. Hold begge
|
|
||||||
filene oppdatert når noe avgjøres.
|
|
||||||
|
|
||||||
## Sikkerhetsregler (ufravikelige)
|
|
||||||
- Rør ALDRI `teeoff`-databasen eller den ekte `teecup_db` uten at brukeren
|
|
||||||
eksplisitt har bekreftet det i samme økt. Test alltid migrasjoner mot en egen
|
|
||||||
scratch-database først, og rydd opp etterpå.
|
|
||||||
- Vis planen (hvilke kommandoer, mot hvilken database) FØR du kjører noe som
|
|
||||||
skriver, migrerer eller sletter. Vent på bekreftelse.
|
|
||||||
- Hemmeligheter (passord, secrets) bor i `.env` (filrettigheter 600), dekkes av
|
|
||||||
`.gitignore`, committes aldri, og skrives aldri i klartekst i chatten eller i
|
|
||||||
SQL-filer. Generer dem på serveren (`openssl rand -base64 32`).
|
|
||||||
- Kjør appen som databaserollen `teecup_app` (NOSUPERUSER, NOBYPASSRLS) — aldri
|
|
||||||
som `teeoff_admin`/superuser i runtime.
|
|
||||||
|
|
||||||
## Arkitektur-invarianter (ikke bryt uten en ny ADR)
|
|
||||||
- Tenant = organisasjon. `organization_id` på alle domenetabeller, håndhevet av
|
|
||||||
RLS. App-koden setter `app.current_org` med `SET LOCAL` per transaksjon.
|
|
||||||
- Verifiser at brukeren er medlem av organisasjonen FØR org-konteksten settes.
|
|
||||||
RLS stoler blindt på `app.current_org`.
|
|
||||||
- Egen innlogging (uavhengig av teeoff). Banedata hentes fra teeoff via lesende
|
|
||||||
API, ikke delt database.
|
|
||||||
- v1 = nøyaktig to lag (Ryder Cup-format), håndhevet i app-laget. Match-modellen
|
|
||||||
holdes generell (to sider) så knockout/flere lag kan komme senere.
|
|
||||||
- Handicap-/matchlogikk skal ligge i `handicap_engine.py` (rent, testet, uten
|
|
||||||
db/API-avhengigheter). Allowances er konfig, ikke hardkodet.
|
|
||||||
- Media (bilder/video) skal i objektlagring (MinIO), ikke i Postgres. Postgres
|
|
||||||
holder bare metadata + nøkkel.
|
|
||||||
|
|
||||||
## Arbeidsmåte
|
|
||||||
- Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før
|
|
||||||
du går videre.
|
|
||||||
- Bruk git (remote: brukerens Forgejo). Commit i logiske steg med tydelige
|
|
||||||
meldinger.
|
|
||||||
- Er du usikker på omfang eller en beslutning: spør heller enn å gjette.
|
|
||||||
|
|
||||||
## Status (oppdater denne når ting endres)
|
|
||||||
Ferdig og verifisert:
|
|
||||||
- Handicap-motor + tester (24/24, R&A-verifisert).
|
|
||||||
- Skjema `001` + roller `002` + scoring/blind draw `003`. Isolasjon bevist med
|
|
||||||
`test_isolation.sql` (RLS-oppførsel, ikke bare at skjemaet kjører).
|
|
||||||
- API-skjelett i `app/` (org-kontekst via SET LOCAL, medlemskapssjekk). Kun
|
|
||||||
syntaks-verifisert — ikke kjørt ennå (mangler container med avhengigheter).
|
|
||||||
|
|
||||||
Neste steg:
|
|
||||||
1. Containerisere TeeCup-API-et (Dockerfile + compose-tjeneste), koble mot
|
|
||||||
`teecup_db` med `teecup_app`, rute via eksisterende Caddy til
|
|
||||||
`teecup.teeoff.no`.
|
|
||||||
2. API-endepunkter: oppsett (turnering/lag/økter/roster/blind draw) og scoring
|
|
||||||
(slag eller hull-resultat → matchstatus fra motoren).
|
|
||||||
3. Deretter frontend (PWA, offline-first) og kommunikasjon (migrasjon 004).
|
|
||||||
Binary file not shown.
|
|
@ -5,9 +5,7 @@
|
||||||
-- sett fra runtime-rollen teecup_app (som IKKE er superuser/BYPASSRLS).
|
-- sett fra runtime-rollen teecup_app (som IKKE er superuser/BYPASSRLS).
|
||||||
--
|
--
|
||||||
-- Kjør som admin/superbruker mot en scratch- eller test-database der
|
-- Kjør som admin/superbruker mot en scratch- eller test-database der
|
||||||
-- 001_initial_schema.sql, 002_roles_and_grants.sql og
|
-- 001_initial_schema.sql og 002_roles_and_grants.sql allerede er kjørt:
|
||||||
-- 003_scoring_and_blinddraw.sql allerede er kjørt (003 kreves for
|
|
||||||
-- match_hole_result/lineup_lock, testet her i tillegg til grunnskjemaet):
|
|
||||||
-- psql -d teecup_scratch -f test_isolation.sql
|
-- psql -d teecup_scratch -f test_isolation.sql
|
||||||
--
|
--
|
||||||
-- Alt kjøres i én transaksjon som RULLES TILBAKE til slutt — ingen testdata
|
-- Alt kjøres i én transaksjon som RULLES TILBAKE til slutt — ingen testdata
|
||||||
|
|
@ -28,52 +26,6 @@ INSERT INTO tournament (organization_id, name) VALUES
|
||||||
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'A-Cup'),
|
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'A-Cup'),
|
||||||
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'B-Cup');
|
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'B-Cup');
|
||||||
|
|
||||||
-- --- Seed nok struktur til å teste match_hole_result og lineup_lock ---
|
|
||||||
-- (ADR-012/013, migrasjon 003). Fortsatt privilegert rolle, RLS omgås her.
|
|
||||||
INSERT INTO course (id, organization_id, name) VALUES
|
|
||||||
('c1111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'A-banen'),
|
|
||||||
('c2222222-2222-2222-2222-222222222222', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'B-banen');
|
|
||||||
|
|
||||||
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
||||||
SELECT 'de111111-1111-1111-1111-111111111111', t.organization_id, t.id, 'A rødt'
|
|
||||||
FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
||||||
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
||||||
SELECT 'de222222-2222-2222-2222-222222222222', t.organization_id, t.id, 'A blått'
|
|
||||||
FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
||||||
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
||||||
SELECT 'df111111-1111-1111-1111-111111111111', t.organization_id, t.id, 'B rødt'
|
|
||||||
FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
||||||
INSERT INTO team (id, organization_id, tournament_id, name)
|
|
||||||
SELECT 'df222222-2222-2222-2222-222222222222', t.organization_id, t.id, 'B blått'
|
|
||||||
FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
||||||
|
|
||||||
INSERT INTO session (id, organization_id, tournament_id, sequence, format, course_id)
|
|
||||||
SELECT 'e5111111-1111-1111-1111-111111111111', t.organization_id, t.id, 1, 'singles',
|
|
||||||
'c1111111-1111-1111-1111-111111111111'
|
|
||||||
FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
||||||
INSERT INTO session (id, organization_id, tournament_id, sequence, format, course_id)
|
|
||||||
SELECT 'e5222222-2222-2222-2222-222222222222', t.organization_id, t.id, 1, 'singles',
|
|
||||||
'c2222222-2222-2222-2222-222222222222'
|
|
||||||
FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
||||||
|
|
||||||
INSERT INTO match (id, organization_id, session_id, sequence, team_a_id, team_b_id) VALUES
|
|
||||||
('fa111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
||||||
'e5111111-1111-1111-1111-111111111111', 1,
|
|
||||||
'de111111-1111-1111-1111-111111111111', 'de222222-2222-2222-2222-222222222222'),
|
|
||||||
('fa222222-2222-2222-2222-222222222222', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
||||||
'e5222222-2222-2222-2222-222222222222', 1,
|
|
||||||
'df111111-1111-1111-1111-111111111111', 'df222222-2222-2222-2222-222222222222');
|
|
||||||
|
|
||||||
INSERT INTO match_hole_result (organization_id, match_id, hole_number, winning_side) VALUES
|
|
||||||
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'fa111111-1111-1111-1111-111111111111', 1, 'a'),
|
|
||||||
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'fa222222-2222-2222-2222-222222222222', 1, 'b');
|
|
||||||
|
|
||||||
INSERT INTO lineup_lock (organization_id, session_id, team_id) VALUES
|
|
||||||
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'e5111111-1111-1111-1111-111111111111',
|
|
||||||
'de111111-1111-1111-1111-111111111111'),
|
|
||||||
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'e5222222-2222-2222-2222-222222222222',
|
|
||||||
'df111111-1111-1111-1111-111111111111');
|
|
||||||
|
|
||||||
-- --- Bytt til runtime-rollen: nå SKAL RLS gjelde ---------------------
|
-- --- Bytt til runtime-rollen: nå SKAL RLS gjelde ---------------------
|
||||||
SET ROLE teecup_app;
|
SET ROLE teecup_app;
|
||||||
SELECT set_config('app.current_org', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', false);
|
SELECT set_config('app.current_org', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', false);
|
||||||
|
|
@ -127,88 +79,11 @@ BEGIN
|
||||||
RAISE NOTICE 'OK Test 4: kontekstbytte gir riktig organisasjons data (%).', nm;
|
RAISE NOTICE 'OK Test 4: kontekstbytte gir riktig organisasjons data (%).', nm;
|
||||||
END $$;
|
END $$;
|
||||||
|
|
||||||
-- --- Tilbake til Org A-kontekst for de nye tabellene (ADR-012/013) ----
|
|
||||||
SELECT set_config('app.current_org', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', false);
|
|
||||||
|
|
||||||
-- Test 5: match_hole_result — ser bare Org A sin rad (1, ikke 2).
|
|
||||||
DO $$
|
|
||||||
DECLARE n int;
|
|
||||||
BEGIN
|
|
||||||
SELECT count(*) INTO n FROM match_hole_result;
|
|
||||||
IF n <> 1 THEN
|
|
||||||
RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result lesing): ser % rader, forventet 1', n;
|
|
||||||
END IF;
|
|
||||||
RAISE NOTICE 'OK Test 5: match_hole_result — ser kun egen organisasjons rad (%).', n;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- Test 6: match_hole_result — kan IKKE sette inn en rad merket med annen org,
|
|
||||||
-- selv om match_id-en (fra org B) i seg selv er FK-gyldig.
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO match_hole_result (organization_id, match_id, hole_number, winning_side)
|
|
||||||
VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
||||||
'fa222222-2222-2222-2222-222222222222', 2, 'b');
|
|
||||||
RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result skriving): fikk sette inn data for annen org';
|
|
||||||
EXCEPTION
|
|
||||||
WHEN insufficient_privilege THEN
|
|
||||||
RAISE NOTICE 'OK Test 6: match_hole_result — WITH CHECK blokkerte kryss-org innsetting.';
|
|
||||||
END;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- Test 7: lineup_lock — ser bare Org A sin rad (1, ikke 2).
|
|
||||||
DO $$
|
|
||||||
DECLARE n int;
|
|
||||||
BEGIN
|
|
||||||
SELECT count(*) INTO n FROM lineup_lock;
|
|
||||||
IF n <> 1 THEN
|
|
||||||
RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock lesing): ser % rader, forventet 1', n;
|
|
||||||
END IF;
|
|
||||||
RAISE NOTICE 'OK Test 7: lineup_lock — ser kun egen organisasjons rad (%).', n;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- Test 8: lineup_lock — kan IKKE sette inn en rad merket med annen org,
|
|
||||||
-- selv om session_id/team_id (fra org B) i seg selv er FK-gyldige.
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO lineup_lock (organization_id, session_id, team_id)
|
|
||||||
VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
||||||
'e5222222-2222-2222-2222-222222222222',
|
|
||||||
'df222222-2222-2222-2222-222222222222');
|
|
||||||
RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock skriving): fikk sette inn data for annen org';
|
|
||||||
EXCEPTION
|
|
||||||
WHEN insufficient_privilege THEN
|
|
||||||
RAISE NOTICE 'OK Test 8: lineup_lock — WITH CHECK blokkerte kryss-org innsetting.';
|
|
||||||
END;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- Test 9: kontekstbytte til Org B gir Org Bs rader for begge nye tabeller.
|
|
||||||
SELECT set_config('app.current_org', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', false);
|
|
||||||
DO $$
|
|
||||||
DECLARE ws team_side;
|
|
||||||
DECLARE tid uuid;
|
|
||||||
BEGIN
|
|
||||||
SELECT winning_side INTO ws FROM match_hole_result;
|
|
||||||
IF ws <> 'b' THEN
|
|
||||||
RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result kontekstbytte): forventet b, fikk %', ws;
|
|
||||||
END IF;
|
|
||||||
RAISE NOTICE 'OK Test 9a: match_hole_result — kontekstbytte gir riktig organisasjons data (%).', ws;
|
|
||||||
|
|
||||||
SELECT team_id INTO tid FROM lineup_lock;
|
|
||||||
IF tid <> 'df111111-1111-1111-1111-111111111111' THEN
|
|
||||||
RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock kontekstbytte): forventet Org Bs lag, fikk %', tid;
|
|
||||||
END IF;
|
|
||||||
RAISE NOTICE 'OK Test 9b: lineup_lock — kontekstbytte gir riktig organisasjons data (%).', tid;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
RESET ROLE;
|
RESET ROLE;
|
||||||
RESET app.current_org;
|
RESET app.current_org;
|
||||||
|
|
||||||
ROLLBACK; -- ingen testdata blir liggende igjen
|
ROLLBACK; -- ingen testdata blir liggende igjen
|
||||||
|
|
||||||
\echo '============================================'
|
\echo '============================================'
|
||||||
\echo ' Alle 9 isolasjonstester bestått (se OK-linjer)'
|
\echo ' Alle isolasjonstester bestått (se OK-linjer)'
|
||||||
\echo ' Dekker: tournament/organization (001) samt'
|
|
||||||
\echo ' match_hole_result/lineup_lock (003, ADR-012/013)'
|
|
||||||
\echo '============================================'
|
\echo '============================================'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue