teecup/app/main.py

81 lines
2.4 KiB
Python
Raw Normal View History

2026-07-16 07:26:04 +02:00
"""
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 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"])