Files
mtgonline/backend/app/main.py
T

156 lines
4.8 KiB
Python

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