WebSocket Message Schemas (WS-002): - Add Pydantic models for all client/server WebSocket messages - Implement discriminated unions for message type parsing - Include JoinGame, Action, Resign, Heartbeat client messages - Include GameState, ActionResult, Error, TurnStart server messages Connection Manager (WS-003): - Add Redis-backed WebSocket connection tracking - Implement user-to-sid mapping with TTL management - Support game room association and opponent lookup - Add heartbeat tracking for connection health Socket.IO Authentication (WS-004): - Add JWT-based authentication middleware - Support token extraction from multiple formats - Implement session setup with ConnectionManager integration - Add require_auth helper for event handlers Socket.IO Server Setup (WS-001): - Configure AsyncServer with ASGI mode - Register /game namespace with event handlers - Integrate with FastAPI via ASGIApp wrapper - Configure CORS from application settings Game Service (GS-001): - Add stateless GameService for game lifecycle orchestration - Create engine per-operation using rules from GameState - Implement action-based RNG seeding for deterministic replay - Add rng_seed field to GameState for replay support Architecture verified: - Core module independence (no forbidden imports) - Config from request pattern (rules in GameState) - Dependency injection (constructor deps, method config) - All 1090 tests passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
190 lines
5.3 KiB
Python
190 lines
5.3 KiB
Python
"""Mantimon TCG - FastAPI Application Entry Point.
|
|
|
|
This module configures and starts the FastAPI application with:
|
|
- Database initialization and cleanup
|
|
- Redis connection management
|
|
- Card service loading
|
|
- CORS middleware
|
|
- API routers
|
|
|
|
Usage:
|
|
uvicorn app.main:app --reload
|
|
"""
|
|
|
|
import logging
|
|
from collections.abc import AsyncGenerator
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from app.api.auth import router as auth_router
|
|
from app.api.collections import router as collections_router
|
|
from app.api.decks import router as decks_router
|
|
from app.api.users import router as users_router
|
|
from app.config import settings
|
|
from app.db import close_db, init_db
|
|
from app.db.redis import close_redis, init_redis
|
|
from app.services import get_card_service
|
|
from app.socketio import create_socketio_app
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
"""Application lifespan context manager.
|
|
|
|
Handles startup and shutdown events:
|
|
- Startup: Initialize DB, Redis, and load cards
|
|
- Shutdown: Close DB and Redis connections
|
|
|
|
Args:
|
|
app: The FastAPI application instance.
|
|
|
|
Yields:
|
|
None - Control returns to the application during its lifetime.
|
|
"""
|
|
# === STARTUP ===
|
|
logger.info("Starting Mantimon TCG server...")
|
|
|
|
# Initialize database connection pool
|
|
logger.info("Initializing database...")
|
|
await init_db()
|
|
logger.info("Database initialized")
|
|
|
|
# Initialize Redis connection pool
|
|
logger.info("Initializing Redis...")
|
|
await init_redis()
|
|
logger.info("Redis initialized")
|
|
|
|
# Load card definitions into memory
|
|
logger.info("Loading card definitions...")
|
|
card_service = get_card_service()
|
|
await card_service.load_all()
|
|
card_count = len(card_service.get_all_cards())
|
|
logger.info(f"Loaded {card_count} card definitions")
|
|
|
|
logger.info("Mantimon TCG server started successfully")
|
|
|
|
yield # Application runs here
|
|
|
|
# === SHUTDOWN ===
|
|
logger.info("Shutting down Mantimon TCG server...")
|
|
|
|
# Close Redis connections
|
|
logger.info("Closing Redis connections...")
|
|
await close_redis()
|
|
logger.info("Redis connections closed")
|
|
|
|
# Close database connections
|
|
logger.info("Closing database connections...")
|
|
await close_db()
|
|
logger.info("Database connections closed")
|
|
|
|
logger.info("Mantimon TCG server shutdown complete")
|
|
|
|
|
|
# Create FastAPI application with lifespan
|
|
app = FastAPI(
|
|
title="Mantimon TCG",
|
|
description="A home-rule-modified Pokemon Trading Card Game API",
|
|
version="0.1.0",
|
|
lifespan=lifespan,
|
|
docs_url="/docs" if settings.is_development else None,
|
|
redoc_url="/redoc" if settings.is_development else None,
|
|
)
|
|
|
|
# Configure CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
# === Health Check Endpoints ===
|
|
|
|
|
|
@app.get("/health")
|
|
async def health_check() -> dict[str, str]:
|
|
"""Basic health check endpoint.
|
|
|
|
Returns:
|
|
Health status indicating the server is running.
|
|
"""
|
|
return {"status": "healthy"}
|
|
|
|
|
|
@app.get("/health/ready")
|
|
async def readiness_check() -> dict[str, str | int]:
|
|
"""Readiness check with service status.
|
|
|
|
Verifies that all required services are available:
|
|
- Database connection
|
|
- Redis connection
|
|
- Card service loaded
|
|
|
|
Returns:
|
|
Detailed status of each service.
|
|
"""
|
|
from app.db import get_engine
|
|
from app.db.redis import get_pool
|
|
|
|
status: dict[str, str | int] = {"status": "ready"}
|
|
|
|
# Check database
|
|
try:
|
|
get_engine() # Raises RuntimeError if not initialized
|
|
status["database"] = "connected"
|
|
except RuntimeError:
|
|
status["database"] = "not initialized"
|
|
status["status"] = "not ready"
|
|
|
|
# Check Redis
|
|
try:
|
|
get_pool() # Raises RuntimeError if not initialized
|
|
status["redis"] = "connected"
|
|
except RuntimeError:
|
|
status["redis"] = "not initialized"
|
|
status["status"] = "not ready"
|
|
|
|
# Check card service
|
|
card_service = get_card_service()
|
|
card_count = len(card_service.get_all_cards())
|
|
if card_count > 0:
|
|
status["cards_loaded"] = card_count
|
|
else:
|
|
status["cards_loaded"] = 0
|
|
status["status"] = "not ready"
|
|
|
|
return status
|
|
|
|
|
|
# === API Routers ===
|
|
app.include_router(auth_router, prefix="/api")
|
|
app.include_router(users_router, prefix="/api")
|
|
app.include_router(collections_router, prefix="/api")
|
|
app.include_router(decks_router, prefix="/api")
|
|
|
|
# TODO: Add remaining routers in future phases
|
|
# from app.api import cards, games, campaign
|
|
# app.include_router(cards.router, prefix="/api/cards", tags=["cards"])
|
|
# app.include_router(games.router, prefix="/api/games", tags=["games"])
|
|
# app.include_router(campaign.router, prefix="/api/campaign", tags=["campaign"])
|
|
|
|
|
|
# === Socket.IO Integration ===
|
|
# Wrap FastAPI with Socket.IO ASGI app for WebSocket support.
|
|
# The combined app handles:
|
|
# - WebSocket connections at /socket.io/
|
|
# - HTTP requests passed through to FastAPI
|
|
#
|
|
# Note: The module-level 'app' variable is now the combined ASGI app.
|
|
# This is intentional - uvicorn imports 'app' and will serve both
|
|
# FastAPI HTTP routes and Socket.IO WebSocket connections.
|
|
_fastapi_app = app
|
|
app = create_socketio_app(_fastapi_app)
|