Files
echoes-of-the-ash/api/main_new.py
2025-11-27 16:27:01 +01:00

171 lines
5.2 KiB
Python

"""
Echoes of the Ashes - Main FastAPI Application
Streamlined with modular routers for maintainability
"""
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager
from pathlib import Path
import logging
# Import core modules
from .core.config import CORS_ORIGINS, IMAGES_DIR
from .core.websockets import manager
from .core.security import get_current_user
# Import database and game data
from . import database as db
from .world_loader import load_world, World, Location
from .items import ItemsManager
from . import background_tasks
from .redis_manager import redis_manager
# Import routers
from .routers import auth, characters
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Load game data
print("🔄 Loading game world...")
WORLD: World = load_world()
LOCATIONS = WORLD.locations
ITEMS_MANAGER = ItemsManager()
print(f"✅ Game world ready: {len(LOCATIONS)} locations, {len(ITEMS_MANAGER.items)} items")
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager for startup/shutdown"""
# Startup
await db.init_db()
print("✅ Database initialized")
# Connect to Redis
await redis_manager.connect()
print("✅ Redis connected")
# Inject Redis manager into ConnectionManager
manager.set_redis_manager(redis_manager)
# Subscribe to all location channels + global broadcast
location_channels = [f"location:{loc_id}" for loc_id in LOCATIONS.keys()]
await redis_manager.subscribe_to_channels(location_channels + ['game:broadcast'])
print(f"✅ Subscribed to {len(location_channels)} location channels")
# Register this worker
await redis_manager.register_worker()
print(f"✅ Worker registered: {redis_manager.worker_id}")
# Start Redis message listener (background task)
redis_manager.start_listener(manager.handle_redis_message)
print("✅ Redis listener started")
# Start background tasks (distributed via Redis locks)
tasks = await background_tasks.start_background_tasks(manager, LOCATIONS)
if tasks:
print(f"✅ Started {len(tasks)} background tasks in this worker")
else:
print("⏭️ Background tasks running in another worker")
yield
# Shutdown
await background_tasks.stop_background_tasks(tasks)
# Unregister worker
await redis_manager.unregister_worker()
print(f"🔌 Worker unregistered: {redis_manager.worker_id}")
# Disconnect from Redis
await redis_manager.disconnect()
print("✅ Redis disconnected")
# Initialize FastAPI app
app = FastAPI(
title="Echoes of the Ashes API",
version="2.0.0",
description="Post-apocalyptic survival RPG API",
lifespan=lifespan
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Mount static files for images
if IMAGES_DIR.exists():
app.mount("/images", StaticFiles(directory=str(IMAGES_DIR)), name="images")
print(f"✅ Mounted images directory: {IMAGES_DIR}")
else:
print(f"⚠️ Images directory not found: {IMAGES_DIR}")
# Include routers
app.include_router(auth.router)
app.include_router(characters.router)
# TODO: Add remaining routers as they are created:
# app.include_router(game_routes.router)
# app.include_router(combat.router)
# app.include_router(equipment.router)
# app.include_router(crafting.router)
# app.include_router(loot.router)
# app.include_router(admin.router)
# app.include_router(statistics.router)
@app.get("/health")
async def health_check():
"""Health check endpoint for load balancers"""
return {"status": "ok"}
@app.websocket("/ws")
async def websocket_endpoint(
websocket: WebSocket,
current_user: dict = Depends(get_current_user)
):
"""WebSocket endpoint for real-time game updates"""
player_id = current_user['id']
username = current_user['name']
await manager.connect(websocket, player_id, username)
# Get player's location and register in Redis
location_id = current_user.get('location_id')
if location_id and redis_manager:
await redis_manager.add_player_to_location(location_id, player_id)
# Store session data
await redis_manager.update_player_session(player_id, {
'username': username,
'location_id': location_id,
'level': current_user.get('level', 1),
'websocket_connected': 'true'
})
try:
while True:
# Keep connection alive
data = await websocket.receive_text()
# You can handle client messages here if needed
logger.debug(f"Received from {username}: {data}")
except WebSocketDisconnect:
await manager.disconnect(player_id)
# Remove from location registry
if location_id and redis_manager:
await redis_manager.remove_player_from_location(location_id, player_id)
print(f"WebSocket disconnected: {username}")