80 lines
2.4 KiB
Python
80 lines
2.4 KiB
Python
"""
|
|
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"])
|