Files
enigFM/backend/app/main.py
mamonov.ep fdc854256c 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>
2025-12-12 18:10:25 +03:00

112 lines
3.4 KiB
Python

import asyncio
import logging
from contextlib import asynccontextmanager
from datetime import datetime
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():
"""Send sync updates to all rooms every 10 seconds"""
while True:
await asyncio.sleep(10)
# Get all active rooms
for room_id in list(manager.active_connections.keys()):
try:
async with async_session() as db:
result = await db.execute(select(Room).where(Room.id == room_id))
room = result.scalar_one_or_none()
if not room or not room.is_playing:
continue
# Calculate current position
current_position = room.playback_position or 0
if room.playback_started_at:
elapsed = (datetime.utcnow() - room.playback_started_at).total_seconds() * 1000
current_position = int((room.playback_position or 0) + elapsed)
track_url = None
if room.current_track_id:
track_url = f"/api/tracks/{room.current_track_id}/stream"
await manager.broadcast_to_room(
room_id,
{
"type": "sync_state",
"is_playing": room.is_playing,
"position": current_position,
"current_track_id": str(room.current_track_id) if room.current_track_id else None,
"track_url": track_url,
"server_time": datetime.utcnow().isoformat(),
},
)
except Exception:
pass
@asynccontextmanager
async def lifespan(app: FastAPI):
# Start background sync task
sync_task = asyncio.create_task(periodic_sync())
yield
# Cleanup
sync_task.cancel()
try:
await sync_task
except asyncio.CancelledError:
pass
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,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Routers
app.include_router(auth.router)
app.include_router(rooms.router)
app.include_router(tracks.router)
app.include_router(messages.router)
app.include_router(websocket.router)
@app.get("/")
async def root():
return {"message": "EnigFM API", "version": "1.0.0"}
@app.get("/health")
async def health():
return {"status": "ok"}