from __future__ import annotations import logging from fastapi import FastAPI, HTTPException, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from app.api.routes_admin import router as admin_router from app.api.routes_auth import router as auth_router from app.api.routes_student import router as student_router from app.core.database import get_connection from app.core.settings import FRONTEND_ORIGINS, UPLOADS_DIR, ensure_runtime_dirs from app.services.repository import init_db logger = logging.getLogger(__name__) ERROR_DETAIL_MAP: dict[str, str] = { "missing_token": "缺少认证令牌", "invalid_token": "认证已过期,请重新登录", "invalid_credentials": "用户名或密码错误", "student_only": "此功能仅限学生使用", "admin_only": "此功能仅限管理员使用", "order_not_found": "工单不存在或已被删除", "session_not_found": "诊断会话已过期,请重新描述故障", "order_not_completed": "维修尚未完成,无法执行此操作", "order_not_ready_for_feedback": "当前状态不可评价", "order_cannot_cancel": "当前状态无法取消,仅已提交或待处理的工单可取消", "order_not_in_rework": "当前工单不在返工申请状态", "unsupported_file_type": "仅支持上传图片文件(jpg, png, gif, webp)", "file_too_large": "文件大小超过限制(最大10MB)", "validation_error": "请求参数有误", } ensure_runtime_dirs() with get_connection() as conn: init_db(conn) conn.execute("DELETE FROM sessions WHERE expires_at < datetime('now')") conn.commit() app = FastAPI(title="Dorm Repair API", version="0.1.0") app.add_middleware( CORSMiddleware, allow_origins=FRONTEND_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(auth_router) app.include_router(student_router) app.include_router(admin_router) app.mount("/uploads", StaticFiles(directory=UPLOADS_DIR), name="uploads") @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse: error_code = str(exc.detail) if not isinstance(exc.detail, dict) else exc.detail.get("error_code", str(exc.detail)) detail = ERROR_DETAIL_MAP.get(error_code, str(exc.detail)) return JSONResponse( status_code=exc.status_code, content={"detail": detail, "error_code": error_code}, ) @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse: return JSONResponse( status_code=422, content={"detail": "请求参数有误", "error_code": "validation_error"}, ) @app.exception_handler(Exception) async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse: logger.exception("Unhandled exception: %s", exc) return JSONResponse( status_code=500, content={"detail": "服务器内部错误", "error_code": "internal_error"}, ) @app.on_event("shutdown") async def shutdown() -> None: from app.services.diagnosis import close_diagnosis_provider await close_diagnosis_provider() @app.get("/api/health") def health() -> dict[str, str]: return {"status": "ok"}