""" CrownCode backend entrypoint with enhanced error handling. """ from __future__ import annotations import asyncio import os from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from .routes.health import router as health_router from .routes.youtube import router as youtube_router from .routes.data_processing import router as data_processing_router from .routes.analyze import router as analyze_router from .routes.commend import router as commend_router from .routes.reservations import router as reservations_router from .routes.dreams import router as dreams_router from .routes.remix import router as remix_router from .services.logging_config import setup_logging, get_logger setup_logging(level=os.getenv("LOG_LEVEL", "INFO")) logger = get_logger(__name__) def _load_origins() -> list[str]: raw = os.getenv("CROWNCODE_CORS_ORIGINS") or os.getenv("CORS_ORIGIN") or "http://localhost:3000" if raw.strip() == "*": logger.warning( "CORS wildcard '*' is not recommended for production. " "Set CROWNCODE_CORS_ORIGINS to an explicit origin list." ) return ["*"] origins = [origin.strip() for origin in raw.split(",") if origin.strip()] if not origins: # No origins configured — default to localhost for development origins = ["http://localhost:3000"] logger.warning("No CORS origins configured, defaulting to localhost") logger.info(f"CORS configured for origins: {origins}") return origins @asynccontextmanager async def lifespan(_app: FastAPI): # On the Space (AURIS_WARMUP=1 in the Dockerfile) load the models and # compile librosa's numba code before the first visitor; off in tests. warm_task = None if os.getenv("AURIS_WARMUP") == "1": from .routes.analyze import warm_up warm_task = asyncio.create_task(warm_up()) yield if warm_task is not None and not warm_task.done(): warm_task.cancel() app = FastAPI(title="CrownCode Backend API", version="0.1.0", lifespan=lifespan) @app.get("/") async def root(): """Root endpoint for health check and API info.""" return { "name": "CrownCode Backend API", "version": "0.1.0", "status": "running", "docs": "/docs", "health": "/api/health" } @app.exception_handler(ValueError) async def value_error_handler(request: Request, exc: ValueError) -> JSONResponse: logger.warning(f"Validation error: {exc}") return JSONResponse( status_code=400, content={"detail": str(exc), "type": "validation_error"} ) @app.exception_handler(FileNotFoundError) async def file_not_found_handler(request: Request, exc: FileNotFoundError) -> JSONResponse: logger.error(f"File not found: {exc}") return JSONResponse( status_code=404, content={"detail": "Resource not found", "type": "not_found"} ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse: logger.error(f"Unhandled exception: {type(exc).__name__}: {exc}", exc_info=True) return JSONResponse( status_code=500, content={"detail": "Internal server error", "type": "server_error"} ) _origins = _load_origins() app.add_middleware( CORSMiddleware, allow_origins=_origins, allow_credentials="*" not in _origins, allow_methods=["GET", "POST", "DELETE", "OPTIONS"], allow_headers=["Content-Type", "Accept"], expose_headers=["X-Remix-Analysis", "X-Multitrack-Analysis"], ) app.include_router(health_router) app.include_router(youtube_router) app.include_router(data_processing_router) app.include_router(analyze_router) app.include_router(commend_router) app.include_router(reservations_router) app.include_router(dreams_router) app.include_router(remix_router) logger.info("CrownCode backend API initialized")