""" TeeCup API — skjelett. Demonstrerer det org-scopede mønsteret: hver rute under /orgs/{organization_id} verifiserer medlemskap (get_authorized_org) og kjører deretter mot en tilkobling med org-konteksten satt lokalt (org_connection). RLS gjør resten. Dette er et FUNDAMENT, ikke full CRUD. To ruter er nok til å vise mønsteret; resten (økter, matcher, score, leaderboard) bygges på samme lest. 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 Depends, FastAPI from pydantic import BaseModel, Field from .auth import get_authorized_org from .db import init_pool, close_pool, org_connection @asynccontextmanager async def lifespan(app: FastAPI): await init_pool() try: yield finally: await close_pool() app = FastAPI(title="TeeCup API", lifespan=lifespan) class TournamentCreate(BaseModel): name: str = Field(min_length=1, max_length=200) class Tournament(BaseModel): id: str name: str status: str @app.get("/health") async def health() -> dict[str, str]: return {"status": "ok"} @app.get("/orgs/{organization_id}/tournaments", response_model=list[Tournament]) async def list_tournaments( organization_id: str = Depends(get_authorized_org), ) -> list[Tournament]: # Ingen manuell 'WHERE organization_id = ...' — RLS filtrerer automatisk. async with org_connection(organization_id) as conn: rows = await conn.fetch( "SELECT id::text, name, status::text FROM tournament ORDER BY created_at DESC" ) return [Tournament(id=r["id"], name=r["name"], status=r["status"]) for r in rows] @app.post("/orgs/{organization_id}/tournaments", response_model=Tournament, status_code=201) async def create_tournament( body: TournamentCreate, organization_id: str = Depends(get_authorized_org), ) -> Tournament: # organization_id settes eksplisitt; WITH CHECK i RLS avviser uansett en verdi # som ikke matcher konteksten — dobbel sikring. async with org_connection(organization_id) as conn: row = await conn.fetchrow( """ INSERT INTO tournament (organization_id, name) VALUES ($1, $2) RETURNING id::text, name, status::text """, organization_id, body.name, ) return Tournament(id=row["id"], name=row["name"], status=row["status"])