""" FastAPI application entry point. Run with: uvicorn app.main:app --reload --port 8000 (see backend/README.md / the project documentation for full setup steps) """ from __future__ import annotations import logging import threading from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from app.infrastructure.settings import API_CORS_ORIGINS from app.api.routers import health, brands, search, chat, catalog, system from app.api.routers import stores, discounts, analytics as store_analytics, trending, recommendations, store_admin from app.api.routers import nutrition, nutrition_admin, upload from app.api.routers import auth, user_products, admin_train from app.services.store_db import ensure_store_intelligence_schema from app.services.nutrition_db import ensure_nutrition_schema logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) app = FastAPI( title="Brand Product Search Engine - RAG API", description=( "Local, CPU-only RAG API over an Indian FMCG product catalog stored in pgvector. " "Includes automated multi-store intelligence, ML discount engines, and nutrition intelligence." ), version="3.2.0", ) @app.on_event("startup") def _on_startup() -> None: # Asynchronously ensure schemas and background auto-init so server starts instantly (<1s) def _async_init(): try: ensure_store_intelligence_schema() ensure_nutrition_schema() from app.api.routers.system import _run_background_auto_seed _run_background_auto_seed() except Exception as e: logger.warning("Startup background init warning: %s", e) threading.Thread(target=_async_init, daemon=True).start() app.add_middleware( CORSMiddleware, allow_origins=API_CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(health.router, prefix="/api") app.include_router(auth.router, prefix="/api") app.include_router(user_products.router, prefix="/api") app.include_router(admin_train.router, prefix="/api") app.include_router(system.router, prefix="/api") app.include_router(brands.router, prefix="/api") app.include_router(search.router, prefix="/api") app.include_router(chat.router, prefix="/api") app.include_router(catalog.router, prefix="/api") app.include_router(stores.router, prefix="/api") app.include_router(discounts.router, prefix="/api") app.include_router(store_analytics.router, prefix="/api") app.include_router(trending.router, prefix="/api") app.include_router(recommendations.router, prefix="/api") app.include_router(store_admin.router, prefix="/api") app.include_router(nutrition.router, prefix="/api") app.include_router(nutrition_admin.router, prefix="/api") app.include_router(upload.router, prefix="/api") # Serve built frontend static files if dist exists (single-port unified deployment) FRONTEND_DIST = Path(__file__).resolve().parents[2] / "frontend" / "dist" if FRONTEND_DIST.exists() and (FRONTEND_DIST / "assets").exists(): app.mount("/assets", StaticFiles(directory=str(FRONTEND_DIST / "assets")), name="assets") @app.get("/{full_path:path}") def serve_frontend(full_path: str): if full_path.startswith("api") or full_path.startswith("docs") or full_path.startswith("openapi.json"): return None file_path = FRONTEND_DIST / full_path if file_path.exists() and file_path.is_file(): return FileResponse(file_path) return FileResponse(FRONTEND_DIST / "index.html") else: @app.get("/") def root() -> dict: return { "service": "Brand Product Search Engine - RAG API", "docs": "/docs", "health": "/api/health", "system_status": "/api/system/status", }