From 9a2a2f6adc0521813dfab3709b645f89d640921f Mon Sep 17 00:00:00 2001 From: Artem Kashaev Date: Mon, 1 Dec 2025 14:25:11 +0500 Subject: [PATCH] feat: add static file serving and frontend asset handling to FastAPI application --- app/main.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index f8ba4ad..15c65ce 100644 --- a/app/main.py +++ b/app/main.py @@ -4,8 +4,11 @@ from __future__ import annotations from collections.abc import AsyncIterator from contextlib import asynccontextmanager +from pathlib import Path -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles from app.api.routes import api_router from app.core.cache import init_cache, shutdown_cache @@ -14,6 +17,10 @@ from app.core.middleware.cache_monitor import CacheAvailabilityMiddleware from fastapi.middleware.cors import CORSMiddleware +PROJECT_ROOT = Path(__file__).resolve().parent.parent +FRONTEND_DIST = PROJECT_ROOT / "frontend" / "dist" +FRONTEND_INDEX = FRONTEND_DIST / "index.html" + def create_app() -> FastAPI: """Build FastAPI application instance.""" @@ -42,6 +49,25 @@ def create_app() -> FastAPI: allow_methods=["*"], # Разрешить все HTTP-методы allow_headers=["*"], # Разрешить все заголовки ) + if FRONTEND_DIST.exists() and FRONTEND_INDEX.exists(): + assets_dir = FRONTEND_DIST / "assets" + if assets_dir.exists(): + application.mount("/assets", StaticFiles(directory=assets_dir), name="frontend-assets") + + @application.get("/", include_in_schema=False) + async def serve_frontend_root() -> FileResponse: # pragma: no cover - simple file response + return FileResponse(FRONTEND_INDEX) + + @application.get("/{path:path}", include_in_schema=False) + async def serve_frontend_path(path: str) -> FileResponse: # pragma: no cover - simple file response + if path == "" or path.startswith("api"): + raise HTTPException(status_code=404) + + candidate = FRONTEND_DIST / path + if candidate.is_file(): + return FileResponse(candidate) + return FileResponse(FRONTEND_INDEX) + return application