diff --git a/backend/app/main_spa.py b/backend/app/main_spa.py new file mode 100644 index 0000000..c2b4a6d --- /dev/null +++ b/backend/app/main_spa.py @@ -0,0 +1,72 @@ +"""FastAPI 主应用 - with SPA static file serving""" +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse +from pathlib import Path +from contextlib import asynccontextmanager + +from .config import settings +from .api.v1 import jobs, upload, results, tasks + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """应用生命周期管理""" + # 启动时 + print("🚀 Starting BtToxin Pipeline API...") + yield + # 关闭时 + print("👋 Shutting down BtToxin Pipeline API...") + + +app = FastAPI( + title=settings.APP_NAME, + version=settings.APP_VERSION, + description="Automated Bacillus thuringiensis toxin mining pipeline", + lifespan=lifespan +) + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=settings.CORS_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 路由 +app.include_router(jobs.router, prefix=f"{settings.API_V1_STR}/jobs", tags=["jobs"]) +app.include_router(tasks.router, prefix=f"{settings.API_V1_STR}/tasks", tags=["tasks"]) +app.include_router(upload.router, prefix=f"{settings.API_V1_STR}/upload", tags=["upload"]) +app.include_router(results.router, prefix=f"{settings.API_V1_STR}/results", tags=["results"]) + + +@app.get("/") +async def root(): + return { + "name": settings.APP_NAME, + "version": settings.APP_VERSION, + "status": "healthy" + } + + +@app.get("/health") +async def health(): + return {"status": "ok"} + + +# Mount static files for frontend (in production) +# The frontend dist files are served from /app/frontend/dist in the container +frontend_dist_path = Path("/app/frontend/dist") +if frontend_dist_path.exists(): + app.mount("/assets", StaticFiles(directory=str(frontend_dist_path / "assets")), name="assets") + + @app.get("/{full_path:path}", include_in_schema=False) + async def serve_spa(full_path: str): + """Serve frontend SPA - all routes go to index.html""" + file_path = frontend_dist_path / full_path + if file_path.exists() and file_path.is_file(): + return FileResponse(file_path) + return FileResponse(frontend_dist_path / "index.html") diff --git a/docker/dockerfiles/Dockerfile.traefik b/docker/dockerfiles/Dockerfile.traefik index 489ae23..6e729b1 100644 --- a/docker/dockerfiles/Dockerfile.traefik +++ b/docker/dockerfiles/Dockerfile.traefik @@ -14,7 +14,7 @@ COPY pixi.toml . COPY pyproject.toml . COPY scripts/ scripts/ COPY bttoxin/ bttoxin/ -COPY web/ web/ +COPY backend/ backend/ COPY Data/ Data/ # Install all pixi environments @@ -31,8 +31,7 @@ FROM node:20 AS frontend-builder WORKDIR /app -# Copy frontend source from builder -COPY --from=builder /app/web ../web/ +# Copy frontend source COPY frontend/ . RUN npm install -g pnpm && \ @@ -60,7 +59,7 @@ COPY --from=builder /app/.pixi /app/.pixi COPY --from=builder /shell-hook.sh /shell-hook.sh # Copy backend code -COPY --from=builder /app/web /app/web +COPY --from=builder /app/backend /app/backend COPY --from=builder /app/Data /app/Data COPY --from=builder /app/bttoxin /app/bttoxin COPY --from=builder /app/scripts /app/scripts @@ -77,11 +76,12 @@ EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:8000/api/health || exit 1 + CMD curl -f http://localhost:8000/health || exit 1 # Entrypoint ENTRYPOINT ["/bin/bash", "/shell-hook.sh"] # Command: Start FastAPI backend # FastAPI will serve both API and frontend static files -CMD ["uvicorn", "web.backend.main:app", "--host", "0.0.0.0", "--port", "8000"] +# Note: Using backend.app.main_spa for SPA static file support +CMD ["uvicorn", "backend.app.main_spa:app", "--host", "0.0.0.0", "--port", "8000"]