""" 🦊 Foxy Dev Team — Backend API v2.0 FastAPI application with WebSocket support for real-time dashboard. """ import logging from contextlib import asynccontextmanager from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse import os from app.config import settings from app.database import init_db from app.routers import projects, agents, logs, workflows, config from app.routers.ws import manager # ─── Logging ─────────────────────────────────────────────────────────────────── logging.basicConfig( level=getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO), format="[%(asctime)s] %(levelname)s %(name)s — %(message)s", datefmt="%Y-%m-%dT%H:%M:%SZ", ) log = logging.getLogger("foxy.main") # ─── Lifespan ────────────────────────────────────────────────────────────────── @asynccontextmanager async def lifespan(app: FastAPI): log.info("🦊 Foxy Dev Team API v2.0 — Starting...") await init_db() log.info("✅ Database initialized") yield log.info("🛑 Foxy Dev Team API — Shutting down") # ─── App ─────────────────────────────────────────────────────────────────────── app = FastAPI( title="🦊 Foxy Dev Team API", description="Backend API for the Foxy Dev Team multi-agent orchestration system", version="2.0.0", lifespan=lifespan, ) # CORS app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ─── Routers ─────────────────────────────────────────────────────────────────── app.include_router(projects.router) app.include_router(agents.router) app.include_router(logs.router) app.include_router(workflows.router) app.include_router(config.router) # ─── Static Files ───────────────────────────────────────────────────────────── if os.path.exists("static"): app.mount("/assets", StaticFiles(directory="static/assets"), name="assets") @app.get("/{full_path:path}") async def serve_spa(full_path: str): # API requests should already be handled by routers # If not an API request, serve index.html if full_path.startswith("api/") or full_path.startswith("ws/"): return None # Should not happen due to router order index_path = os.path.join("static", "index.html") if os.path.exists(index_path): return FileResponse(index_path) return {"detail": "Frontend not found"} # ─── WebSocket ───────────────────────────────────────────────────────────────── @app.websocket("/ws/live") async def websocket_endpoint(websocket: WebSocket): await manager.connect(websocket) try: while True: # Keep connection alive; client-initiated messages can be handled here data = await websocket.receive_text() # Echo ping/pong or handle subscriptions if data == "ping": await websocket.send_text('{"type":"pong"}') except WebSocketDisconnect: await manager.disconnect(websocket) except Exception: await manager.disconnect(websocket) # ─── Health ──────────────────────────────────────────────────────────────────── @app.get("/api/health") async def health(): return {"status": "ok", "version": "2.0.0", "service": "foxy-dev-team"}