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}
This commit is contained in:
27
src/api/SIGCM2.Application/Audit/IAuditEventRepository.cs
Normal file
27
src/api/SIGCM2.Application/Audit/IAuditEventRepository.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user