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"}