Commit
This commit is contained in:
170
api/main_new.py
Normal file
170
api/main_new.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
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}")
|
||||
Reference in New Issue
Block a user