"""CRUD operations for Audit Log model.""" import json from datetime import datetime, timedelta from typing import Optional, List, Any from sqlalchemy.orm import Session from sqlalchemy import func, desc from app.models.audit_log import AuditLog from app.schemas.audit_log import AuditLogCreate, AuditLogFilter class CRUDAuditLog: """CRUD operations for Audit Log model.""" def create( self, db: Session, *, obj_in: AuditLogCreate ) -> AuditLog: """Create a new audit log entry.""" db_obj = AuditLog( user_id=obj_in.user_id, username=obj_in.username, action=obj_in.action, resource_type=obj_in.resource_type, resource_id=obj_in.resource_id, details=obj_in.details, ip_address=obj_in.ip_address, user_agent=obj_in.user_agent, status=obj_in.status ) db.add(db_obj) db.commit() db.refresh(db_obj) return db_obj def log_action( self, db: Session, *, user_id: Optional[str] = None, username: Optional[str] = None, action: str, resource_type: Optional[str] = None, resource_id: Optional[str] = None, details: Optional[dict] = None, ip_address: Optional[str] = None, user_agent: Optional[str] = None, status: str = "success" ) -> AuditLog: """Convenience method to log an action.""" details_str = json.dumps(details) if details else None obj_in = AuditLogCreate( user_id=user_id, username=username, action=action, resource_type=resource_type, resource_id=resource_id, details=details_str, ip_address=ip_address, user_agent=user_agent, status=status ) return self.create(db, obj_in=obj_in) def get(self, db: Session, id: str) -> Optional[AuditLog]: """Get a single audit log entry by ID.""" return db.query(AuditLog).filter(AuditLog.id == id).first() def get_multi( self, db: Session, *, skip: int = 0, limit: int = 100, filters: Optional[AuditLogFilter] = None ) -> tuple[List[AuditLog], int]: """Get multiple audit log entries with optional filtering.""" query = db.query(AuditLog) if filters: if filters.user_id: query = query.filter(AuditLog.user_id == filters.user_id) if filters.username: query = query.filter(AuditLog.username.ilike(f"%{filters.username}%")) if filters.action: query = query.filter(AuditLog.action == filters.action) if filters.resource_type: query = query.filter(AuditLog.resource_type == filters.resource_type) if filters.resource_id: query = query.filter(AuditLog.resource_id == filters.resource_id) if filters.status: query = query.filter(AuditLog.status == filters.status) if filters.start_date: query = query.filter(AuditLog.created_at >= filters.start_date) if filters.end_date: query = query.filter(AuditLog.created_at <= filters.end_date) total = query.count() items = query.order_by(desc(AuditLog.created_at)).offset(skip).limit(limit).all() return items, total def get_by_user( self, db: Session, *, user_id: str, skip: int = 0, limit: int = 100 ) -> List[AuditLog]: """Get audit logs for a specific user.""" return db.query(AuditLog)\ .filter(AuditLog.user_id == user_id)\ .order_by(desc(AuditLog.created_at))\ .offset(skip)\ .limit(limit)\ .all() def get_recent( self, db: Session, *, hours: int = 24, limit: int = 100 ) -> List[AuditLog]: """Get recent audit logs within specified hours.""" since = datetime.utcnow() - timedelta(hours=hours) return db.query(AuditLog)\ .filter(AuditLog.created_at >= since)\ .order_by(desc(AuditLog.created_at))\ .limit(limit)\ .all() def get_stats(self, db: Session) -> dict[str, Any]: """Get audit log statistics.""" now = datetime.utcnow() today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) week_start = today_start - timedelta(days=today_start.weekday()) month_start = today_start.replace(day=1) # Total entries total = db.query(func.count(AuditLog.id)).scalar() # Entries today entries_today = db.query(func.count(AuditLog.id))\ .filter(AuditLog.created_at >= today_start)\ .scalar() # Entries this week entries_week = db.query(func.count(AuditLog.id))\ .filter(AuditLog.created_at >= week_start)\ .scalar() # Entries this month entries_month = db.query(func.count(AuditLog.id))\ .filter(AuditLog.created_at >= month_start)\ .scalar() # Actions breakdown actions_query = db.query( AuditLog.action, func.count(AuditLog.id).label('count') ).group_by(AuditLog.action).all() actions_breakdown = {action: count for action, count in actions_query} # Top users (by action count) top_users_query = db.query( AuditLog.user_id, AuditLog.username, func.count(AuditLog.id).label('count') ).filter(AuditLog.user_id.isnot(None))\ .group_by(AuditLog.user_id, AuditLog.username)\ .order_by(desc('count'))\ .limit(10)\ .all() top_users = [ {"user_id": uid, "username": uname, "count": count} for uid, uname, count in top_users_query ] # Recent failures (last 24h) recent_failures = db.query(func.count(AuditLog.id))\ .filter(AuditLog.status == "failure")\ .filter(AuditLog.created_at >= today_start - timedelta(days=1))\ .scalar() return { "total_entries": total or 0, "entries_today": entries_today or 0, "entries_this_week": entries_week or 0, "entries_this_month": entries_month or 0, "actions_breakdown": actions_breakdown, "top_users": top_users, "recent_failures": recent_failures or 0 } def delete_old( self, db: Session, *, days: int = 90 ) -> int: """Delete audit logs older than specified days.""" cutoff = datetime.utcnow() - timedelta(days=days) count = db.query(AuditLog)\ .filter(AuditLog.created_at < cutoff)\ .delete() db.commit() return count def get_distinct_actions(self, db: Session) -> List[str]: """Get list of distinct action types.""" result = db.query(AuditLog.action).distinct().all() return [r[0] for r in result] def get_distinct_resource_types(self, db: Session) -> List[str]: """Get list of distinct resource types.""" result = db.query(AuditLog.resource_type)\ .filter(AuditLog.resource_type.isnot(None))\ .distinct().all() return [r[0] for r in result] # Create instance audit_log = CRUDAuditLog()