""" Audit API Routes для DAARION City Service /api/v1/audit/* """ from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel from typing import Optional, List import logging import repo_governance as repo logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/audit", tags=["audit"]) # ============================================================================= # Pydantic Models # ============================================================================= from datetime import datetime as dt class AuditEvent(BaseModel): id: str event_type: str payload: Optional[dict] = None status: Optional[str] = None created_at: Optional[dt] = None actor_id: Optional[str] = None target_id: Optional[str] = None scope: Optional[str] = None class AuditStats(BaseModel): total_events: int events_24h: int events_7d: int unique_actors: int unique_targets: int top_event_types: List[dict] # ============================================================================= # Routes # ============================================================================= @router.get("/events", response_model=List[AuditEvent]) async def get_audit_events( limit: int = Query(50, ge=1, le=500), offset: int = Query(0, ge=0), event_type: Optional[str] = None, actor_id: Optional[str] = None, target_id: Optional[str] = None, scope: Optional[str] = None ): """ Отримати події аудиту з фільтрами Параметри: - limit: кількість записів (1-500) - offset: зміщення для пагінації - event_type: фільтр по типу події (agent.promoted, agent.revoked, etc.) - actor_id: фільтр по актору - target_id: фільтр по цілі - scope: фільтр по scope (city, district, microdao) """ try: events = await repo.get_audit_events( limit=limit, offset=offset, event_type=event_type, actor_id=actor_id, target_id=target_id, scope=scope ) return events except Exception as e: logger.error(f"Error fetching audit events: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/events/{event_id}", response_model=AuditEvent) async def get_audit_event(event_id: str): """ Отримати подію аудиту за ID """ try: event = await repo.get_audit_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail="Event not found") return event except HTTPException: raise except Exception as e: logger.error(f"Error fetching audit event: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/actor/{actor_id}", response_model=List[AuditEvent]) async def get_events_by_actor( actor_id: str, limit: int = Query(50, ge=1, le=500) ): """ Отримати події по актору (хто виконав дію) """ try: events = await repo.get_audit_events_by_actor(actor_id, limit=limit) return events except Exception as e: logger.error(f"Error fetching events by actor: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/target/{target_id}", response_model=List[AuditEvent]) async def get_events_by_target( target_id: str, limit: int = Query(50, ge=1, le=500) ): """ Отримати події по цілі (над ким виконано дію) """ try: events = await repo.get_audit_events_by_target(target_id, limit=limit) return events except Exception as e: logger.error(f"Error fetching events by target: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/stats", response_model=AuditStats) async def get_audit_stats(): """ Отримати статистику аудиту Повертає: - total_events: загальна кількість подій - events_24h: події за останні 24 години - events_7d: події за останні 7 днів - unique_actors: кількість унікальних акторів - unique_targets: кількість унікальних цілей - top_event_types: топ-10 типів подій """ try: stats = await repo.get_audit_stats() return stats except Exception as e: logger.error(f"Error fetching audit stats: {e}") raise HTTPException(status_code=500, detail=str(e))