Files
MincedPad/backend/app/main.py
T
2026-07-14 13:03:51 +08:00

83 lines
2.5 KiB
Python

import asyncio
from contextlib import asynccontextmanager
from pathlib import Path
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
from app.config import settings
from app.db import Base, SessionLocal, engine
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()
try:
cleaner_service.run_once(db)
except Exception:
pass
finally:
db.close()
await asyncio.sleep(60)
@asynccontextmanager
async def lifespan(_app: FastAPI):
settings.data_dir.mkdir(parents=True, exist_ok=True)
settings.uploads_dir.mkdir(parents=True, exist_ok=True)
Base.metadata.create_all(bind=engine)
task = asyncio.create_task(_cleaner_loop())
try:
yield
finally:
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
app = FastAPI(title="MincedPad", lifespan=lifespan)
app.include_router(public_router)
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")