109 lines
4.3 KiB
Python

"""
🦊 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"}