97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
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"}
|