101 lines
2.8 KiB
Python
101 lines
2.8 KiB
Python
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"<failed to read body: {body_error}>"
|
|
|
|
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",
|
|
}
|