teecup/app/routers/courses.py
Erol Haagenrud b566595e85 Program-skjermen er bygget og grundig scratch-verifisert. Kort oppsummert:
Nytt:

app/routers/courses.py — enkel bane-CRUD (GET/POST /orgs/{id}/courses), fant og tettet et reelt hull: SessionCreate.course_id var påkrevd, men ingen vei fantes til å skaffe én
components/tournament-program.tsx + rute /tournaments/[id]/program — tidslinje over økter, opprett-skjema med bane-type-ahead og avanserte handicap-brytere
Fanerad lagt til i både roster- og program-skjermen så du kan bevege deg mellom dem
To reelle feil rettet før integrering:

V0-promptet mitt ba om ett generisk "Scramble"-format, men databasen/motoren krever scramble_2/scramble_4 som atskilte verdier — rettet til to segment-knapper
Verifiserte allowance_override-JSON-formen eksakt mot parse_allowance_config (typet combined/per_player + 0–1-brøk, ikke flat prosent) — bekreftet med en ekte rundtur i scratch, ikke bare lest fra koden
Verifisert: courses opprettet+listet, kryss-org-isolasjon, økt med klokkeslett, økt med scramble_4+full handicap-override-rundtur, gammel "scramble"-verdi korrekt avvist, test_isolation.sql 12/12, ekte typesjekket produksjonsbuild (samme Dockerfile som deployes).
2026-07-18 12:20:45 +02:00

61 lines
2 KiB
Python

"""
Egendefinerte baner på organisasjonsnivå (ADR-001: gjenbrukbar på tvers av
turneringer, samme mønster som players.py).
`course.source = 'custom'` er den eneste veien inn her -- 'official' (ekte
teeoff-baner via ADR-004 sitt planlagte, lesende API) er fortsatt kun vedtatt,
ikke bygget (se FEATURE_BACKLOG.md), og krever uansett `external_course_ref`
satt (skjemaets CHECK-constraint), som ingen klient kan oppgi meningsfullt
ennå. Denne runden dekker KUN det organisatoren trenger for å opprette en
økt: en bane ved navn, ingen hull-/tee-/rating-detaljer ennå (session-motoren
bruker foreløpig kun course_id som fremmednøkkel, ikke banens innhold).
"""
from fastapi import APIRouter, Depends
from ..auth import get_authorized_org
from ..db import org_connection
from ..errors import translate_db_errors
from pydantic import BaseModel, Field
router = APIRouter()
class CourseCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
class Course(BaseModel):
id: str
name: str
source: str
_COURSE_COLUMNS = "id::text, name, source::text AS source"
@router.get("/orgs/{organization_id}/courses", response_model=list[Course])
async def list_courses(
organization_id: str = Depends(get_authorized_org),
) -> list[Course]:
async with org_connection(organization_id) as conn:
rows = await conn.fetch(f"SELECT {_COURSE_COLUMNS} FROM course ORDER BY name")
return [Course(**dict(r)) for r in rows]
@router.post("/orgs/{organization_id}/courses", response_model=Course, status_code=201)
async def create_course(
body: CourseCreate,
organization_id: str = Depends(get_authorized_org),
) -> Course:
async with org_connection(organization_id) as conn, translate_db_errors():
row = await conn.fetchrow(
f"""
INSERT INTO course (organization_id, name, source)
VALUES ($1, $2, 'custom')
RETURNING {_COURSE_COLUMNS}
""",
organization_id,
body.name,
)
return Course(**dict(row))