o2o-triple-pick/backend/app/routers/matches.py

67 lines
2.2 KiB
Python

"""공개 읽기 API — 경기 목록 / 경기 상세 (AI예측 + crowd 포함)."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from ..database import get_db
from ..domain import match_out
from ..models import Match
from ..schemas import MatchOut
from ..services.baseball_details import fetch_live, get_extras
router = APIRouter(prefix="/api/matches", tags=["matches"])
@router.get("", response_model=list[MatchOut])
async def list_matches(
lang: str = Query("ko"),
league: str = Query("", description="wc | kbo | mlb — 빈값이면 전체"),
db: AsyncSession = Depends(get_db),
) -> list[MatchOut]:
q = (
select(Match)
.options(selectinload(Match.predictions), selectinload(Match.crowd))
.order_by(Match.kickoff_at)
)
if league:
q = q.where(Match.league == league)
rows = (await db.execute(q)).scalars().all()
extras = await get_extras(db, rows)
return [match_out(m, lang, extras=extras.get(m.match_id)) for m in rows]
@router.get("/{match_id}", response_model=MatchOut)
async def get_match(
match_id: str,
lang: str = Query("ko"),
db: AsyncSession = Depends(get_db),
) -> MatchOut:
m = (
await db.execute(
select(Match)
.where(Match.match_id == match_id)
.options(selectinload(Match.predictions), selectinload(Match.crowd))
)
).scalars().first()
if not m:
raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND")
extras = await get_extras(db, [m])
return match_out(m, lang, extras=extras.get(m.match_id))
@router.get("/{match_id}/live")
async def get_live(
match_id: str,
db: AsyncSession = Depends(get_db),
) -> dict:
"""야구 라이브 필드 뷰 (kbo=네이버 relay, mlb=공식 feed/live). 15초 TTL 캐시."""
m = await db.get(Match, match_id)
if not m:
raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND")
if m.league not in ("kbo", "mlb"):
return {"available": False}
return await fetch_live(m)