using Microsoft.Extensions.Options; using SIGCM2.Application.Audit; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Infrastructure.Audit; /// UDT-010 — IAuditLogger implementation. Enriches from IAuditContext, sanitizes metadata, /// persists via IAuditEventRepository. Fail-closed: throws AuditContextMissingException /// when ActorUserId is null (system-emitted events should use a different path). public sealed class AuditLogger : IAuditLogger { private readonly IAuditContext _context; private readonly IAuditEventRepository _repo; private readonly IOptions _options; private readonly TimeProvider _timeProvider; public AuditLogger( IAuditContext context, IAuditEventRepository repo, IOptions options, TimeProvider timeProvider) { _context = context; _repo = repo; _options = options; _timeProvider = timeProvider; } public async Task LogAsync( string action, string targetType, string targetId, object? metadata = null, CancellationToken ct = default) { if (_context.ActorUserId is null) throw new AuditContextMissingException(); var sanitized = metadata is null ? null : JsonSanitizer.Sanitize(metadata, _options.Value.SanitizedKeys); var correlationId = _context.CorrelationId == Guid.Empty ? (Guid?)null : _context.CorrelationId; await _repo.InsertAsync( occurredAt: _timeProvider.GetUtcNow().UtcDateTime, actorUserId: _context.ActorUserId, actorRoleId: _context.ActorRoleId, action: action, targetType: targetType, targetId: targetId, correlationId: correlationId, ipAddress: _context.Ip, userAgent: _context.UserAgent, metadata: sanitized, ct: ct); } }