feat: add AuditSaveChangesInterceptor and failing interceptor tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-05-27 13:48:03 -07:00
parent f703519838
commit 820ca6981c
2 changed files with 117 additions and 0 deletions
@@ -0,0 +1,55 @@
using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using ROLAC.API.Entities.Base;
namespace ROLAC.API.Data.Interceptors;
public class AuditSaveChangesInterceptor : SaveChangesInterceptor
{
private readonly IHttpContextAccessor _http;
public AuditSaveChangesInterceptor(IHttpContextAccessor http) => _http = http;
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData, InterceptionResult<int> result)
{
Stamp(eventData.Context);
return base.SavingChanges(eventData, result);
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData, InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
Stamp(eventData.Context);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
private void Stamp(DbContext? db)
{
if (db is null) return;
var userId = _http.HttpContext?.User
.FindFirstValue(ClaimTypes.NameIdentifier) ?? "system";
var now = DateTimeOffset.UtcNow;
foreach (var entry in db.ChangeTracker.Entries())
{
if (entry.Entity is not AuditableEntity audit) continue;
if (entry.State == EntityState.Added)
{
audit.CreatedAt = now;
audit.CreatedBy = userId;
audit.UpdatedAt = now;
audit.UpdatedBy = userId;
}
else if (entry.State == EntityState.Modified)
{
audit.UpdatedAt = now;
audit.UpdatedBy = userId;
}
}
}
}