"""Service for creating and querying audit log entries.""" import asyncio import csv import io import json import uuid from datetime import datetime from typing import Optional from sqlalchemy.ext.asyncio import AsyncSession from sqlmodel import select from models.sql.audit_log import AuditLogModel from services.database import async_session_maker def log( user_id: Optional[uuid.UUID], action: str, resource_type: str, resource_id: Optional[uuid.UUID] = None, client_id: Optional[uuid.UUID] = None, details: Optional[dict] = None, ip_address: Optional[str] = None, ) -> None: """Fire-and-forget audit log entry creation.""" asyncio.create_task( _write_log(user_id, action, resource_type, resource_id, client_id, details, ip_address) ) async def _write_log( user_id: Optional[uuid.UUID], action: str, resource_type: str, resource_id: Optional[uuid.UUID], client_id: Optional[uuid.UUID], details: Optional[dict], ip_address: Optional[str], ) -> None: try: async with async_session_maker() as session: entry = AuditLogModel( user_id=user_id, action=action, resource_type=resource_type, resource_id=resource_id, client_id=client_id, details=details, ip_address=ip_address, ) session.add(entry) await session.commit() except Exception as e: # Audit logging should never break the request print(f"Audit log error: {e}") async def query( session: AsyncSession, user_id: Optional[uuid.UUID] = None, action: Optional[str] = None, resource_type: Optional[str] = None, client_id: Optional[uuid.UUID] = None, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None, offset: int = 0, limit: int = 50, ) -> list: """Query audit logs with optional filters.""" q = select(AuditLogModel) if user_id: q = q.where(AuditLogModel.user_id == user_id) if action: q = q.where(AuditLogModel.action == action) if resource_type: q = q.where(AuditLogModel.resource_type == resource_type) if client_id: q = q.where(AuditLogModel.client_id == client_id) if date_from: q = q.where(AuditLogModel.created_at >= date_from) if date_to: q = q.where(AuditLogModel.created_at <= date_to) q = q.order_by(AuditLogModel.created_at.desc()).offset(offset).limit(limit) result = await session.execute(q) return list(result.scalars().all()) def export_csv(logs: list) -> str: """Export audit logs as CSV string.""" output = io.StringIO() writer = csv.writer(output) writer.writerow(["id", "user_id", "action", "resource_type", "resource_id", "client_id", "details", "ip_address", "created_at"]) for log_entry in logs: writer.writerow([ str(log_entry.id), str(log_entry.user_id) if log_entry.user_id else "", log_entry.action, log_entry.resource_type, str(log_entry.resource_id) if log_entry.resource_id else "", str(log_entry.client_id) if log_entry.client_id else "", json.dumps(log_entry.details) if log_entry.details else "", log_entry.ip_address or "", log_entry.created_at.isoformat() if log_entry.created_at else "", ]) return output.getvalue() def export_json(logs: list) -> str: """Export audit logs as JSON string.""" entries = [] for log_entry in logs: entries.append({ "id": str(log_entry.id), "user_id": str(log_entry.user_id) if log_entry.user_id else None, "action": log_entry.action, "resource_type": log_entry.resource_type, "resource_id": str(log_entry.resource_id) if log_entry.resource_id else None, "client_id": str(log_entry.client_id) if log_entry.client_id else None, "details": log_entry.details, "ip_address": log_entry.ip_address, "created_at": log_entry.created_at.isoformat() if log_entry.created_at else None, }) return json.dumps(entries, indent=2)