feat: Docker deploy serving SPA and API on :8080

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
肉末
2026-07-14 13:03:51 +08:00
parent 600c536367
commit 7c2489ea5c
5 changed files with 149 additions and 1 deletions
+34 -1
View File
@@ -1,7 +1,10 @@
import asyncio
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.admin import router as admin_router
from app.api.public import router as public_router
@@ -11,6 +14,20 @@ from app import models # noqa: F401 — register models with Base.metadata
from app.services import cleaner as cleaner_service
def _resolve_frontend_dist() -> Path | None:
"""Locate frontend/dist in Docker (/app/frontend/dist) or monorepo checkout."""
here = Path(__file__).resolve()
candidates = (
here.parents[2] / "frontend" / "dist", # .../backend/app/main.py → repo root
here.parents[1] / "frontend" / "dist", # /app/app/main.py → /app/frontend/dist
Path("/app/frontend/dist"),
)
for path in candidates:
if (path / "index.html").is_file():
return path
return None
async def _cleaner_loop() -> None:
while True:
db = SessionLocal()
@@ -47,3 +64,19 @@ app.include_router(admin_router)
@app.get("/api/health")
def health():
return {"ok": True, "name": "MincedPad"}
_dist = _resolve_frontend_dist()
if _dist is not None:
assets_dir = _dist / "assets"
if assets_dir.is_dir():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
@app.api_route("/{full_path:path}", methods=["GET", "HEAD"])
async def spa(full_path: str):
if full_path == "api" or full_path.startswith("api/"):
raise HTTPException(status_code=404, detail="Not Found")
file_path = _dist / full_path
if full_path and file_path.is_file():
return FileResponse(file_path)
return FileResponse(_dist / "index.html")