345ae0a4b5
CI/CD / CI · Admin API (dotnet build) (push) Successful in 41s
CI/CD / CI · Admin Web (tsc) (push) Failing after 5s
CI/CD / CI · Website (tsc) (push) Failing after 4s
CI/CD / CI · Koja (tsc) (push) Failing after 5s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m13s
CI/CD / CI · Dashboard (tsc) (push) Failing after 2m32s
CI/CD / Deploy · all services (push) Has been skipped
81 lines
2.9 KiB
C#
81 lines
2.9 KiB
C#
using System.Text.Json;
|
|
using Meezi.Core.Entities;
|
|
using Meezi.Core.Interfaces;
|
|
using Meezi.Infrastructure.Data;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace Meezi.API.Services;
|
|
|
|
/// <summary>
|
|
/// Persists audit entries on a fresh, isolated <see cref="AppDbContext"/> so the
|
|
/// write never participates in (or rolls back with) the caller's transaction, and
|
|
/// swallows all failures — auditing must never break the recorded operation.
|
|
/// </summary>
|
|
public sealed class AuditLogService : IAuditLogService
|
|
{
|
|
private static readonly JsonSerializerOptions DetailsJsonOptions = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
|
};
|
|
|
|
private readonly ITenantContext _tenant;
|
|
private readonly IServiceScopeFactory _scopeFactory;
|
|
private readonly ILogger<AuditLogService> _logger;
|
|
|
|
public AuditLogService(
|
|
ITenantContext tenant,
|
|
IServiceScopeFactory scopeFactory,
|
|
ILogger<AuditLogService> logger)
|
|
{
|
|
_tenant = tenant;
|
|
_scopeFactory = scopeFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task LogAsync(AuditEntry entry, CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
var cafeId = _tenant.CafeId;
|
|
if (string.IsNullOrEmpty(cafeId))
|
|
{
|
|
_logger.LogWarning(
|
|
"Skipping audit log '{Category}/{Action}' — no cafe context.",
|
|
entry.Category, entry.Action);
|
|
return;
|
|
}
|
|
|
|
var log = new AuditLog
|
|
{
|
|
CafeId = cafeId,
|
|
BranchId = entry.BranchId ?? _tenant.BranchId,
|
|
Category = entry.Category,
|
|
Action = entry.Action,
|
|
EntityType = entry.EntityType,
|
|
EntityId = entry.EntityId,
|
|
ActorId = _tenant.UserId,
|
|
ActorName = entry.ActorName,
|
|
ActorRole = _tenant.Role?.ToString(),
|
|
Summary = entry.Summary,
|
|
DetailsJson = entry.Details is null
|
|
? null
|
|
: JsonSerializer.Serialize(entry.Details, DetailsJsonOptions)
|
|
};
|
|
|
|
// Fresh scope → fresh DbContext (café-wide, unfiltered) so this write is
|
|
// independent of the business operation's change-tracker and transaction.
|
|
using var scope = _scopeFactory.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
db.AuditLogs.Add(log);
|
|
await db.SaveChangesAsync(ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex,
|
|
"Failed to write audit log '{Category}/{Action}' for entity {EntityType}:{EntityId}.",
|
|
entry.Category, entry.Action, entry.EntityType, entry.EntityId);
|
|
}
|
|
}
|
|
}
|