Add track filtering, WS keepalive, and improve error handling
- Add track filtering by uploader (my tracks / all tracks) with UI tabs - Add WebSocket ping/pong keepalive (30s interval) to prevent disconnects - Add auto-reconnect on WebSocket close (3s delay) - Add request logging middleware with DATABASE_URL output on startup - Handle missing S3 files gracefully (return 404 instead of 500) - Add debug logging for audio ended event 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,23 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy import select
|
||||
from .routers import auth, rooms, tracks, websocket, messages
|
||||
from .database import async_session
|
||||
from .models.room import Room
|
||||
from .services.sync import manager
|
||||
from .config import get_settings
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Log config on startup
|
||||
settings = get_settings()
|
||||
logger.info(f"DATABASE_URL: {settings.database_url}")
|
||||
|
||||
|
||||
async def periodic_sync():
|
||||
@@ -65,6 +75,15 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
app = FastAPI(title="EnigFM", description="Listen to music together with friends", lifespan=lifespan)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def log_requests(request: Request, call_next):
|
||||
logger.info(f"Request: {request.method} {request.url.path}")
|
||||
response = await call_next(request)
|
||||
logger.info(f"Response: {request.method} {request.url.path} - {response.status_code}")
|
||||
return response
|
||||
|
||||
|
||||
# CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
||||
@@ -19,8 +19,16 @@ router = APIRouter(prefix="/api/tracks", tags=["tracks"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[TrackResponse])
|
||||
async def get_tracks(db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Track).order_by(Track.created_at.desc()))
|
||||
async def get_tracks(
|
||||
my: bool = False,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
query = select(Track)
|
||||
if my:
|
||||
query = query.where(Track.uploaded_by == current_user.id)
|
||||
query = query.order_by(Track.created_at.desc())
|
||||
result = await db.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@@ -174,6 +182,8 @@ async def stream_track(track_id: uuid.UUID, request: Request, db: AsyncSession =
|
||||
|
||||
# Get file size from S3 (without downloading)
|
||||
file_size = get_file_size(track.s3_key)
|
||||
if file_size is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Track file not found in storage")
|
||||
|
||||
# Encode filename for non-ASCII characters
|
||||
encoded_filename = quote(f"{track.title}.mp3")
|
||||
|
||||
@@ -58,6 +58,11 @@ async def room_websocket(websocket: WebSocket, room_id: UUID):
|
||||
data = await websocket.receive_text()
|
||||
message = json.loads(data)
|
||||
|
||||
# Handle ping/pong for keepalive
|
||||
if message.get("type") == "ping":
|
||||
await websocket.send_json({"type": "pong"})
|
||||
continue
|
||||
|
||||
async with async_session() as db:
|
||||
if message["type"] == "player_action":
|
||||
await handle_player_action(db, room_id, user, message)
|
||||
|
||||
@@ -77,11 +77,16 @@ def get_file_content(s3_key: str) -> bytes:
|
||||
return response["Body"].read()
|
||||
|
||||
|
||||
def get_file_size(s3_key: str) -> int:
|
||||
"""Get file size from S3 without downloading"""
|
||||
def get_file_size(s3_key: str) -> int | None:
|
||||
"""Get file size from S3 without downloading. Returns None if file not found."""
|
||||
client = get_s3_client()
|
||||
response = client.head_object(Bucket=settings.s3_bucket_name, Key=s3_key)
|
||||
return response["ContentLength"]
|
||||
try:
|
||||
response = client.head_object(Bucket=settings.s3_bucket_name, Key=s3_key)
|
||||
return response["ContentLength"]
|
||||
except client.exceptions.ClientError as e:
|
||||
if e.response['Error']['Code'] == '404':
|
||||
return None
|
||||
raise
|
||||
|
||||
|
||||
def get_file_range(s3_key: str, start: int, end: int):
|
||||
|
||||
Reference in New Issue
Block a user