76 lines
3.5 KiB
C#
76 lines
3.5 KiB
C#
using System.Data;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Data.SqlClient;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using PowderCoating.Application.Interfaces;
|
|
|
|
namespace PowderCoating.Infrastructure.Data;
|
|
|
|
/// <summary>
|
|
/// Writes manual audit log entries via raw SQL, matching the approach used by
|
|
/// <see cref="AuditInterceptor"/> so neither path can trigger an infinite loop
|
|
/// through the EF change tracker.
|
|
/// </summary>
|
|
public class AuditService : IAuditService
|
|
{
|
|
private readonly ApplicationDbContext _db;
|
|
private readonly IHttpContextAccessor _http;
|
|
|
|
public AuditService(ApplicationDbContext db, IHttpContextAccessor http)
|
|
{
|
|
_db = db;
|
|
_http = http;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task LogAsync(string action, string entityType, string? description = null,
|
|
object? details = null, string? entityId = null)
|
|
{
|
|
var (userId, userName, companyId, ip) = GetRequestContext();
|
|
var newValues = details != null ? JsonSerializer.Serialize(details) : null;
|
|
|
|
const string sql = """
|
|
INSERT INTO AuditLogs
|
|
(UserId, UserName, CompanyId, CompanyName, Action, EntityType, EntityId,
|
|
EntityDescription, OldValues, NewValues, IpAddress, Timestamp)
|
|
VALUES (@userId, @userName, @companyId, @companyName, @action, @entityType, @entityId,
|
|
@entityDescription, NULL, @newValues, @ipAddress, @timestamp)
|
|
""";
|
|
|
|
SqlParameter[] p =
|
|
[
|
|
new("@userId", SqlDbType.NVarChar, -1) { Value = (object?)userId ?? DBNull.Value },
|
|
new("@userName", SqlDbType.NVarChar, -1) { Value = (object)userName },
|
|
new("@companyId", SqlDbType.Int) { Value = (object?)companyId ?? DBNull.Value },
|
|
new("@companyName", SqlDbType.NVarChar, -1) { Value = DBNull.Value },
|
|
new("@action", SqlDbType.NVarChar, -1) { Value = (object)action },
|
|
new("@entityType", SqlDbType.NVarChar, -1) { Value = (object)entityType },
|
|
new("@entityId", SqlDbType.NVarChar, -1) { Value = (object?)entityId ?? DBNull.Value },
|
|
new("@entityDescription", SqlDbType.NVarChar, -1) { Value = (object?)description ?? DBNull.Value },
|
|
new("@newValues", SqlDbType.NVarChar, -1) { Value = (object?)newValues ?? DBNull.Value },
|
|
new("@ipAddress", SqlDbType.NVarChar, -1) { Value = (object?)ip ?? DBNull.Value },
|
|
new("@timestamp", SqlDbType.DateTime2) { Value = DateTime.UtcNow },
|
|
];
|
|
|
|
await _db.Database.ExecuteSqlRawAsync(sql, p);
|
|
}
|
|
|
|
private (string? userId, string userName, int? companyId, string? ip) GetRequestContext()
|
|
{
|
|
var ctx = _http.HttpContext;
|
|
if (ctx == null) return (null, "System", null, null);
|
|
|
|
var user = ctx.User;
|
|
var userId = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
|
var userName = user.Identity?.Name
|
|
?? user.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value
|
|
?? "Unknown";
|
|
var cidStr = user.FindFirst("CompanyId")?.Value;
|
|
int? companyId = cidStr != null && int.TryParse(cidStr, out var c) ? c : null;
|
|
var ip = ctx.Connection.RemoteIpAddress?.ToString();
|
|
|
|
return (userId, userName, companyId, ip);
|
|
}
|
|
}
|