feat: expose recommendation streaming api

This commit is contained in:
Justin Visser 2026-08-10 13:41:43 +02:00
parent e8d20158e3
commit 41aa93c3c7
7 changed files with 473 additions and 12 deletions

View file

@ -1,5 +1,6 @@
"""HTTP routes for Spotify login and session management."""
from dataclasses import dataclass
from typing import cast
import httpx2
@ -7,7 +8,7 @@ from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse, RedirectResponse, Response
from app.adapters.spotify.login import begin_login, complete_login
from app.adapters.spotify.session import PendingLogins, SessionStore
from app.adapters.spotify.session import PendingLogins, SessionStore, SpotifySession
from app.config import Settings
SESSION_COOKIE_NAME = "discovery_session"
@ -15,6 +16,14 @@ SESSION_COOKIE_NAME = "discovery_session"
router = APIRouter()
@dataclass(frozen=True)
class ResolvedSession:
"""A session and stable cache key selected for one request."""
session_id: str
session: SpotifySession
@router.get("/api/auth/login")
def login(request: Request) -> RedirectResponse:
"""Start Spotify Authorization Code with PKCE login."""
@ -65,12 +74,10 @@ async def callback(
@router.get("/api/auth/me")
def current_session(request: Request) -> JSONResponse:
"""Return the display name for a valid application session."""
session_id = request.cookies.get(SESSION_COOKIE_NAME)
session_store = cast(SessionStore, request.app.state.session_store)
session = session_store.get(session_id) if session_id is not None else None
if session is None:
resolved = resolve_session(request)
if resolved is None:
return JSONResponse({"detail": "Not authenticated"}, status_code=401)
return JSONResponse({"display_name": session.display_name})
return JSONResponse({"display_name": resolved.session.display_name})
@router.post("/api/auth/logout", status_code=204)
@ -95,3 +102,19 @@ def logout(request: Request) -> Response:
def _login_error_redirect() -> RedirectResponse:
return RedirectResponse("/?login=error", status_code=307)
def resolve_session(request: Request) -> ResolvedSession | None:
"""Resolve the cookie session or the installed live seed session."""
session_store = cast(SessionStore, request.app.state.session_store)
cookie_session_id = request.cookies.get(SESSION_COOKIE_NAME)
if cookie_session_id is not None:
cookie_session = session_store.get(cookie_session_id)
if cookie_session is not None:
return ResolvedSession(cookie_session_id, cookie_session)
seed_session_id = cast(str | None, getattr(request.app.state, "seed_session_id", None))
seed_session = session_store.get(seed_session_id) if seed_session_id is not None else None
if seed_session is None or seed_session_id is None:
return None
return ResolvedSession(seed_session_id, seed_session)