mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-12 23:23:21 +00:00
145 lines
6.5 KiB
Python
145 lines
6.5 KiB
Python
import asyncio
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse, Response
|
|
import logging
|
|
|
|
from app.logging_config import configure_logging
|
|
configure_logging() # Must be called before any logger is created
|
|
|
|
from app.config import settings
|
|
from app.routers import auth, chatbots, documents, chat, marketplace, billing, models, analytics, inbox, leads, upload
|
|
from app.routers.documents import router_url_sources
|
|
from app.routers.leads import leads_public_router
|
|
from app.routers.channels import router as channels_router, webhook_router as channels_webhook_router
|
|
from app.routers import admin as admin_router
|
|
from app.routers.appointments import router as appointments_router, public_router as appointments_public_router
|
|
from app.routers.campaigns import router as campaigns_router
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
logger.info("Contexta API starting up...")
|
|
logger.info(f"Environment: {settings.app_env}")
|
|
logger.info(f"Allowed origins: {settings.allowed_origins_list}")
|
|
asyncio.create_task(_requeue_pending_url_sources())
|
|
yield
|
|
logger.info("Contexta API shutting down...")
|
|
|
|
|
|
async def _requeue_pending_url_sources():
|
|
"""Re-queue any url_sources stuck in pending/processing from a previous crash."""
|
|
try:
|
|
from app.database import get_supabase
|
|
from app.routers.documents import _process_url_source
|
|
supabase = get_supabase()
|
|
stuck = supabase.table("url_sources") \
|
|
.select("id, url, chatbot_id") \
|
|
.in_("status", ["pending", "processing"]) \
|
|
.execute()
|
|
if not stuck.data:
|
|
return
|
|
logger.info(f"Re-queuing {len(stuck.data)} stuck URL source(s) from previous run")
|
|
for src in stuck.data:
|
|
asyncio.create_task(_process_url_source(src["id"], src["url"], src["chatbot_id"], supabase))
|
|
except Exception as e:
|
|
logger.warning(f"Failed to re-queue pending URL sources: {e}")
|
|
|
|
|
|
# ── App ──────────────────────────────────────────────────────────────────────────
|
|
app = FastAPI(
|
|
title="Contexta API",
|
|
description="AI Chatbot Platform - Create, deploy and share custom AI chatbots powered by your data",
|
|
version="1.0.0",
|
|
docs_url="/docs",
|
|
redoc_url="/redoc",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# ── Middleware ─────────────────────────────────────────────────────────────────
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.allowed_origins_list,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# ── Routers ────────────────────────────────────────────────────────────────────
|
|
app.include_router(auth.router, prefix="/api/v1")
|
|
app.include_router(chatbots.router, prefix="/api/v1")
|
|
app.include_router(documents.router, prefix="/api/v1")
|
|
app.include_router(chat.router, prefix="/api/v1")
|
|
app.include_router(marketplace.router, prefix="/api/v1")
|
|
app.include_router(billing.router, prefix="/api/v1")
|
|
app.include_router(models.router, prefix="/api/v1")
|
|
app.include_router(analytics.router, prefix="/api/v1")
|
|
app.include_router(inbox.router, prefix="/api/v1")
|
|
app.include_router(leads.router, prefix="/api/v1")
|
|
app.include_router(upload.router, prefix="/api/v1")
|
|
app.include_router(router_url_sources, prefix="/api/v1")
|
|
app.include_router(leads_public_router, prefix="/api/v1")
|
|
app.include_router(channels_router, prefix="/api/v1")
|
|
app.include_router(channels_webhook_router, prefix="/api/v1")
|
|
app.include_router(appointments_router, prefix="/api/v1")
|
|
app.include_router(appointments_public_router, prefix="/api/v1")
|
|
app.include_router(campaigns_router, prefix="/api/v1")
|
|
app.include_router(admin_router.router, prefix="/api/v1")
|
|
|
|
|
|
# ── Widget ─────────────────────────────────────────────────────────────────────
|
|
@app.get("/widget.js", include_in_schema=False)
|
|
async def serve_widget():
|
|
from app.services.widget import generate_widget_js
|
|
return Response(
|
|
content=generate_widget_js(settings.app_url),
|
|
media_type="application/javascript",
|
|
headers={
|
|
# Allow any site to load this script tag cross-origin
|
|
"Access-Control-Allow-Origin": "*",
|
|
# Cache for 1 hour in browsers / CDN; revalidate when stale
|
|
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
|
|
# Prevent MIME sniffing
|
|
"X-Content-Type-Options": "nosniff",
|
|
},
|
|
)
|
|
|
|
|
|
# ── Health & Info ──────────────────────────────────────────────────────────────
|
|
@app.get("/")
|
|
async def root():
|
|
return {
|
|
"name": "Contexta API",
|
|
"version": "1.0.0",
|
|
"status": "running",
|
|
"docs": "/docs",
|
|
}
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "healthy", "environment": settings.app_env}
|
|
|
|
|
|
# ── Prometheus Metrics ──────────────────────────────────────────────────────────
|
|
try:
|
|
from prometheus_fastapi_instrumentator import Instrumentator
|
|
Instrumentator().instrument(app).expose(app, endpoint="/metrics", include_in_schema=False)
|
|
logger.info("Prometheus metrics enabled at /metrics")
|
|
except ImportError:
|
|
logger.info("prometheus-fastapi-instrumentator not installed, metrics endpoint disabled")
|
|
|
|
|
|
# ── Sentry ─────────────────────────────────────────────────────────────────────
|
|
if settings.sentry_dsn:
|
|
import sentry_sdk
|
|
sentry_sdk.init(dsn=settings.sentry_dsn, traces_sample_rate=0.1)
|
|
logger.info("Sentry initialized")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True) |