Updated backend
This commit is contained in:
65
app/main.py
65
app/main.py
@@ -10,9 +10,10 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse
|
||||
@@ -31,19 +32,17 @@ logging.basicConfig(
|
||||
)
|
||||
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",
|
||||
)
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI):
|
||||
"""
|
||||
Schema checks and auto-seeding, kicked off without blocking startup.
|
||||
|
||||
The work runs on a daemon thread rather than being awaited: it touches
|
||||
Postgres, which may be slow or briefly unreachable on a cold boot, and the
|
||||
server answering /api/health in under a second is what lets the container
|
||||
healthcheck pass while that settles.
|
||||
"""
|
||||
|
||||
@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()
|
||||
@@ -54,12 +53,42 @@ def _on_startup() -> None:
|
||||
logger.warning("Startup background init warning: %s", e)
|
||||
|
||||
threading.Thread(target=_async_init, daemon=True).start()
|
||||
yield
|
||||
|
||||
|
||||
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",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
# When the app is served same-origin (the frontend's nginx proxies /api/* to
|
||||
# this service), API_CORS_ORIGINS is empty and no cross-origin request is ever
|
||||
# made. It is populated only when the API is also published on its own host -
|
||||
# see api.{$DOMAIN} in the Caddyfile.
|
||||
#
|
||||
# A wildcard origin and credentialed requests are mutually exclusive under the
|
||||
# CORS spec: browsers reject `Access-Control-Allow-Origin: *` on any request
|
||||
# carrying credentials. Sending both is a silent misconfiguration - the server
|
||||
# looks configured while every browser call fails - so a wildcard turns
|
||||
# credentials off explicitly and says so in the log.
|
||||
_allow_credentials = "*" not in API_CORS_ORIGINS
|
||||
if not _allow_credentials:
|
||||
logger.warning(
|
||||
"API_CORS_ORIGINS contains '*': disabling allow_credentials, because "
|
||||
"browsers reject credentialed cross-origin requests to a wildcard origin. "
|
||||
"List the exact origins instead if you need cookies or Authorization headers."
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=API_CORS_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_credentials=_allow_credentials,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
@@ -90,8 +119,14 @@ if FRONTEND_DIST.exists() and (FRONTEND_DIST / "assets").exists():
|
||||
|
||||
@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
|
||||
# API and docs paths are served by the routers registered above, so
|
||||
# this catch-all only sees them when the path genuinely doesn't exist.
|
||||
# Returning None there would answer 200 with a `null` body - an unknown
|
||||
# endpoint would look like a successful call to any client. 404 is the
|
||||
# honest answer, and it matters now that the API is also reachable
|
||||
# directly at api.{$DOMAIN} rather than only behind the frontend.
|
||||
if full_path.startswith(("api", "docs", "redoc", "openapi.json")):
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
file_path = FRONTEND_DIST / full_path
|
||||
if file_path.exists() and file_path.is_file():
|
||||
return FileResponse(file_path)
|
||||
|
||||
Reference in New Issue
Block a user