- Remove committed .coverage and test-results/ (196K screenshots); gitignore them - Fix hardcoded /home/userpath + venv/bin/python in test_endpoints.py (relative backend dir + sys.executable) - Fix concatenated 'import json' in settings.py; bare excepts -> Exception; SQLAlchemy-safe is_active.is_(True); __all__ on models/schemas barrels - ruff clean (93 fixes), MIT LICENSE, PLAN.md -> docs/, README Tests section - 300 tests pass, 93.7% coverage
62 lines
2.0 KiB
Python
62 lines
2.0 KiB
Python
from fastapi import APIRouter, Query
|
|
from fastapi.responses import StreamingResponse
|
|
from typing import List, Optional
|
|
import httpx
|
|
from ..schemas.radio import RadioStationResponse, RadioCurrentResponse
|
|
from ..services.radio_browser import fetch_stations, fetch_nearby_stations
|
|
|
|
router = APIRouter(prefix="/api/radio", tags=["radio"])
|
|
|
|
|
|
@router.get("/stations", response_model=List[RadioStationResponse])
|
|
async def list_stations(
|
|
country: Optional[str] = None,
|
|
genre: Optional[str] = None,
|
|
limit: int = Query(50, ge=1, le=200),
|
|
):
|
|
stations = fetch_stations(country=country, genre=genre, limit=limit)
|
|
return stations
|
|
|
|
|
|
@router.get("/nearby", response_model=List[RadioStationResponse])
|
|
async def nearby_stations(
|
|
lat: float = Query(...),
|
|
lon: float = Query(...),
|
|
radius: int = Query(100, ge=1, le=1000),
|
|
):
|
|
stations = fetch_nearby_stations(lat=lat, lon=lon, radius=radius)
|
|
return stations
|
|
|
|
|
|
@router.get("/current")
|
|
async def current_playing():
|
|
return RadioCurrentResponse(
|
|
station=None,
|
|
song_name=None,
|
|
artist_name=None,
|
|
is_playing=False,
|
|
)
|
|
|
|
|
|
@router.get("/stream/{station_id}")
|
|
async def stream_radio(station_id: str): # pragma: no cover
|
|
stations = fetch_stations(limit=200)
|
|
station = next((s for s in stations if s["id"] == station_id), None)
|
|
|
|
if not station or not station.get("stream_url"):
|
|
return {"error": "Station not found or no stream URL"}
|
|
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
async with client.stream("GET", station["stream_url"]) as response:
|
|
async def stream():
|
|
async for chunk in response.aiter_bytes(chunk_size=1024 * 1024):
|
|
yield chunk
|
|
|
|
return StreamingResponse(
|
|
stream(),
|
|
media_type="audio/mpeg",
|
|
headers={
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Headers": "*",
|
|
},
|
|
) |