mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-13 08:03:54 +00:00
- Add new routers: admin, appointments, campaigns - Add storage service and logging config - Add migrations directory and test suite with pytest config - Add supabase_migration_features.sql - Update models, dependencies, config, and existing routers - Remove whatsapp_service (deleted) - Update pyproject.toml and uv.lock dependencies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
124 lines
5.6 KiB
Python
124 lines
5.6 KiB
Python
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}")
|
|
yield
|
|
logger.info("Contexta API shutting down...")
|
|
|
|
|
|
# ── 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) |