from datetime import datetime, timezone from typing import Annotated, Optional from fastapi import APIRouter, Depends, HTTPException, Query from api import response from domain.audit.service import AuditService from domain.audit.types import AuditAction from domain.auth.service import get_current_active_user from domain.auth.types import User CurrentUser = Annotated[User, Depends(get_current_active_user)] router = APIRouter(prefix="/api/audit", tags=["Audit"]) def _parse_iso(value: Optional[str], field: str): if not value: return None try: normalized = value.replace("Z", "+00:00") dt = datetime.fromisoformat(normalized) if dt.tzinfo: dt = dt.astimezone(timezone.utc).replace(tzinfo=None) return dt except ValueError as exc: # noqa: BLE001 raise HTTPException(status_code=400, detail=f"invalid {field}") from exc @router.get("/logs") async def list_audit_logs( current_user: CurrentUser, page_num: int = Query(1, ge=1, alias="page", description="页码"), page_size: int = Query(20, ge=1, le=200, description="每页条数"), action: AuditAction | None = Query(None, description="操作类型"), success: bool | None = Query(None, description="是否成功"), username: str | None = Query(None, description="用户名模糊匹配"), path: str | None = Query(None, description="路径模糊匹配"), start_time: str | None = Query(None, description="开始时间 (ISO 8601)"), end_time: str | None = Query(None, description="结束时间 (ISO 8601)"), ): start_dt = _parse_iso(start_time, "start_time") end_dt = _parse_iso(end_time, "end_time") items, total = await AuditService.list_logs( page=page_num, page_size=page_size, action=str(action) if action else None, success=success, username=username, path=path, start_time=start_dt, end_time=end_dt, ) return response.success(response.page(items, total, page_num, page_size)) @router.delete("/logs") async def clear_audit_logs( current_user: CurrentUser, start_time: str | None = Query(None, description="开始时间 (ISO 8601)"), end_time: str | None = Query(None, description="结束时间 (ISO 8601)"), ): start_dt = _parse_iso(start_time, "start_time") end_dt = _parse_iso(end_time, "end_time") if start_dt is None and end_dt is None: raise HTTPException(status_code=400, detail="start_time 或 end_time 至少提供一个") deleted_count = await AuditService.clear_logs(start_time=start_dt, end_time=end_dt) return response.success({"deleted_count": deleted_count})