using System.Text.Json; using ProposalSystem.Application.Interfaces; using ProposalSystem.Domain.Entities; using ProposalSystem.Infrastructure.Data; namespace ProposalSystem.Infrastructure.Services; public class AuditService : IAuditService { private readonly ProposalDbContext _db; private readonly ICurrentUserService _currentUser; public AuditService(ProposalDbContext db, ICurrentUserService currentUser) { _db = db; _currentUser = currentUser; } public async Task LogAsync(AuditAction action, Guid? proposalId, string? details = null, CancellationToken ct = default) { Stage(action, proposalId, details); await _db.SaveChangesAsync(ct); } public void Stage(AuditAction action, Guid? proposalId, string? details = null) { _db.AuditLogs.Add(BuildEntry(action, proposalId, details)); } private AuditLog BuildEntry(AuditAction action, Guid? proposalId, string? details) { // Fix: API-M12 — accept pre-serialized JSON from callers that provide structured audit data. // If the details string is already valid JSON (starts with '{'), use it directly; // otherwise, wrap plain text in a JSON envelope for consistency. string? jsonDetails = null; if (details != null) { var trimmed = details.TrimStart(); if (trimmed.StartsWith('{') || trimmed.StartsWith('[')) jsonDetails = details; else jsonDetails = JsonSerializer.Serialize(new { message = details }); } return new AuditLog { Id = Guid.NewGuid(), ProposalId = proposalId, UserId = _currentUser.UserId, Action = action, Details = jsonDetails, Timestamp = DateTime.UtcNow, IpAddress = _currentUser.IpAddress, }; } }