"""Main FastAPI application entry point.""" from contextlib import asynccontextmanager from pathlib import Path from typing import AsyncGenerator from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, HTMLResponse from fastapi.staticfiles import StaticFiles from app.core.config import settings from app.core.database import close_db, init_db # Get the base directory BASE_DIR = Path(__file__).resolve().parent.parent @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """ Lifespan context manager for FastAPI application. Handles startup and shutdown events. """ # Startup print("Starting up...") if settings.DEBUG: print("Debug mode is ON") print(f"Database URL: {settings.DATABASE_URL}") print(f"Redis URL: {settings.FULL_REDIS_URL}") # Initialize database await init_db() print("Database initialized") yield # Shutdown print("Shutting down...") await close_db() print("Database connections closed") # Create FastAPI application app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, description="Alternative to Spotify with YouTube streaming", docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json", lifespan=lifespan, ) # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=settings.BACKEND_CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/", response_class=HTMLResponse) async def root() -> str: """Serve the web application.""" template_path = BASE_DIR / "app" / "templates" / "index.html" with open(template_path, 'r', encoding='utf-8') as f: return f.read() @app.get("/health") async def health_check() -> dict[str, str]: """Health check endpoint.""" return {"status": "healthy"} @app.get("/api/v1") async def api_v1_info() -> dict[str, str]: """API v1 information endpoint.""" return { "version": "v1", "prefix": settings.API_V1_PREFIX, "docs": "/api/docs", } # Exception handlers @app.exception_handler(Exception) async def global_exception_handler(request, exc) -> JSONResponse: """Global exception handler for unhandled exceptions.""" if settings.DEBUG: # In debug mode, return full error details return JSONResponse( status_code=500, content={ "detail": str(exc), "type": type(exc).__name__, }, ) # In production, return generic error message return JSONResponse( status_code=500, content={"detail": "Internal server error"}, ) # API routes from app.api.v1 import auth, music, playlists app.include_router(auth.router, prefix=settings.API_V1_PREFIX, tags=["authentication"]) app.include_router(music.router, prefix=settings.API_V1_PREFIX, tags=["music"]) app.include_router(playlists.router, prefix=settings.API_V1_PREFIX, tags=["playlists"]) # Mount static files static_dir = BASE_DIR / "app" / "static" static_dir.mkdir(exist_ok=True) app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") if __name__ == "__main__": import uvicorn uvicorn.run( "app.main:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG, log_level=settings.LOG_LEVEL.lower(), )