""" MTG Online Backend Application FastAPI application for the MTG Online multiplayer platform. Mounts all routers and provides centralized configuration. ## Routers - Authentication: /auth/* - Users: /users/* - Decks: /decks/* - Rooms: /rooms/* - Games: /games/* - Admin: /admin/* - MTG Cards: /api/cards/* - Card Interactions: /interactions/* """ import asyncio import logging from contextlib import asynccontextmanager from typing import AsyncGenerator from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.core.settings import get_settings from app.core.database import engine, mtg_engine, async_session, mtg_async_session from app.routers import auth, users, decks, rooms, games, admin, card_router, interactions, refresh, user_data, card_import from app.services.mtgjson_manager import MTGJSONManager def setup_logging(debug: bool = False) -> None: """Configure application logging with verbose support.""" level = logging.DEBUG if debug else logging.INFO logging.basicConfig( level=level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(), ] ) if debug: logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING) logging.getLogger('sqlalchemy.pool').setLevel(logging.WARNING) logger = logging.getLogger(__name__) logger.info(f"Logging initialized at level {logging.getLevelName(level)}") async def run_initial_download(): """Run initial MTGJSON data download and upsert.""" logger = logging.getLogger(__name__) try: settings = get_settings() manager = MTGJSONManager(settings.DATA_DIR) # Check if data already exists last_refresh = await manager.get_last_refresh() if last_refresh: logger.info(f"MTGJSON data already exists (last refresh: {last_refresh})") return logger.info("Running initial MTGJSON data download with sanity checks...") logger.info("This may take several minutes depending on network speed...") # Download files and upsert data in one operation result = await manager.download_and_refresh(force=False) if not result.get("success", False): error_msg = result.get("error", "Unknown error") logger.error(f"Failed to download MTGJSON files: {error_msg}") raise RuntimeError(f"MTGJSON data download failed: {error_msg}") # Log success counts = result.get("upsert", {}) await manager.log_refresh("SUCCESS", counts, result.get("duration", 0)) logger.info(f"Initial MTGJSON data load complete!") logger.info(f" Sets: {counts.get('sets', 0)}") logger.info(f" Cards: {counts.get('cards', 0)}") except Exception as e: logger.error(f"Failed to run initial download: {e}") raise def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """Application lifespan events for startup and shutdown.""" settings = get_settings() # Startup setup_logging(debug=settings.DEBUG) logger = logging.getLogger(__name__) logger.info(f"MTG Online Backend starting (v{settings.APP_VERSION})") logger.info(f"Database: {settings.DATABASE_URL.split('@')[1] if '@' in settings.DATABASE_URL else 'configured'}") logger.info(f"MTG Database: {settings.MTG_DATABASE_URL.split('@')[1] if '@' in settings.MTG_DATABASE_URL else 'configured'}") logger.info(f"Redis: {settings.REDIS_URL}") # Run initial download in background try: asyncio.create_task(run_initial_download()) except RuntimeError: # Event loop already running asyncio.get_event_loop().create_task(run_initial_download()) yield # Shutdown logger.info("Shutting down MTG Online Backend") # Engine disposal is handled by FastAPI's shutdown events app = FastAPI( title="MTG Online Backend API", description="Backend API for the MTG Online multiplayer platform", version="0.2.0", lifespan=lifespan, ) # CORS middleware settings = get_settings() app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Mount all routers app.include_router(auth.router, prefix="/auth", tags=["Authentication"]) app.include_router(users.router, prefix="/users", tags=["Users"]) app.include_router(decks.router, prefix="/decks", tags=["Decks"]) app.include_router(rooms.router, prefix="/rooms", tags=["Rooms"]) app.include_router(games.router, prefix="/games", tags=["Games"]) app.include_router(admin.router, prefix="/admin", tags=["Admin"]) app.include_router(card_router.router, prefix="/api", tags=["MTG Cards"]) app.include_router(interactions.router, tags=["Card Interactions"]) app.include_router(refresh.router) app.include_router(user_data.router, prefix="/api/v1/user-data", tags=["User Data"]) app.include_router(card_import.router, prefix="/api/v1/card-import", tags=["Card Import"]) @app.get("/health", tags=["Health"]) async def health_check(): """Health check endpoint with MTGJSON data status.""" from app.services.mtgjson_manager import get_manager # Get MTGJSON health status try: manager = get_manager() mtg_status = await manager.get_health_status() except Exception as e: mtg_status = { "status": "unhealthy", "error": str(e), } return { "status": "healthy" if mtg_status.get("status") == "healthy" else "degraded", "version": settings.APP_VERSION, "mtgjson": mtg_status, } @app.get("/", tags=["Root"]) async def root(): """Root endpoint with API information.""" return { "name": settings.APP_NAME, "version": settings.APP_VERSION, "docs": "/docs", }