venthrive-api / main.py
GitHub Actions
Sync from GitHub · 2026-10-22 23:15 UTC
b324c71
Raw History Blame Contribute Delete
4.94 kB
import os
import json
from contextlib import asynccontextmanager
# ─── Load environment FIRST — before any local imports that read env vars ─────
# Routes instantiate Groq/Firebase clients at module level, so env vars must
# be present before those modules are imported.
from dotenv import load_dotenv
load_dotenv()
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import firebase_admin
from firebase_admin import credentials
from routes import bff, chat, transcribe, documents, consultation, planning, admin, account, help as help_routes, news, bsa as bsa_routes, bff_form
# ─── Firebase Admin SDK initialisation ───────────────────────────────────────
def _init_firebase():
if firebase_admin._apps:
return # already initialised (e.g. during hot-reload)
sa_json = os.environ.get("FIREBASE_SERVICE_ACCOUNT")
if sa_json:
try:
cred_dict = json.loads(sa_json)
cred = credentials.Certificate(cred_dict)
except json.JSONDecodeError as e:
raise RuntimeError(f"FIREBASE_SERVICE_ACCOUNT is not valid JSON: {e}")
else:
# Fall back to application default credentials (GCP environment)
cred = credentials.ApplicationDefault()
firebase_admin.initialize_app(cred)
_init_firebase()
# ─── App lifecycle ────────────────────────────────────────────────────────────
@asynccontextmanager
async def lifespan(app: FastAPI):
print("VenThrive API starting up...")
yield
print("VenThrive API shutting down.")
# ─── FastAPI app ──────────────────────────────────────────────────────────────
app = FastAPI(
title="VenThrive API",
version="1.0.0",
description="AI backend for VenThrive — Nigerian business operating system",
# Disable auto-generated docs in production
docs_url="/docs" if os.environ.get("ENV") != "production" else None,
redoc_url=None,
lifespan=lifespan,
)
# ─── CORS ─────────────────────────────────────────────────────────────────────
allowed_origins_str = os.environ.get("ALLOWED_ORIGINS", "http://localhost:3000")
allowed_origins = [o.strip() for o in allowed_origins_str.split(",")]
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
)
# ─── Global error handler ─────────────────────────────────────────────────────
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
# Never expose internal error details to the client in production
return JSONResponse(
status_code=500,
content={"detail": "An unexpected error occurred. Please try again."},
)
# ─── Routes ───────────────────────────────────────────────────────────────────
app.include_router(bff.router, prefix="/api/bff", tags=["BFF"])
app.include_router(chat.router, prefix="/api/chat", tags=["Chat"])
app.include_router(transcribe.router, prefix="/api", tags=["Transcribe"])
app.include_router(documents.router, prefix="/api/documents", tags=["Documents"])
app.include_router(consultation.router, prefix="/api/consultation", tags=["Consultation"])
app.include_router(planning.router, prefix="/api/planning", tags=["Planning"])
app.include_router(admin.router, prefix="/api/admin", tags=["Admin"])
app.include_router(account.router, prefix="/api/account", tags=["Account"])
app.include_router(help_routes.router, prefix="/api/help", tags=["Help"])
app.include_router(news.router, prefix="/api/news", tags=["News"])
app.include_router(bsa_routes.router, prefix="/api/bsa", tags=["BSA"])
app.include_router(bff_form.router, prefix="/api/bff-form", tags=["BFF form"])
# ─── Health check (unauthenticated) ──────────────────────────────────────────
@app.get("/health", tags=["System"])
def health_check():
return {"status": "ok", "service": "venthrive-api"}