- Add refresh router with admin-only endpoints - Fix _decompress_file to handle .psql files correctly - Fix duplicate imports in mtgjson_manager.py - Mount refresh router in main.py - Add download_mtgjson.py standalone script - Add SUPPORTED_FILE_TYPES.md documentation
174 lines
5.6 KiB
Python
174 lines
5.6 KiB
Python
"""
|
|
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
|
|
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.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",
|
|
} |