using Microsoft.EntityFrameworkCore; using ROLAC.API.Data.Logging; using ROLAC.API.DTOs.Logging; using ROLAC.API.DTOs.Shared; using ROLAC.API.Entities.Logging; namespace ROLAC.API.Services.Logging; public interface IAuditLogQueryService { Task> GetPagedAsync(AuditLogQuery query); Task GetByIdAsync(long id); AuditCatalogDto GetCatalog(); } /// Read-only, paged access to the AuditLogs table via the dedicated LogDbContext. public sealed class AuditLogQueryService : IAuditLogQueryService { private readonly LogDbContext _db; public AuditLogQueryService(LogDbContext db) => _db = db; public async Task> GetPagedAsync(AuditLogQuery query) { var page = Math.Max(1, query.Page); var pageSize = Math.Clamp(query.PageSize, 1, 200); var rows = _db.AuditLogs.AsNoTracking().AsQueryable(); if (query.From is not null) rows = rows.Where(l => l.Timestamp >= query.From); if (query.To is not null) rows = rows.Where(l => l.Timestamp <= query.To); if (query.MinLevel is not null) rows = rows.Where(l => l.Level >= query.MinLevel); if (!string.IsNullOrWhiteSpace(query.Category)) rows = rows.Where(l => l.Category == query.Category); if (!string.IsNullOrWhiteSpace(query.Action)) rows = rows.Where(l => l.Action == query.Action); if (!string.IsNullOrWhiteSpace(query.EntityName)) rows = rows.Where(l => l.EntityName == query.EntityName); if (!string.IsNullOrWhiteSpace(query.EntityId)) rows = rows.Where(l => l.EntityId == query.EntityId); if (!string.IsNullOrWhiteSpace(query.UserId)) rows = rows.Where(l => l.UserId == query.UserId); if (!string.IsNullOrWhiteSpace(query.Search)) { var term = query.Search.Trim().ToLower(); rows = rows.Where(l => (l.Summary != null && l.Summary.ToLower().Contains(term)) || (l.EntityName != null && l.EntityName.ToLower().Contains(term)) || (l.UserEmail != null && l.UserEmail.ToLower().Contains(term))); } var total = await rows.CountAsync(); var items = await rows .OrderByDescending(l => l.Timestamp) .Skip((page - 1) * pageSize).Take(pageSize) .Select(l => new AuditLogListItemDto { Id = l.Id, Timestamp = l.Timestamp, Level = l.Level.ToString(), Action = l.Action, Category = l.Category, EntityName = l.EntityName, EntityId = l.EntityId, Summary = l.Summary, UserId = l.UserId, UserEmail = l.UserEmail, }) .ToListAsync(); return new PagedResult { Items = items, TotalCount = total, Page = page, PageSize = pageSize, }; } public async Task GetByIdAsync(long id) { return await _db.AuditLogs.AsNoTracking() .Where(l => l.Id == id) .Select(l => new AuditLogDetailDto { Id = l.Id, Timestamp = l.Timestamp, Level = l.Level.ToString(), Action = l.Action, Category = l.Category, EntityName = l.EntityName, EntityId = l.EntityId, Summary = l.Summary, UserId = l.UserId, UserEmail = l.UserEmail, Changes = l.Changes, IpAddress = l.IpAddress, CorrelationId = l.CorrelationId, }) .FirstOrDefaultAsync(); } public AuditCatalogDto GetCatalog() => new() { Categories = AuditCategories.All, Actions = AuditActions.All, Levels = Enum.GetNames(), }; }