using System.Text.Json; using line_gestao_api.Data; using line_gestao_api.Dtos; using line_gestao_api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Globalization; namespace line_gestao_api.Controllers; [ApiController] [Route("api/historico")] [Authorize(Roles = "admin")] public class HistoricoController : ControllerBase { private readonly AppDbContext _db; public HistoricoController(AppDbContext db) { _db = db; } [HttpGet] public async Task>> GetAll( [FromQuery] string? pageName, [FromQuery] string? action, [FromQuery] string? entity, [FromQuery] Guid? userId, [FromQuery] string? search, [FromQuery] DateTime? dateFrom, [FromQuery] DateTime? dateTo, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { page = page < 1 ? 1 : page; pageSize = pageSize < 1 ? 20 : pageSize; var q = _db.AuditLogs .AsNoTracking() .Where(x => !EF.Functions.ILike(x.RequestPath ?? "", "%import-excel%") || x.Page == AuditLogBuilder.SpreadsheetImportPageName); if (!string.IsNullOrWhiteSpace(pageName)) { var p = pageName.Trim(); q = q.Where(x => EF.Functions.ILike(x.Page, $"%{p}%")); } if (!string.IsNullOrWhiteSpace(action)) { var a = action.Trim().ToUpperInvariant(); q = q.Where(x => x.Action == a); } if (!string.IsNullOrWhiteSpace(entity)) { var e = entity.Trim(); q = q.Where(x => EF.Functions.ILike(x.EntityName, $"%{e}%")); } if (userId.HasValue) { q = q.Where(x => x.UserId == userId.Value); } if (!string.IsNullOrWhiteSpace(search)) { var s = search.Trim(); var hasGuidSearch = Guid.TryParse(s, out var searchGuid); var hasDateSearch = TryParseSearchDateUtcStart(s, out var searchDateStartUtc); var searchDateEndUtc = hasDateSearch ? searchDateStartUtc.AddDays(1) : DateTime.MinValue; q = q.Where(x => EF.Functions.ILike(x.UserName ?? "", $"%{s}%") || EF.Functions.ILike(x.UserEmail ?? "", $"%{s}%") || EF.Functions.ILike(x.Action ?? "", $"%{s}%") || EF.Functions.ILike(x.EntityName ?? "", $"%{s}%") || EF.Functions.ILike(x.EntityLabel ?? "", $"%{s}%") || EF.Functions.ILike(x.EntityId ?? "", $"%{s}%") || EF.Functions.ILike(x.Page ?? "", $"%{s}%") || EF.Functions.ILike(x.RequestPath ?? "", $"%{s}%") || EF.Functions.ILike(x.RequestMethod ?? "", $"%{s}%") || EF.Functions.ILike(x.IpAddress ?? "", $"%{s}%") || // ChangesJson is stored as jsonb; applying ILIKE directly causes PostgreSQL 42883. (hasGuidSearch && (x.Id == searchGuid || x.UserId == searchGuid)) || (hasDateSearch && x.OccurredAtUtc >= searchDateStartUtc && x.OccurredAtUtc < searchDateEndUtc)); } if (dateFrom.HasValue) { var fromUtc = ToUtc(dateFrom.Value); q = q.Where(x => x.OccurredAtUtc >= fromUtc); } if (dateTo.HasValue) { var toUtc = ToUtc(dateTo.Value); if (dateTo.Value.TimeOfDay == TimeSpan.Zero) { toUtc = toUtc.Date.AddDays(1).AddTicks(-1); } q = q.Where(x => x.OccurredAtUtc <= toUtc); } var total = await q.CountAsync(); var items = await q .OrderByDescending(x => x.OccurredAtUtc) .ThenByDescending(x => x.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return Ok(new PagedResult { Page = page, PageSize = pageSize, Total = total, Items = items.Select(ToDto).ToList() }); } private static AuditLogDto ToDto(Models.AuditLog log) { return new AuditLogDto { Id = log.Id, OccurredAtUtc = log.OccurredAtUtc, Action = log.Action, Page = log.Page, EntityName = log.EntityName, EntityId = log.EntityId, EntityLabel = log.EntityLabel, UserId = log.UserId, UserName = log.UserName, UserEmail = log.UserEmail, RequestPath = log.RequestPath, RequestMethod = log.RequestMethod, IpAddress = log.IpAddress, Changes = ParseChanges(log.ChangesJson) }; } private static List ParseChanges(string? json) { if (string.IsNullOrWhiteSpace(json)) { return new List(); } try { return JsonSerializer.Deserialize>(json) ?? new List(); } catch { return new List(); } } private static DateTime ToUtc(DateTime value) { if (value.Kind == DateTimeKind.Utc) return value; if (value.Kind == DateTimeKind.Local) return value.ToUniversalTime(); return DateTime.SpecifyKind(value, DateTimeKind.Utc); } private static bool TryParseSearchDateUtcStart(string value, out DateTime utcStart) { utcStart = default; if (string.IsNullOrWhiteSpace(value)) return false; var s = value.Trim(); DateTime parsed; if (DateTime.TryParse(s, new CultureInfo("pt-BR"), DateTimeStyles.None, out parsed) || DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsed)) { utcStart = DateTime.SpecifyKind(parsed.Date, DateTimeKind.Utc); return true; } return false; } }