"""오늘의 응원가 API — 조회(공개) · Suno 콜백 싱크 · 관리자 수동 생성.""" from __future__ import annotations from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from ..database import get_db from ..models import Song from ..services.songs import poll_generating, start_due_songs from .admin import require_admin router = APIRouter(prefix="/api/songs", tags=["songs"]) KST = timezone(timedelta(hours=9)) def _song_out(s: Song) -> dict: return { "matchId": s.match_id, "league": s.league, "teamCode": s.team_code, "teamName": s.team_name, "title": s.title, "lyrics": s.lyrics, "tracks": s.tracks or [], } @router.get("/today") async def today_songs(league: str = "kbo", db: AsyncSession = Depends(get_db)) -> list[dict]: today = datetime.now(KST).date() # 업그레이드(라인업 반영 재생성) 중인 곡도 이전 트랙을 계속 서빙 — 재생 공백 없음 rows = ( await db.execute( select(Song) .where( Song.league == league, Song.date_kst == today, Song.status.in_(("complete", "generating")), ) .order_by(Song.match_id, Song.team_code) ) ).scalars().all() return [_song_out(s) for s in rows if s.tracks] @router.post("/callback") async def suno_callback(request: Request) -> dict: """Suno 게이트웨이 callBackUrl 싱크대 — 완료 감지는 워커 폴링이 담당.""" try: await request.json() except Exception: # noqa: BLE001 — 본문 형식 무관 pass return {"ok": True} @router.post("/generate", dependencies=[Depends(require_admin)]) async def force_generate(db: AsyncSession = Depends(get_db)) -> dict: """관리자: 오늘 KBO 전 경기 응원가 즉시 생성 시작 + 폴링 1회.""" started = await start_due_songs(db, force_today=True) done = await poll_generating(db) return {"ok": True, "started": started, "completed": done}