ThreadHouse / app /main.py
paudelapil's picture
Project do-over
65fa0c5
Raw
History Blame Contribute Delete
4.21 kB
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from app.db.session import engine, Base
from app.db.asyncpg_pool import close_asyncpg_pool, init_asyncpg_pool
# Intel routers (SQLAlchemy)
from app.routers import admin, query, results, upload
# Shop routers (asyncpg)
from app.routers import analytics, auth, orders, products, admin_orders, intel_live
@asynccontextmanager
async def lifespan(app: FastAPI):
try:
from app.db import models
Base.metadata.create_all(bind=engine)
print("SQLAlchemy tables ready: jobs, customer_profiles, insights.")
except Exception as e:
print(f"SQLAlchemy startup error: {e}")
try:
await init_asyncpg_pool()
except Exception as e:
print(f"asyncpg startup error: {e}")
raise
try:
yield
finally:
try:
await close_asyncpg_pool()
print("asyncpg pool closed")
except Exception as e:
print(f"Warning: error closing pool: {e}")
app = FastAPI(
title="ThreadHouse + Customer Intelligence Engine",
version="1.0.0",
lifespan=lifespan,
docs_url="/docs",
redoc_url="/redoc",
)
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"http://(localhost|127\.0\.0\.1):\d+",
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
# Intel / CIE routers - match what frontend/src/services/api.js expects.
app.include_router(upload.router, prefix="/api", tags=["Upload"])
app.include_router(results.router, prefix="/api", tags=["Results"])
app.include_router(query.router, prefix="/api", tags=["Query"])
app.include_router(admin.router, prefix="/api", tags=["Admin"])
# Shop routers - match what Login.jsx, ShopContext.jsx, useAnalytics.js expect.
app.include_router(auth.router, prefix="/api/auth", tags=["Auth"])
app.include_router(orders.router, prefix="/api/orders", tags=["Orders"])
app.include_router(analytics.router, prefix="/api/analytics", tags=["Analytics"])
# Legacy alias for admin/Login.jsx (POST /api/user/admin)
app.include_router(auth.admin_alias_router, prefix="/api/user", tags=["Auth (legacy alias)"])
# Admin panel singular-path endpoints.
app.include_router(products.router, prefix="/api/product", tags=["Products (admin)"])
app.include_router(admin_orders.router, prefix="/api/order", tags=["Orders (admin)"])
app.include_router(intel_live.router, prefix="/api/intel", tags=["Intel (live)"])
# Static files (product images uploaded via the admin panel).
import os as _os
_STATIC_DIR = _os.path.join(_os.path.dirname(__file__), "..", "static")
_os.makedirs(_os.path.join(_STATIC_DIR, "images"), exist_ok=True)
app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
@app.get("/health")
def health():
return {"status": "healthy"}
# Optional: if the frontend has been built into ../frontend/dist, serve it.
FRONTEND_DIST = os.path.join(os.path.dirname(__file__), "..", "..", "frontend", "dist")
if os.path.exists(FRONTEND_DIST):
app.mount(
"/assets",
StaticFiles(directory=os.path.join(FRONTEND_DIST, "assets")),
name="assets",
)
@app.get("/{full_path:path}", include_in_schema=False)
async def serve_react(full_path: str):
if full_path.startswith("api/"):
raise HTTPException(status_code=404, detail="API route not found")
index_file = os.path.join(FRONTEND_DIST, "index.html")
if os.path.exists(index_file):
return FileResponse(index_file)
return {"error": "Frontend not built yet."}
else:
@app.get("/", include_in_schema=False)
def root():
return {
"message": "ThreadHouse + Customer Intelligence Engine API",
"note": "Frontend not built. Run: cd frontend && npm run build",
"docs": "/docs",
}