Kommunikasjon er live — det største enkeltløftet i prosjektet så langt:
Lag-chat («det hemmelige rommet»), tilgjengelig via en ny chat-knapp på hvert lagkort i lag/spillere-skjermen — ekte privat, org-eier/admin har ikke tilgang (verifisert helt ned til selve WebSocket-håndtrykket, ikke bare REST). Offentlig runde-feed («Banter Board»), ny seksjon nederst på turneringens offentlige side — gjenbruker eksisterende synlighetsnivåer, men posting krever innlogging + tilknytning til turneringen/organisasjonen. Bilder i begge, sanntid via WebSockets i begge. Underveis: fant at Next.js ikke proxyer WebSocket-oppgraderinger pålitelig, løst med en egen Caddy-rute rett til API-et (samme mønster som media-ruten fra MinIO-runden). Alt scratch-verifisert grundig (20 automatiserte sjekker inkl. faktisk sanntidsmottak over en åpen WebSocket, ikke bare REST-svar), og bekreftet på ekte produksjon med et reelt wss://-håndtrykk over https. Viktig å huske til neste økt: Caddy-endringen ligger uncommitted i det separate /opt/teeoff-repoet — samme fallgruve som tidligere Caddy-runder. Alt er dokumentert i .md-filene. Naturlig neste kandidat er tilskuer-rollen (som Kommunikasjon-arbeidet nå gjør mulig å definere skikkelig), men si fra hva du vil prioritere.
This commit is contained in:
parent
20ae4fe6ea
commit
6774cec72b
4 changed files with 137 additions and 19 deletions
|
|
@ -286,7 +286,13 @@
|
|||
"Bash(curl -s -o /dev/null -w \"teeoff.no: %{http_code}\\\\n\" https://teeoff.no)",
|
||||
"Bash(python3 -c \"import ast; ast.parse\\(open\\('app/routers/registration.py'\\).read\\(\\)\\)\")",
|
||||
"Bash(mkdir -p /opt/teecup/frontend/app/tournaments/\\\\[id\\\\]/teams/\\\\[teamId\\\\]/chat)",
|
||||
"Bash(grep -n -A 20 \"teecup.teeoff.no\" /opt/teeoff/deploy/Caddyfile)"
|
||||
"Bash(grep -n -A 20 \"teecup.teeoff.no\" /opt/teeoff/deploy/Caddyfile)",
|
||||
"Bash(curl -s -o /dev/null -w \"teecup health: %{http_code}\\\\n\" https://teecup.teeoff.no/health)",
|
||||
"Bash(curl -s -o /dev/null -w \"teecup dashboard: %{http_code}\\\\n\" https://teecup.teeoff.no/dashboard)",
|
||||
"Bash(curl -s -o /dev/null -w \"ws-route \\(uten upgrade-header\\): %{http_code}\\\\n\" https://teecup.teeoff.no/ws/orgs/test/teams/test/messages)",
|
||||
"Bash(curl -s https://teecup.teeoff.no/ws/orgs/test/teams/test/messages)",
|
||||
"Bash(curl -s -H \"Content-Type: application/json\" https://teecup.teeoff.no/health)",
|
||||
"Bash(docker run --rm python:3.12-slim bash -c 'pip install -q websockets 2>/dev/null; python3 -c \" *)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/opt/teeoff/deploy",
|
||||
|
|
|
|||
|
|
@ -1093,7 +1093,7 @@ feed-innlegg. Lag-chat har INGEN moderering utover forfatteren selv (rommet
|
|||
er privat, org-admin har uansett ikke lesetilgang og kan derfor ikke
|
||||
moderere det).
|
||||
|
||||
**Status: ✅ BYGGET 2026-07-19.** Se CLAUDE.md-status for scratch-
|
||||
**Status: ✅ BYGGET OG LIVE 2026-07-19.** Se CLAUDE.md-status for scratch-
|
||||
verifisering og utrulling, inkl. en egen Caddy-rute (`/ws/*`) for
|
||||
WebSocket-trafikk direkte til `teecup_api` (Next.js sin `rewrites()`
|
||||
proxyer ikke WebSocket-oppgraderinger pålitelig — samme klasse
|
||||
|
|
|
|||
116
CLAUDE.md
116
CLAUDE.md
|
|
@ -1480,14 +1480,118 @@ Ferdig og verifisert:
|
|||
begge containere boot-et rent, `/health` og `/dashboard` → 200,
|
||||
`teeoff.no` upåvirket.
|
||||
|
||||
- **Kommunikasjon LIVE (2026-07-19, ADR-025) — det største enkeltløftet i
|
||||
prosjektet så langt:** lag-intern chat («det hemmelige rommet») + offentlig
|
||||
runde-feed («Banter Board»), begge med bilder og ekte WebSocket-sanntid,
|
||||
bygget i samme runde. Fire hovedbeslutninger avklart eksplisitt med bruker
|
||||
(AskUserQuestion) før bygging — brukeren valgte den mest ambisiøse
|
||||
kombinasjonen på alle fire (begge deler nå, WebSockets fremfor polling,
|
||||
ekte privat chat, bilder fra start).
|
||||
**Datamodell:** ny migrasjon `013_messaging.sql` — delt `message`-tabell
|
||||
med `scope`-diskriminator (`team`/`tournament_feed`) i stedet for to
|
||||
separate tabeller, RLS org_isolation som ellers. `author_display_name`
|
||||
FRYSES ved skrivetidspunkt (samme prinsipp som handicap-snapshot,
|
||||
ADR-007) — spillerens `player.display_name` i org-en hvis den finnes,
|
||||
ellers e-postens lokaldel (dekker org-ansatte uten egen spillerprofil).
|
||||
**Lag-chat er BEVISST ekte privat** — ny `user_is_rostered_on_team` i
|
||||
`app/team_authz.py`, med VILJE uten org-admin-fallback, ulikt de to andre
|
||||
funksjonene i samme fil (`user_is_team_captain`/`user_is_match_participant`,
|
||||
ADR-023, som begge har et slikt unntak). Første sted i hele appen der
|
||||
org-eier/admin er strukturelt utestengt fra noe.
|
||||
**Offentlig feed:** LESING gjenbruker `registration.py` sitt eksisterende
|
||||
trenivå-visibility-mønster (ADR-018) helt uendret, inkl. anonym tilgang.
|
||||
POSTING er strengere enn lesing — krever ekte innlogging OG org-
|
||||
medlemskap/faktisk deltakelse, selv på en `public`-synlig turnering (en
|
||||
helt urelatert innlogget bruker skal ikke kunne poste på en fremmed
|
||||
offentlig side). Moderering: forfatteren selv ELLER org-eier/admin kan
|
||||
slette et feed-innlegg (motsatt av lag-chatten, som ikke har noen
|
||||
ekstern moderator).
|
||||
**`registration.py` sine fire interne hjelpefunksjoner gjort delt**
|
||||
(fjernet ledende understrek — samme "gjort delt for gjenbruk"-mønster som
|
||||
tidligere runder): `resolve_org`, `is_participant`, `code_matches`,
|
||||
`check_visibility`. `team_authz.py` sin `_is_org_admin` likeens →
|
||||
`is_org_admin`. Ingen atferdsendring, kun navn, for at `messaging.py`
|
||||
skulle kunne gjenbruke dem uendret i stedet for å duplisere logikk.
|
||||
**WebSockets, ikke polling:** in-memory tilkoblingsregister PER PROSESS i
|
||||
`app/routers/messaging.py` — trygt med dagens ene `teecup_api`-container,
|
||||
men deles IKKE på tvers av flere prosesser/containere (samme klasse
|
||||
begrensning som den allerede aksepterte in-memory-cachen, se ARCHITECTURE_
|
||||
DECISIONS.md "Åpne spørsmål"). Ny `get_current_user_from_websocket` i
|
||||
`app/auth.py` — WS-ruter kan ikke bruke `get_current_user`/
|
||||
`get_authorized_org` direkte via `Depends()` (de er `Request`-typet, ingen
|
||||
ekte HTTP Request finnes i en WS-scope), derfor en bevisst minimal, egen
|
||||
kopi av samme cookie-dekode-/oppslagslogikk.
|
||||
**Reell infrastrukturoppdagelse FØR noe ble forsøkt, ikke i etterkant:**
|
||||
Next.js sin `rewrites()` proxyer ikke WebSocket-oppgraderinger pålitelig i
|
||||
"standalone"-modus — løst likt som MinIO-media-ruten (ADR-018): en egen
|
||||
Caddy-rute (`handle /ws/* { reverse_proxy teecup_api:8000 }`) rett til
|
||||
API-et, forbi Next.js/`teecup_frontend` helt. Caddyfile ligger i det
|
||||
SEPARATE `/opt/teeoff`-repoet — samme stale-bind-mount-inode-oppførsel som
|
||||
ALLE tidligere Caddyfile-runder (graceful reload plukker ikke opp
|
||||
endringen), løst likt: full `docker restart teeoff_caddy`, brukeren
|
||||
bekreftet eksplisitt på forhånd, noen sekunders nedetid for `teeoff.no`.
|
||||
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
||||
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container
|
||||
med `websockets`-Python-biblioteket installert kun for testen): et
|
||||
20-punkts asyncio/httpx/websockets-testskript som dekket BEGGE
|
||||
meldingstyper ende-til-ende. Kritiske personvern-/sanntid-funn, alle
|
||||
bekreftet med ekte tilkoblinger (ikke bare REST):
|
||||
- Rostret spiller på lag A FÅR lese/skrive lag A sin chat; rostret spiller
|
||||
på lag B NEKTES; **organisatoren (org-eier) NEKTES OGSÅ** — bekreftet
|
||||
BÅDE over REST (`GET`) og over selve WebSocket-håndtrykket (avvist med
|
||||
lukkekode 4403 før `accept()` i det hele tatt kalles).
|
||||
- Sanntid bekreftet reelt: A1 koblet til lag A sin chat-socket, A2 sendte
|
||||
en melding over vanlig REST, A1 mottok den umiddelbart over den åpne
|
||||
WebSocket-tilkoblingen (ikke bare at REST-svaret så riktig ut).
|
||||
- Bildeopplasting i chat bekreftet (ekte AVIF-konvertert `image_url`
|
||||
returnert).
|
||||
- Offentlig feed: anonym NEKTES å poste (401), en tilfeldig INNLOGGET men
|
||||
uvedkommende bruker NEKTES (403 `NOT_A_PARTICIPANT`), org-medlem FÅR,
|
||||
en faktisk deltaker (rostret, IKKE org-medlem) FÅR — beviser
|
||||
`is_participant`-veien fungerer uavhengig av `is_member`-veien. Anonym
|
||||
WebSocket-tilkobling til en `public`-synlig turnerings feed FÅR lov og
|
||||
mottar sanntidsoppdateringer.
|
||||
- Moderering bekreftet: forfatter sletter eget innlegg, org-admin sletter
|
||||
ANDRES innlegg (feeden), uvedkommende NEKTES å slette andres innlegg.
|
||||
`test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket
|
||||
produksjonsbuild av frontend kjørt og bekreftet, inkl. den nye
|
||||
`/tournaments/[id]/teams/[teamId]/chat`-ruten.
|
||||
**Frontend:** ny `components/team-chat.tsx` (meldingsliste med egen/andres-
|
||||
styling, bildeopplasting, sanntid via nettleserens native `WebSocket`,
|
||||
slett-egen-melding), lenket fra en ny chat-ikon-knapp i `tournament-
|
||||
detail.tsx` sin `TeamPanel`. Ny seksjon `TournamentFeed` i
|
||||
`components/public-tournament.tsx` (nederst på den offentlige
|
||||
turneringssiden) — viser 401/403-svar fra posting som forklarende
|
||||
inline-tekst ("logg inn for å poste" / "du må være medlem/deltaker") i
|
||||
stedet for en generisk feilmelding.
|
||||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt for alle tre
|
||||
stegene (migrasjon, containere, Caddy-restart): migrasjon 013 kjørt mot
|
||||
ekte `teecup_db`, `test_isolation.sql` fortsatt 12/12, begge containere
|
||||
boot-et rent, Caddy validert (`caddy validate` — "Valid configuration")
|
||||
FØR restart, restarten ren (ingen feil i loggen). Verifisert grundig
|
||||
etterpå: `/health`/`dashboard` → 200, `teeoff.no` → 200, et ekte
|
||||
`wss://`-håndtrykk over produksjons-https bekreftet å nå helt frem til
|
||||
applikasjonslaget (testet mot en ukjent turnering-id — ingen ekte data
|
||||
berørt — ga korrekt 404 fra selve WS-ruten, ikke en Caddy/Next.js-feil),
|
||||
og et ren-HTTP-kall mot en `/ws/*`-sti bekreftet å returnere FastAPI sin
|
||||
egen JSON-404 (`{"detail":"Not Found"}`) og ikke Next.js sin HTML-404 —
|
||||
beviser Caddy-ruten faktisk treffer `teecup_api`, ikke `teecup_frontend`.
|
||||
|
||||
Neste steg:
|
||||
1. PWA-egenskaper (manifest, service worker, offline-cache) — ikke startet.
|
||||
2. Kommunikasjon (chat/feed) — ikke startet.
|
||||
3. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: sanntid/WebSockets vs.
|
||||
polling, tilskuer-rolle (bevisst utsatt til Kommunikasjon-runden),
|
||||
kode-regenerering for ADR-020, turnering-status (draft/active/completed/
|
||||
archived) via API, korrigering-godkjenning fra motpart.
|
||||
4. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt),
|
||||
2. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: tilskuer-rolle (bevisst
|
||||
utsatt til nå at Kommunikasjon er ferdig — bør tas fatt på snart, samme
|
||||
begrunnelse som før: definer sammen med feed-synligheten, som nå finnes),
|
||||
kode-regenerering for ADR-020, korrigering-godkjenning fra motpart, live
|
||||
leaderboard (samme WebSocket-mekanisme som meldinger kan gjenbrukes, ikke
|
||||
koblet til ennå), video/1-til-1-meldinger (bevisst utsatt i ADR-025).
|
||||
3. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/
|
||||
Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå.
|
||||
4. **Merk for neste økt:** `deploy/Caddyfile`-endringen (ny `/ws/*`-rute)
|
||||
ligger uncommitted i det SEPARATE `/opt/teeoff`-repoet, ikke i
|
||||
`teecup`-repoet — samme fallgruve som ADR-016-runden sin Caddy-endring,
|
||||
lett å glemme siden denne økten ellers kun har jobbet i `/opt/teecup`.
|
||||
5. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt),
|
||||
FORVENT en full re-eksport hver gang — diff mot live-treet i et
|
||||
scratch-område før noe pakkes ut over eksisterende filer, og sjekk om
|
||||
V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR
|
||||
|
|
|
|||
|
|
@ -464,10 +464,12 @@
|
|||
- «BREAKING: X vant matchen». PWA push. Egen infrastruktur-bit.
|
||||
|
||||
### Sanntid (WebSockets)
|
||||
- **Status:** ❓ trenger beslutning
|
||||
- Live leaderboard og chat som oppdateres uten refresh. Vi har leaderboard-
|
||||
backend-endepunktet (se egen seksjon over), men ikke sanntidsleveringen
|
||||
eller frontend-skjermen ennå. Valg: WebSockets vs. polling.
|
||||
- **Status:** ✅ mekanisme LIVE 2026-07-19 (ADR-025), men KUN koblet til
|
||||
meldinger ennå. WebSockets valgt (ikke polling) — se Kommunikasjon-
|
||||
seksjonen. `app/routers/messaging.py` sitt in-memory tilkoblingsregister
|
||||
og broadcast-mønster kan gjenbrukes for et live leaderboard senere (samme
|
||||
backend-endepunkt finnes allerede), men det er IKKE gjort i denne runden
|
||||
— egen, liten oppfølgingssak om/når etterspurt.
|
||||
|
||||
### Knockout / cup-turnering (egen turneringstype)
|
||||
- **Status:** 💤 utsatt (egen fremtidig type, ADR-011)
|
||||
|
|
@ -537,16 +539,22 @@
|
|||
|
||||
---
|
||||
|
||||
## Kommunikasjon (under design)
|
||||
## Kommunikasjon — ✅ HELT FERDIG 2026-07-19 (ADR-025)
|
||||
|
||||
| Del | Status | Notat |
|
||||
|---|---|---|
|
||||
| Lag-intern chat («det hemmelige rommet») | 🔨 | Bekreftet ønsket. Kanal m/ scope `team`. |
|
||||
| Offentlig runde-feed («Banter Board») | ❓ | Synlighetsnivå fortsatt ikke besluttet for FEED-en spesifikt, men mekanismen finnes nå: `tournament.visibility` + `get_current_user_optional`/`_is_participant()` (ADR-018) er bygget og live — gjenbruk dette, ikke bygg en ny mekanisme. |
|
||||
| Bilder i feed/chat | 📋 | v1. Objektlagring (MinIO), presigned opplasting. |
|
||||
| Video | 💤 | Arkitekt for det, bygg senere (ADR-forslag). |
|
||||
| 1-til-1 direktemeldinger | 💤 | Gemini frarådet for v1; ikke etterspurt av deg. |
|
||||
| Moderering (for offentlig innhold) | ❓ | Kreves hvis «alle med lenken». Mønster finnes i teeoff. |
|
||||
| Lag-intern chat («det hemmelige rommet») | ✅ LIVE | `/tournaments/[id]/teams/[teamId]/chat`. Ekte privat — kun rostrede spillere, INGEN unntak for org-eier/admin (bevisst avvik fra appens vanlige autorisasjonsmønster). `user_is_rostered_on_team` i `app/team_authz.py`. |
|
||||
| Offentlig runde-feed («Banter Board») | ✅ LIVE | Egen seksjon nederst på `/t/[id]`. Lesing gjenbruker `tournament.visibility`-mønsteret (ADR-018) uendret, inkl. anonym tilgang for `public`-synlige turneringer. Posting er strengere: krever innlogging OG org-medlemskap/deltakelse. |
|
||||
| Bilder i feed/chat | ✅ LIVE | Med fra start (ikke utsatt) — gjenbruker `app/storage.py` sin MinIO+AVIF-pipeline fra ADR-018 uendret. |
|
||||
| Sanntid-levering | ✅ LIVE | WebSockets, ikke polling — løser samtidig det tidligere åpne "sanntid vs. polling"-spørsmålet generelt. In-memory tilkoblingsregister per prosess (kun trygt med dagens ene `teecup_api`-container, se ARCHITECTURE_DECISIONS.md "Åpne spørsmål"). Egen Caddy-rute `/ws/*` rett til `teecup_api`. |
|
||||
| Video | 💤 | Fortsatt utsatt, egen ADR om/når etterspurt. |
|
||||
| 1-til-1 direktemeldinger | 💤 | Fortsatt utsatt, ikke etterspurt. |
|
||||
| Moderering (offentlig feed) | ✅ LIVE | Forfatteren selv, ELLER org-eier/admin, kan slette et innlegg. Lag-chatten har ingen moderering utover forfatteren (rommet er privat, org-admin har uansett ikke lesetilgang). |
|
||||
|
||||
Se ARCHITECTURE_DECISIONS.md ADR-025 for alle fire hovedbeslutningene og
|
||||
CLAUDE.md-status for full byggerunde (datamodell, autorisasjon, scratch-
|
||||
verifisering med 20 automatiserte sjekker inkl. reell WebSocket-sanntid,
|
||||
og utrulling).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue