from contextlib import asynccontextmanager import logging import os from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from starlette.datastructures import Headers from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.sessions import SessionMiddleware import sqladmin from app.admin import mount_admin from app.api.v1.routes import router as v1_router 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("sqladmin").setLevel(logging.DEBUG) 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 sqladmin_dir = os.path.dirname(sqladmin.__file__) statics_path = os.path.join(sqladmin_dir, "statics") 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)} ) app.add_middleware(ForceHTTPSMiddleware) app.add_middleware(SessionMiddleware, secret_key=settings.ADMIN_SECRET_KEY) app.add_middleware(ForwardedProtoMiddleware) app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(v1_router) app.mount("/admin/statics", StaticFiles(directory=statics_path), name="admin_statics") app.mount("/apiservice/admin/statics", StaticFiles(directory=statics_path), name="proxy_admin_statics") mount_admin(app)