botWebWars/backend/app/main.py

96 lines
3.1 KiB
Python

import json
import logging
from pathlib import Path
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from app.api.routes import router as api_router
from app.api.websocket import manager
from app.config import settings
from app.game import game_engine
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
app = FastAPI(
title="botWebWars API",
description="Backend service for botWebWars 64x64 grid battle arena.",
version="1.0.0",
)
# Enable CORS for frontend development
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Mount REST API routes
app.include_router(api_router, prefix="/api")
# WebSocket endpoint for real-time board updates
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
# Immediately transmit current board state on connection
state = await game_engine.get_board_state()
await websocket.send_text(
json.dumps({"event": "init", "state": state.model_dump()}, default=str)
)
while True:
# Keep connection open, receive ping or client commands
data = await websocket.receive_text()
try:
msg = json.loads(data)
if msg.get("action") == "ping":
await websocket.send_text(json.dumps({"event": "pong"}))
except json.JSONDecodeError:
pass
except WebSocketDisconnect:
manager.disconnect(websocket)
except Exception as e:
logger.error(f"WebSocket error: {e}")
manager.disconnect(websocket)
# Mount static frontend files if built
frontend_dist = settings.FRONTEND_DIR
if frontend_dist.exists() and (frontend_dist / "index.html").exists():
logger.info(f"Serving frontend static files from: {frontend_dist}")
# Mount assets folder
assets_dir = frontend_dist / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
# SPA catch-all for root and unmatched GET paths (excluding /api and /ws)
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
# If the requested file directly exists inside frontend_dist, serve it
target_file = frontend_dist / full_path
if full_path and target_file.is_file():
return FileResponse(target_file)
# Otherwise fallback to index.html for SPA routing
return FileResponse(frontend_dist / "index.html")
else:
logger.info("Frontend dist directory not found. Running in API-only mode.")
@app.get("/")
async def root():
return {
"message": "Welcome to botWebWars API! Frontend build not detected yet.",
"docs": "/docs",
"health": "/api/health",
"board": "/api/board",
}