Files
bttoxin-pipeline/backend/app/main_spa.py
zly 9835b6e341 feat(deploy): fix docker deployment and add backend i18n
- Docker Deployment Fixes:
  - Switch base images to docker.m.daocloud.io to resolve registry 401 errors
  - Add Postgres and Redis services to docker-compose.traefik.yml
  - Fix frontend build: replace missing icons (Globe->Location, Chart->TrendCharts)
  - Fix frontend build: resolve pnpm CI/TTY issues and frozen lockfile errors
  - Add missing backend dependencies (sqlalchemy, psycopg2, redis-py, celery, docker-py) in pixi.toml
  - Ensure database tables are created on startup (lifespan event)

- Backend Internationalization (i18n):
  - Add backend/app/core/i18n.py for locale handling
  - Update API endpoints (jobs, tasks, uploads, results) to return localized messages
  - Support 'Accept-Language' header (en/zh)

- Documentation:
  - Update DOCKER_DEPLOYMENT.md with new architecture and troubleshooting
  - Update AGENTS.md with latest stack details and deployment steps
  - Update @fix_plan.md status

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 12:38:54 +08:00

77 lines
2.3 KiB
Python

"""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 .database import Base, engine
from .api.v1 import jobs, upload, results, tasks
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
# 启动时
print("🚀 Starting BtToxin Pipeline API...")
# 创建数据库表
Base.metadata.create_all(bind=engine)
print("✅ Database tables created")
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")