Files
SIG-CM2.0/src/api/SIGCM2.Application/Audit/IAuditEventRepository.cs

28 lines
806 B
C#
Raw Normal View History

feat(infra): audit + security event repositories (UDT-010 B5) Introduces persistence layer for audit and security events per design #D-6: SIGCM2.Application/Audit/: - IAuditEventRepository: InsertAsync + QueryAsync with cursor pagination - ISecurityEventRepository: InsertAsync only (no query — SecurityEvent is queried only from an admin dashboard deferred to ADM-004) - AuditEventQueryResult: (Items, NextCursor) record SIGCM2.Infrastructure/Audit/: - AuditEventCursor (public): base64(OccurredAt:O|Id) opaque cursor for DESC pagination. TryDecode is fail-open — malformed cursor returns null and the query starts from the top. - AuditEventRepository: Dapper INSERT via OUTPUT INSERTED.Id + dynamic WHERE composition with parameterized filters (zero SQL injection risk). LEFT JOIN to dbo.Usuario to populate ActorUsername in AuditEventDto. Pagination fetches Limit+1 rows to detect "more pages"; emits cursor from the Nth row when overflow observed. - SecurityEventRepository: straight INSERT for login/logout/refresh/ permission.denied events. DI: AddScoped for both repos in AddInfrastructure. Integration tests (Strict TDD): 13 total, all against SIGCM2_Test. - AuditEventRepositoryTests (10): insert-roundtrip, filter-by-actor, filter-by-target, filter-by-date-range, cursor pagination across 3 pages (no overlap/no gap), malformed-cursor fail-open, LEFT JOIN Usuario populates username, cursor encode/decode roundtrip, cursor malformed variants. - SecurityEventRepositoryTests (3): insert success, insert failure with null ActorUserId + AttemptedUsername, CK_SecurityEvent_Result rejection. Suite: 368/368 Application.Tests + 141/141 Api.Tests = 509/509 passing. Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-2,7 #REQ-SEC-1, design#D-6, tasks#B5}
2026-04-16 13:38:05 -03:00
namespace SIGCM2.Application.Audit;
public sealed record AuditEventQueryResult(
IReadOnlyList<AuditEventDto> Items,
string? NextCursor);
/// Persists and queries AuditEvent rows. Insert participates in any ambient
/// TransactionScope (single connection string enlistment — validated by B0 spike).
public interface IAuditEventRepository
{
Task<long> InsertAsync(
DateTime occurredAt,
int? actorUserId,
int? actorRoleId,
string action,
string targetType,
string targetId,
Guid? correlationId,
string? ipAddress,
string? userAgent,
string? metadata,
CancellationToken ct = default);
Task<AuditEventQueryResult> QueryAsync(
AuditEventFilter filter,
CancellationToken ct = default);
}