teecup/app/realtime.py

47 lines
1.8 KiB
Python

"""
Delt in-memory WebSocket-kringkasting for sanntidsoppdateringer på den
offentlige "Følg live"-siden (ADR-027, oppfølging av ADR-025/026).
Egen, RUTEFRI modul -- importerer INGEN router. Grunnen: både
`scoring.py`/`tournaments.py` (som endrer matchstatus) og `messaging.py`
(som eier selve WS-ruten, `/ws/public/tournaments/{id}/live`) trenger denne,
og `registration.py` importerer allerede fra scoring.py/tournaments.py/
matches.py mens `messaging.py` importerer fra `registration.py` -- en
kringkastingsfunksjon plassert i en av routerne ville skapt en sirkulær
import. Denne modulen ligger BAK alle routere i importgrafen i stedet.
In-memory PER PROSESS -- samme kjente begrensning som meldinger sin egen
kringkasting (`app/routers/messaging.py`): trygt med dagens ene
`teecup_api`-container, deles IKKE på tvers av flere prosesser/containere
(se ARCHITECTURE_DECISIONS.md sine "Åpne spørsmål").
Bevisst "noe endret seg, hent på nytt"-signal, IKKE selve dataene --
i motsetning til meldinger (der selve meldingen kringkastes direkte),
ville det å bygge/sende hele leaderboard+matchliste+scorekort-formen her
duplisert betydelig beregningslogikk. Klienten reagerer ved å kalle de
samme REST-endepunktene på nytt, akkurat som ved førstegangslasting.
"""
from collections import defaultdict
from fastapi import WebSocket
_tournament_live_sockets: dict[str, set[WebSocket]] = defaultdict(set)
def live_sockets_for(tournament_id: str) -> set[WebSocket]:
return _tournament_live_sockets[tournament_id]
async def broadcast_live_update(tournament_id: str) -> None:
sockets = _tournament_live_sockets.get(tournament_id)
if not sockets:
return
dead = []
for ws in list(sockets):
try:
await ws.send_json({"type": "update"})
except Exception:
dead.append(ws)
for ws in dead:
sockets.discard(ws)