using MediatR;
namespace KArtSell.Modules.ModelOperations.Compliance;
///
/// Command to log an audit event.
///
public class LogAuditEventCommand : ICommand
{
public Guid Id { get; set; } = Guid.NewGuid();
public string EventType { get; set; } = string.Empty;
public string EntityType { get; set; } = string.Empty;
public Guid EntityId { get; set; }
public string ActorEmail { get; set; } = string.Empty;
public string? ActorRole { get; set; }
public DateTime EventAt { get; set; } = DateTime.UtcNow;
public string Result { get; set; } = "SUCCESS";
public string? ErrorMessage { get; set; }
public Dictionary? Details { get; set; }
public string[]? EvidenceLinks { get; set; }
public string? IpAddress { get; set; }
public string? UserAgent { get; set; }
public Guid CorrelationId { get; set; }
}
///
/// Handler to log audit events (immutable insert).
/// Idempotent: Multiple calls with same Id result in same outcome.
///
public class LogAuditEventHandler : ICommandHandler
{
private readonly IDbConnection _db;
private readonly AuditSql _sql;
private readonly ILogger _logger;
public LogAuditEventHandler(
IDbConnection db,
AuditSql sql,
ILogger logger)
{
_db = db;
_sql = sql;
_logger = logger;
}
public async Task Handle(LogAuditEventCommand request, CancellationToken ct)
{
try
{
// Log immutable audit event
await _sql.InsertAuditEventAsync(
_db,
request.Id,
request.EventType,
request.EntityType,
request.EntityId,
request.ActorEmail,
request.ActorRole,
request.EventAt,
request.Result,
request.ErrorMessage,
request.Details,
request.EvidenceLinks,
request.IpAddress,
request.UserAgent,
request.CorrelationId,
ct);
// Track GDPR retention for 7 years (FSS requirement)
var retentionEndsAt = DateTime.UtcNow.AddYears(7);
await _sql.InsertGdprRetentionAsync(
_db,
Guid.NewGuid(),
request.Id,
null, // CustomerId would be extracted from request.Details if present
new[] { GdprDataCategories.TradingHistory, GdprDataCategories.PortfolioData },
retentionEndsAt,
ct);
_logger.LogInformation(
"Audit event {EventId} logged: {EventType} for {EntityType} {EntityId}",
request.Id, request.EventType, request.EntityType, request.EntityId);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to log audit event {EventId}: {EventType} for {EntityType} {EntityId}",
request.Id, request.EventType, request.EntityType, request.EntityId);
throw;
}
}
}