import logging from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.config import settings from app.database import Base, SessionLocal, engine, ensure_database_schema from app.routers import html from app.routers.html import cleanup_expired_files logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) logger = logging.getLogger(__name__) settings.html_storage_dir.mkdir(parents=True, exist_ok=True) Base.metadata.create_all(bind=engine) ensure_database_schema() app = FastAPI( title=settings.app_name, version="2.0.0", description=( "Store agent-generated educational HTML pages and return a direct access URL. " "The generated OpenAPI document can be imported directly into Tencent Cloud " "Agent plugins." ), ) app.add_middleware( CORSMiddleware, allow_origins=settings.allowed_origins, allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) app.include_router(html.router, prefix=settings.api_prefix) def _truncate_for_log(value: str) -> str: limit = settings.request_log_max_chars if len(value) <= limit: return value return f"{value[:limit]}... [truncated {len(value) - limit} chars]" @app.exception_handler(RequestValidationError) async def validation_exception_handler( request: Request, exc: RequestValidationError, ) -> JSONResponse: if settings.enable_request_debug_log: try: raw_body = (await request.body()).decode("utf-8", errors="replace") except Exception as body_error: raw_body = f"" interesting_headers = { key: value for key, value in request.headers.items() if key.lower() in {"content-type", "content-length", "x-api-key", "user-agent"} } logger.error( "422 validation error: method=%s path=%s query=%s headers=%s errors=%s body=%s", request.method, request.url.path, dict(request.query_params), interesting_headers, exc.errors(), _truncate_for_log(raw_body), ) return JSONResponse( status_code=422, content={"detail": exc.errors()}, ) @app.on_event("startup") def cleanup_on_startup() -> None: db = SessionLocal() try: deleted_count = cleanup_expired_files(db) if deleted_count > 0: logger.info("Deleted %s expired HTML files during startup", deleted_count) finally: db.close() @app.get("/", summary="Health check") def health_check() -> dict[str, str]: return { "message": "HTML Knowledge API is running", "openapi_url": "/openapi.json", }