É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.
44 lines
1.2 KiB
Python
44 lines
1.2 KiB
Python
"""
|
|
TeeCup API.
|
|
|
|
App-factory + livssyklus. All forretningslogikk ligger i app/routers/ — hver
|
|
rute der følger samme mønster: Depends(get_authorized_org) verifiserer
|
|
medlemskap, org_connection(organization_id) setter RLS-konteksten lokalt for
|
|
transaksjonen. Se ARCHITECTURE_DECISIONS.md (ADR-001…014) for reglene bak.
|
|
|
|
Kjøres i app-containeren (FastAPI + asyncpg + uvicorn):
|
|
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
|
"""
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from .db import init_pool, close_pool
|
|
from .routers import auth, matches, organizations, players, registration, scoring, tournaments
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
await init_pool()
|
|
try:
|
|
yield
|
|
finally:
|
|
await close_pool()
|
|
|
|
|
|
app = FastAPI(title="TeeCup API", lifespan=lifespan)
|
|
|
|
app.include_router(auth.router)
|
|
app.include_router(organizations.router)
|
|
app.include_router(players.router)
|
|
app.include_router(tournaments.router)
|
|
app.include_router(matches.router)
|
|
app.include_router(scoring.router)
|
|
app.include_router(registration.router)
|
|
app.include_router(registration.org_router)
|
|
|
|
|
|
@app.get("/health")
|
|
async def health() -> dict[str, str]:
|
|
return {"status": "ok"}
|