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; } } }