amazon-transcreation/backend/app/services/audit_service.py
DJP 84f37a4649 feat: wire audit trail page to real backend data
- Fix API path: frontend now calls /audit/logs (was /audit)
- Backend eagerly loads User relationship for audit entries
- Backend response includes user_name field instead of just user_id
- Frontend logs page fetches real data with pagination
- Derive INFO/WARN/ERROR levels from action type
- Format details JSON into readable descriptions
- Add loading state and empty state handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 16:59:36 -04:00

79 lines
2.4 KiB
Python

from datetime import datetime
from typing import Any
from uuid import UUID
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.audit import AuditLog
class AuditService:
"""Service for audit log creation and retrieval."""
async def log(
self,
db: AsyncSession,
action: str,
entity_type: str,
entity_id: str,
user_id: UUID | None = None,
details: dict[str, Any] | None = None,
ip_address: str | None = None,
) -> AuditLog:
"""Create an audit log entry."""
entry = AuditLog(
user_id=user_id,
action=action,
entity_type=entity_type,
entity_id=entity_id,
details=details,
ip_address=ip_address,
)
db.add(entry)
await db.flush()
return entry
async def list_logs(
self,
db: AsyncSession,
user_id: UUID | None = None,
action: str | None = None,
entity_type: str | None = None,
entity_id: str | None = None,
date_from: datetime | None = None,
date_to: datetime | None = None,
page: int = 1,
page_size: int = 50,
) -> tuple[list[AuditLog], int]:
"""List audit logs with filters and pagination."""
query = select(AuditLog)
if user_id:
query = query.where(AuditLog.user_id == user_id)
if action:
query = query.where(AuditLog.action == action)
if entity_type:
query = query.where(AuditLog.entity_type == entity_type)
if entity_id:
query = query.where(AuditLog.entity_id == entity_id)
if date_from:
query = query.where(AuditLog.timestamp >= date_from)
if date_to:
query = query.where(AuditLog.timestamp <= date_to)
# Count
count_query = select(func.count()).select_from(query.subquery())
total_result = await db.execute(count_query)
total = total_result.scalar() or 0
# Paginate and eager-load user relationship for name display
query = query.order_by(AuditLog.timestamp.desc())
query = query.offset((page - 1) * page_size).limit(page_size)
query = query.options(selectinload(AuditLog.user))
result = await db.execute(query)
logs = list(result.scalars().all())
return logs, total