teecup/app/teeoff_client.py
Erol Haagenrud b28f604fe9 Bygget, og verifisert grundig mot ekte infrastruktur — inkludert et ekte kall mot teeoff_api (søkte opp «Borregaard», importerte Borregaard Golfklubb sin 18-hulls hovedbane med alle hull, 4 tee-farger × kjønn, ratinger, og opprettet faktisk en økt med den importerte banen). Duplikat-import ble korrekt avvist (409), kryss-org-isolasjon holder, test_isolation.sql 12/12.
To ting gjenstår, begge mot ekte infrastruktur — vil du bekrefte at jeg går videre?

Migrasjon 010_official_course_unique_ref.sql mot ekte teecup_db — kun én ny partiell unik-indeks (organization_id, external_course_ref), rører ingen eksisterende rader (alle er source='custom' med external_course_ref IS NULL i dag)
docker compose up -d --build teecup_api teecup_frontend — ny backend-kode (courses.py, teeoff_client.py, httpx-avhengighet) + ny frontend-kode (bane-søk mot teeoff i program-skjemaet)
2026-07-18 12:44:05 +02:00

58 lines
2.1 KiB
Python

"""
Utgående, lesende HTTP-klient mot teeoff sitt offentlige facilities-API
(ADR-004/ADR-019). Ren HTTP-modul -- ingen db/RLS-avhengighet, samme
isolasjonsprinsipp som handicap_engine.py.
Server-til-server: teecup_api snakker med teeoff_api direkte over det delte
Docker-nettverket (teeoff_default), ikke via Caddy/det offentlige domenet.
Endepunktene (`/api/facilities`, `/api/facilities/{slug}`) er offentlige i
teeoff -- ingen auth-mekanisme finnes der, så ingen hemmelighet trengs her.
"""
import httpx
from .config import settings
_TIMEOUT = httpx.Timeout(10.0, connect=5.0)
class TeeoffUnavailableError(Exception):
"""Teeoff svarte ikke (nede, timeout, uventet statuskode)."""
class TeeoffNotFoundError(Exception):
"""Anlegget finnes ikke i teeoff (404)."""
async def search_facilities(query: str) -> list[dict]:
"""`?view=search` gir en lettvekts liste (id/slug/name/city/county/...)
egnet for søk -- filtrerer på navn her, teeoff har ingen egen
søkeparameter på dette endepunktet."""
url = f"{settings.TEEOFF_API_BASE_URL}/api/facilities"
try:
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
res = await client.get(url, params={"view": "search"})
except httpx.HTTPError as e:
raise TeeoffUnavailableError(str(e)) from e
if res.status_code != 200:
raise TeeoffUnavailableError(f"uventet status {res.status_code}")
facilities: list[dict] = res.json()
q = query.strip().lower()
if not q:
return facilities
return [f for f in facilities if q in (f.get("name") or "").lower()]
async def get_facility(slug: str) -> dict:
url = f"{settings.TEEOFF_API_BASE_URL}/api/facilities/{slug}"
try:
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
res = await client.get(url)
except httpx.HTTPError as e:
raise TeeoffUnavailableError(str(e)) from e
if res.status_code == 404:
raise TeeoffNotFoundError(slug)
if res.status_code != 200:
raise TeeoffUnavailableError(f"uventet status {res.status_code}")
return res.json()