Files
meezi/src/Meezi.API/Services/AuditLogService.cs
T
soroush.asadi 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
first commit
2026-05-31 11:06:24 +03:30

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);
}
}
}