Files
CapacityReport/app/api/routers/api_tokens.py
T

136 lines
4.6 KiB
Python

from typing import Any
from fastapi import APIRouter, Body, HTTPException, Request
from app.auth import resolve_login_context
from app.services.api_tokens import create_token, delete_token, delete_tokens, list_tokens, regenerate_token, update_token
router = APIRouter(tags=["api-tokens"])
def _require_login(request: Request) -> None:
if resolve_login_context(request) is None:
raise HTTPException(status_code=401, detail="未登录或登录已过期")
def _bad_expiration_error(exc: ValueError) -> HTTPException:
return HTTPException(status_code=400, detail="到期日期格式无效,请使用 YYYY-MM-DD 或 ISO 日期时间")
def _resolve_expires_at(payload: dict[str, Any]) -> str | None:
if bool(payload.get("permanent", False)):
return None
expires_at = str(payload.get("expires_at") or "").strip()
if not expires_at:
raise HTTPException(status_code=400, detail="请选择 Token 到期日期,或设置为永久有效")
return expires_at
@router.get("/api/tokens")
async def get_tokens(request: Request):
_require_login(request)
return {"success": True, "tokens": list_tokens()}
@router.post("/api/tokens/create")
async def create_api_token(request: Request, payload: dict[str, Any] = Body(...)):
_require_login(request)
name = str(payload.get("name", "")).strip()
enabled = bool(payload.get("enabled", True))
raw_expires_at = _resolve_expires_at(payload)
try:
raw_token, record = create_token(
name=name,
expires_at=raw_expires_at,
enabled=enabled,
)
except ValueError as exc:
raise _bad_expiration_error(exc) from exc
return {
"success": True,
"message": "API Token 创建成功",
"token": raw_token,
"record": record,
}
@router.post("/api/tokens/update")
async def update_api_token(request: Request, payload: dict[str, Any] = Body(...)):
_require_login(request)
token_id = str(payload.get("id", "")).strip()
if not token_id:
raise HTTPException(status_code=400, detail="缺少 Token ID")
changes: dict[str, Any] = {}
if "name" in payload:
changes["name"] = payload.get("name")
if "enabled" in payload:
changes["enabled"] = payload.get("enabled")
if "permanent" in payload or "expires_at" in payload:
changes["expires_at"] = _resolve_expires_at(payload)
try:
record = update_token(token_id, **changes)
return {"success": True, "message": "API Token 已更新", "record": record}
except ValueError as exc:
raise _bad_expiration_error(exc) from exc
except KeyError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.post("/api/tokens/regenerate")
async def regenerate_api_token(request: Request, payload: dict[str, Any] = Body(...)):
_require_login(request)
token_id = str(payload.get("id", "")).strip()
if not token_id:
raise HTTPException(status_code=400, detail="缺少 Token ID")
try:
raw_token, record = regenerate_token(token_id)
return {
"success": True,
"message": "API Token 已重新生成",
"token": raw_token,
"record": record,
}
except KeyError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.post("/api/tokens/delete")
async def delete_api_token(request: Request, payload: dict[str, Any] = Body(...)):
_require_login(request)
token_id = str(payload.get("id", "")).strip()
if not token_id:
raise HTTPException(status_code=400, detail="缺少 Token ID")
delete_token(token_id)
return {"success": True, "message": "API Token 已删除"}
@router.post("/api/tokens/batch-delete")
async def batch_delete_api_tokens(request: Request, payload: dict[str, Any] = Body(...)):
_require_login(request)
token_ids = payload.get("ids", [])
if not isinstance(token_ids, list) or not token_ids:
raise HTTPException(status_code=400, detail="请选择要删除的 Token")
deleted_count = delete_tokens([str(token_id) for token_id in token_ids])
return {"success": True, "message": f"已删除 {deleted_count} 个 API Token", "deleted_count": deleted_count}
@router.get("/api/docs-info")
async def docs_info(request: Request):
_require_login(request)
return {
"success": True,
"docs_url": "/api/docs-ui",
"openapi_url": "/api/openapi.json",
"token_header": "Authorization: Bearer <token>",
"alt_header": "X-API-Token: <token>",
"note": "API 文档仅登录后可访问。",
}