mattzh72--articraft
58 行
2.3 KiB
Python
58 行
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.responses import FileResponse
|
|
|
|
|
|
def install_frontend_routes(app: FastAPI, *, dist_dir: Path | None = None) -> None:
|
|
frontend_dist = dist_dir or Path(__file__).resolve().parents[1] / "web" / "dist"
|
|
if frontend_dist.exists():
|
|
resolved_dist_dir = frontend_dist.resolve()
|
|
|
|
@app.get("/", include_in_schema=False)
|
|
async def frontend_index() -> FileResponse:
|
|
return FileResponse(
|
|
resolved_dist_dir / "index.html",
|
|
headers={"Cache-Control": "no-cache"},
|
|
)
|
|
|
|
@app.get("/{full_path:path}", include_in_schema=False)
|
|
async def frontend_fallback(full_path: str) -> FileResponse:
|
|
if (
|
|
full_path == "api"
|
|
or full_path.startswith("api/")
|
|
or full_path in {"health", "docs", "redoc", "openapi.json"}
|
|
):
|
|
raise HTTPException(status_code=404)
|
|
requested_path = Path(full_path)
|
|
if requested_path.is_absolute() or ".." in requested_path.parts:
|
|
raise HTTPException(status_code=404)
|
|
target = (resolved_dist_dir / requested_path).resolve()
|
|
try:
|
|
target.relative_to(resolved_dist_dir)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=404) from exc
|
|
if target.exists() and target.is_file():
|
|
cache_control = (
|
|
"public, max-age=31536000, immutable"
|
|
if target.parts[-2:-1] == ("assets",)
|
|
else "no-cache"
|
|
)
|
|
return FileResponse(target, headers={"Cache-Control": cache_control})
|
|
if requested_path.parts[:1] == ("assets",):
|
|
raise HTTPException(status_code=404)
|
|
return FileResponse(
|
|
resolved_dist_dir / "index.html",
|
|
headers={"Cache-Control": "no-cache"},
|
|
)
|
|
|
|
return
|
|
|
|
@app.get("/", include_in_schema=False)
|
|
async def frontend_missing() -> dict[str, str]:
|
|
return {
|
|
"message": "Frontend build not found. Run `npm --prefix viewer/web install && npm --prefix viewer/web run build`."
|
|
}
|