from contextlib import asynccontextmanager import logging from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from starlette.datastructures import Headers from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.sessions import SessionMiddleware from app.api.v1.routes import router as v1_router from app.routes.pages import router as pages_router from app.routes.auth import router as auth_router from app.routes.admin_users import router as admin_users_router from app.routes.admin_api_keys import router as admin_api_keys_router from app.middleware.auth_middleware import WebAuthenticationMiddleware from app.core.config import settings from app.db.init_db import init_db # Configure logging for better error visibility logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) logging.getLogger("uvicorn.error").setLevel(logging.DEBUG) logging.getLogger("uvicorn.access").setLevel(logging.INFO) logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) class ForceHTTPSMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): request.scope["scheme"] = "https" response = await call_next(request) return response class ForwardedProtoMiddleware: def __init__(self, app): self.app = app async def __call__(self, scope, receive, send): if scope["type"] in {"http", "websocket"}: headers = Headers(scope=scope) forwarded_proto = headers.get("x-forwarded-proto") if forwarded_proto: proto = forwarded_proto.split(",", 1)[0].strip() if proto: new_scope = dict(scope) new_scope["scheme"] = proto return await self.app(new_scope, receive, send) return await self.app(scope, receive, send) origins = [ "http://localhost:8040", "https://ai.sriphat.com", "http://ai.sriphat.com", ] @asynccontextmanager async def lifespan(_: FastAPI): init_db() yield app = FastAPI(title=settings.APP_NAME, root_path=settings.ROOT_PATH, lifespan=lifespan) # Add exception handler to log all errors with traceback @app.exception_handler(Exception) async def global_exception_handler(request, exc): import traceback logging.error(f"Unhandled exception: {exc}") logging.error(traceback.format_exc()) from starlette.responses import JSONResponse return JSONResponse( status_code=500, content={"detail": "Internal server error", "error": str(exc)} ) # Middleware order is important! They execute in reverse order (LIFO) # WebAuthenticationMiddleware needs SessionMiddleware, so SessionMiddleware must be added AFTER app.add_middleware(ForceHTTPSMiddleware) app.add_middleware(ForwardedProtoMiddleware) app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Add web authentication middleware (protects /, /docs, /data-management/* only) # API endpoints (/api/v1/*) continue to use API Key authentication app.add_middleware(WebAuthenticationMiddleware) # SessionMiddleware must be added AFTER middlewares that use it (due to LIFO execution) app.add_middleware(SessionMiddleware, secret_key=settings.ADMIN_SECRET_KEY) app.include_router(v1_router) # API endpoints - use API Key auth app.include_router(pages_router) # Web pages - use Keycloak auth app.include_router(auth_router) # Authentication routes app.include_router(admin_users_router) # Admin user management API app.include_router(admin_api_keys_router) # API key management - use Keycloak admin auth