""" FastAPI application factory and middleware setup. Configures CORS, authentication, and error handling. """ import asyncio import logging from datetime import datetime from pathlib import Path from typing import Dict, Any from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker from sqlalchemy import text from app.core.settings import get_settings from app.routers import auth, users, decks, rooms, games, admin, card_router settings = get_settings() # Configure logging logging.basicConfig(level=getattr(logging, settings.LOG_LEVEL)) logger = logging.getLogger(__name__) app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, docs_url="/docs", redoc_url="/redoc", ) # CORS configuration app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Track initialization status mtg_data_ready = False mtg_data_count = 0 async def check_mtg_data_count() -> int: """Check if MTG data has been loaded.""" try: engine = create_async_engine(settings.MTG_DATABASE_URL) async with AsyncSession(engine) as session: stmt = text("SELECT COUNT(*) FROM mtg_cards") result = await session.execute(stmt) count = result.scalar() await engine.dispose() return count or 0 except Exception as e: logger.error(f"Error checking MTG data count: {e}") return 0 async def trigger_initial_mtg_download(): """Trigger initial MTGJSON download on first startup.""" global mtg_data_ready, mtg_data_count logger.info("Checking MTG data status...") count = await check_mtg_data_count() if count == 0: logger.info("No MTG data found. Triggering initial download...") mtg_data_ready = False try: # Import and run the refresh script from app.scripts.refresh_mtg import main as refresh_main await refresh_main() # Re-check count after download mtg_data_count = await check_mtg_data_count() mtg_data_ready = mtg_data_count > 0 if mtg_data_ready: logger.info(f"MTG data loaded successfully: {mtg_data_count} cards") else: logger.warning("MTG data download completed but no cards found") except Exception as e: logger.error(f"Failed to load MTG data: {e}") mtg_data_ready = False else: logger.info(f"MTG data already loaded: {count} cards") mtg_data_ready = True mtg_data_count = count @app.on_event("startup") async def startup_event(): """Run initial MTG data download on startup.""" logger.info("Starting MTG Online backend...") # Run MTG data download in background to not block startup asyncio.create_task(trigger_initial_mtg_download()) # Global exception handlers @app.exception_handler(Exception) async def global_exception_handler(request, exc): """Handle unhandled exceptions gracefully.""" return JSONResponse( status_code=500, content={"detail": "Internal server error"}, ) # Include routers app.include_router(auth.router, prefix="/api/v1/auth", tags=["Authentication"]) app.include_router(card_router.router, prefix="/api/v1/mtg/cards", tags=["MTG Cards"]) app.include_router(users.router, prefix="/api/v1/users", tags=["Users"]) app.include_router(decks.router, prefix="/api/v1/decks", tags=["Decks"]) app.include_router(rooms.router, prefix="/api/v1/rooms", tags=["Rooms"]) app.include_router(games.router, prefix="/api/v1/games", tags=["Games"]) app.include_router(admin.router, prefix="/api/v1/admin", tags=["Admin"]) @app.get("/health") async def health_check() -> Dict[str, Any]: """Health check endpoint with MTG data status.""" health_status = { "status": "healthy", "version": settings.APP_VERSION, "timestamp": datetime.now().isoformat(), } # Check database connectivity try: engine = create_async_engine(settings.DATABASE_URL) async with AsyncSession(engine) as session: await session.execute(text("SELECT 1")) await engine.dispose() health_status["database"] = "connected" except Exception as e: health_status["status"] = "degraded" health_status["database"] = f"error: {str(e)}" # Check MTG data status health_status["mtg_data"] = { "ready": mtg_data_ready, "count": mtg_data_count, } if not mtg_data_ready: health_status["status"] = "initializing" return health_status