from __future__ import annotations from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from .api import router as api_router from .config import get_control_settings from .db import ControlDB, set_control_db def resolve_frontend_dist() -> Path: # control/backend/app -> control/frontend/dist return Path(__file__).resolve().parents[2] / "frontend" / "dist" @asynccontextmanager async def lifespan(app: FastAPI): from .envfile import ensure_env_control_defaults # 首次启动补全 .env.control 缺项(已有值不覆盖) ensure_env_control_defaults() db = ControlDB() set_control_db(db) yield db.close() set_control_db(None) app = FastAPI( title="比特骆驼中控", version="0.1.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=[], allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) app.include_router(api_router) @app.get("/health") async def health() -> dict: s = get_control_settings() return {"ok": True, "app": "control", "sse_interval_sec": s.control_sse_interval_sec} _DIST = resolve_frontend_dist() if (_DIST / "assets").is_dir(): app.mount("/assets", StaticFiles(directory=str(_DIST / "assets")), name="assets") @app.get("/") @app.get("/monitor") @app.get("/stats") @app.get("/settings") @app.get("/login") async def spa(): index = _DIST / "index.html" if index.exists(): return FileResponse(index) return { "ok": True, "msg": "control frontend not built; cd control/frontend && npm ci && npm run build", "health": "/health", }