from datetime import datetime from typing import Literal from uuid import UUID from fastapi import APIRouter, Depends, Query, Request, status from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.auth.metadata_dependencies import ( get_current_metadata_admin, get_current_metadata_user, ) from app.core.audit import AuditAction, log_audit_event from app.domain.schemas.audit import AuditLogResponse from app.infra.db.metadb.database import get_metadata_session from app.infra.db.metadb.repositories.audit_repository import AuditRepository router = APIRouter() class SessionAuditEventRequest(BaseModel): event: Literal["login", "logout"] async def get_audit_repository( session: AsyncSession = Depends(get_metadata_session), ) -> AuditRepository: return AuditRepository(session) @router.get( "/audit-logs", summary="查询审计日志", description="查询审计日志(仅管理员)", response_model=list[AuditLogResponse], ) async def get_audit_logs( user_id: UUID | None = Query(None, description="按用户ID过滤"), project_id: UUID | None = Query(None, description="按项目ID过滤"), action: str | None = Query(None, description="按操作类型过滤"), resource_type: str | None = Query(None, description="按资源类型过滤"), start_time: datetime | None = Query(None, description="开始时间"), end_time: datetime | None = Query(None, description="结束时间"), skip: int = Query(0, ge=0, description="跳过记录数"), limit: int = Query(100, ge=1, le=1000, description="限制记录数"), _current_user=Depends(get_current_metadata_admin), audit_repo: AuditRepository = Depends(get_audit_repository), ) -> list[AuditLogResponse]: return await audit_repo.get_logs( user_id=user_id, project_id=project_id, action=action, resource_type=resource_type, start_time=start_time, end_time=end_time, skip=skip, limit=limit, ) @router.get( "/audit-logs/count", summary="获取审计日志总数", description="获取审计日志总数(仅管理员)", ) async def get_audit_logs_count( user_id: UUID | None = Query(None, description="按用户ID过滤"), project_id: UUID | None = Query(None, description="按项目ID过滤"), action: str | None = Query(None, description="按操作类型过滤"), resource_type: str | None = Query(None, description="按资源类型过滤"), start_time: datetime | None = Query(None, description="开始时间"), end_time: datetime | None = Query(None, description="结束时间"), _current_user=Depends(get_current_metadata_admin), audit_repo: AuditRepository = Depends(get_audit_repository), ) -> dict: count = await audit_repo.get_log_count( user_id=user_id, project_id=project_id, action=action, resource_type=resource_type, start_time=start_time, end_time=end_time, ) return {"count": count} @router.post("/audit-events", status_code=status.HTTP_204_NO_CONTENT) async def record_session_event( payload: SessionAuditEventRequest, request: Request, current_user=Depends(get_current_metadata_user), session: AsyncSession = Depends(get_metadata_session), ) -> None: await log_audit_event( action=AuditAction.LOGIN if payload.event == "login" else AuditAction.LOGOUT, user_id=current_user.id, resource_type="session", resource_id=str(current_user.keycloak_id), ip_address=request.client.host if request.client else None, request_method=request.method, request_path=request.url.path, response_status=status.HTTP_204_NO_CONTENT, session=session, ) @router.get( "/audit-logs/mine", summary="查询我的审计日志", description="查询当前用户的审计日志", response_model=list[AuditLogResponse], ) async def get_my_audit_logs( action: str | None = Query(None, description="按操作类型过滤"), start_time: datetime | None = Query(None, description="开始时间"), end_time: datetime | None = Query(None, description="结束时间"), skip: int = Query(0, ge=0, description="跳过记录数"), limit: int = Query(100, ge=1, le=1000, description="限制记录数"), current_user=Depends(get_current_metadata_user), audit_repo: AuditRepository = Depends(get_audit_repository), ) -> list[AuditLogResponse]: return await audit_repo.get_logs( user_id=current_user.id, action=action, start_time=start_time, end_time=end_time, skip=skip, limit=limit, )