Phase 3.1: Instance password AES-256-GCM encryption Phase 3.2: SQL audit rule engine (10 rules) Phase 3.3: Auto-audit on ticket creation Phase 3.4: Operation audit logs + GET /audit/logs Tests: 44 -> 85 (41 new), Frontend: AuditLogsPage
79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
"""Audit log service — centralized logging for all operations."""
|
|
from typing import Optional, List, Dict, Any
|
|
from sqlalchemy import select, func
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.audit_log import AuditLog
|
|
|
|
|
|
class AuditLogService:
|
|
@staticmethod
|
|
async def log(
|
|
db: AsyncSession,
|
|
user_id: int,
|
|
action: str,
|
|
target_type: str,
|
|
target_id: Optional[int] = None,
|
|
detail: Optional[str] = None,
|
|
ip_address: Optional[str] = None,
|
|
) -> AuditLog:
|
|
"""Create an audit log entry."""
|
|
entry = AuditLog(
|
|
user_id=user_id,
|
|
action=action,
|
|
target_type=target_type,
|
|
target_id=target_id,
|
|
detail=detail[:500] if detail else None,
|
|
ip_address=ip_address,
|
|
)
|
|
db.add(entry)
|
|
# Don't commit — let the caller's transaction handle it
|
|
await db.flush()
|
|
return entry
|
|
|
|
@staticmethod
|
|
async def list_logs(
|
|
db: AsyncSession,
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
user_id: Optional[int] = None,
|
|
action: Optional[str] = None,
|
|
target_type: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
"""List audit logs with pagination and filters."""
|
|
stmt = select(AuditLog)
|
|
|
|
if user_id:
|
|
stmt = stmt.where(AuditLog.user_id == user_id)
|
|
if action:
|
|
stmt = stmt.where(AuditLog.action == action)
|
|
if target_type:
|
|
stmt = stmt.where(AuditLog.target_type == target_type)
|
|
|
|
total_stmt = select(func.count()).select_from(stmt.subquery())
|
|
total = (await db.execute(total_stmt)).scalar()
|
|
|
|
stmt = stmt.order_by(AuditLog.created_at.desc())
|
|
stmt = stmt.offset((page - 1) * page_size).limit(page_size)
|
|
result = await db.execute(stmt)
|
|
logs = result.scalars().all()
|
|
|
|
return {
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"items": [
|
|
{
|
|
"id": log.id,
|
|
"user_id": log.user_id,
|
|
"action": log.action,
|
|
"target_type": log.target_type,
|
|
"target_id": log.target_id,
|
|
"detail": log.detail,
|
|
"ip_address": log.ip_address,
|
|
"created_at": log.created_at.isoformat() if log.created_at else None,
|
|
}
|
|
for log in logs
|
|
],
|
|
}
|