Files
Project_Velocity/backend/main.py
2026-04-11 19:33:13 +05:30

118 lines
4.0 KiB
Python

"""
The Catalyst — FastAPI Backend
Autonomous Digital Marketing Agency powered by Meta Marketing API.
"""
import os
import json
import asyncio
from datetime import datetime
from typing import Set
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
from api.routes_catalyst import router as catalyst_router
from oracle.router_v1 import router as oracle_router
load_dotenv()
# ── App ───────────────────────────────────────────────────────────────────────
app = FastAPI(
title="Velocity — Catalyst Backend",
description="Meta Marketing API integration for autonomous campaign management.",
version="1.0.0",
)
# ── CORS ──────────────────────────────────────────────────────────────────────
origins = [o.strip() for o in os.getenv("CORS_ORIGINS", "http://localhost:5173").split(",")]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ── Routers ───────────────────────────────────────────────────────────────────
app.include_router(catalyst_router, prefix="/api/catalyst", tags=["Catalyst"])
app.include_router(oracle_router, prefix="/api/oracle/v1", tags=["Oracle"])
# ── WebSocket — Live Optimization Feed ────────────────────────────────────────
class ConnectionManager:
"""Manages active WebSocket connections for live optimization broadcasts."""
def __init__(self) -> None:
self.active: Set[WebSocket] = set()
async def connect(self, ws: WebSocket) -> None:
await ws.accept()
self.active.add(ws)
def disconnect(self, ws: WebSocket) -> None:
self.active.discard(ws)
async def broadcast(self, payload: dict) -> None:
dead: Set[WebSocket] = set()
for ws in self.active:
try:
await ws.send_text(json.dumps(payload))
except Exception:
dead.add(ws)
self.active -= dead
manager = ConnectionManager()
@app.websocket("/ws/catalyst")
async def websocket_endpoint(ws: WebSocket) -> None:
"""
WebSocket endpoint for streaming live Claw Agent optimization events.
Clients connect from <LiveOptimizationFeed /> in Catalyst.tsx.
"""
await manager.connect(ws)
try:
while True:
# Keep-alive: wait for any incoming ping/message
data = await ws.receive_text()
# Echo back as acknowledgment (clients may send heartbeat pings)
await ws.send_text(json.dumps({"type": "ack", "data": data}))
except WebSocketDisconnect:
manager.disconnect(ws)
# ── Helper: broadcast a live event (called from routes after API mutations) ───
async def broadcast_live_event(
event_type: str,
message: str,
campaign_name: str | None = None,
value: str | None = None,
) -> None:
payload = {
"type": event_type,
"message": message,
"campaignName": campaign_name,
"value": value,
"timestamp": datetime.utcnow().isoformat(),
}
await manager.broadcast(payload)
# Attach broadcaster so routes can call it
app.state.broadcast_live_event = broadcast_live_event
# ── Health check ──────────────────────────────────────────────────────────────
@app.get("/health", tags=["Health"])
async def health() -> dict:
return {"status": "ok", "service": "catalyst-backend", "version": "1.0.0"}