WebhookEvent, FeatureStateResult, ProjectPermissionRequirement, AuditLog, Tag, and FeatureUsageDaily are plain data holders with no behavior, so switch them to records. EF-mapped entities with identity semantics (User, Organization, Project, Feature, Segment, etc.) stay classes, since record value-equality and deep ToString fight EF's change tracker. Also restore `virtual` on AuditService.RecordAsync/LogAsync, which had been dropped and broke Mock<AuditService>-based test setups.
65 lines
2.8 KiB
C#
Executable File
65 lines
2.8 KiB
C#
Executable File
using MicCheck.Api.Common;
|
|
using MicCheck.Api.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace MicCheck.Api.Audit;
|
|
|
|
public class AuditLogQueryService(IMicCheckDbContext db)
|
|
{
|
|
public async Task<PagedResult<AuditLogResponse>> ListByOrganizationAsync(int organizationId, AuditLogFilter filter, CancellationToken ct = default)
|
|
{
|
|
var query = db.AuditLogs.Where(l => l.OrganizationId == organizationId);
|
|
return await ApplyFilterAndPageAsync(query, filter, ct);
|
|
}
|
|
|
|
public async Task<PagedResult<AuditLogResponse>> ListByProjectAsync(int projectId, AuditLogFilter filter, CancellationToken ct = default)
|
|
{
|
|
var query = db.AuditLogs.Where(l => l.ProjectId == projectId);
|
|
return await ApplyFilterAndPageAsync(query, filter, ct);
|
|
}
|
|
|
|
public async Task<PagedResult<AuditLogResponse>> ListByEnvironmentAsync(int environmentId, AuditLogFilter filter, CancellationToken ct = default)
|
|
{
|
|
var query = db.AuditLogs.Where(l => l.EnvironmentId == environmentId);
|
|
return await ApplyFilterAndPageAsync(query, filter, ct);
|
|
}
|
|
|
|
private async Task<PagedResult<AuditLogResponse>> ApplyFilterAndPageAsync(IQueryable<AuditLog> query, AuditLogFilter filter, CancellationToken ct)
|
|
{
|
|
if (filter.From.HasValue)
|
|
query = query.Where(l => l.CreatedAt >= filter.From.Value);
|
|
if (filter.To.HasValue)
|
|
query = query.Where(l => l.CreatedAt <= filter.To.Value);
|
|
if (!string.IsNullOrEmpty(filter.ResourceType))
|
|
query = query.Where(l => l.ResourceType == filter.ResourceType);
|
|
if (!string.IsNullOrEmpty(filter.Action))
|
|
query = query.Where(l => l.Action == filter.Action);
|
|
if (filter.ProjectId.HasValue)
|
|
query = query.Where(l => l.ProjectId == filter.ProjectId);
|
|
if (filter.EnvironmentId.HasValue)
|
|
query = query.Where(l => l.EnvironmentId == filter.EnvironmentId);
|
|
if (filter.ActorUserId.HasValue)
|
|
query = query.Where(l => l.ActorUserId == filter.ActorUserId);
|
|
|
|
var total = await query.CountAsync(ct);
|
|
var pageSize = Math.Clamp(filter.PageSize, 1, 100);
|
|
|
|
var items = await query
|
|
.OrderByDescending(l => l.CreatedAt)
|
|
.Skip((filter.Page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.GroupJoin(
|
|
db.Users,
|
|
log => log.ActorUserId,
|
|
user => (int?)user.Id,
|
|
(log, users) => new { log, users })
|
|
.SelectMany(
|
|
x => x.users.DefaultIfEmpty(),
|
|
(x, user) => AuditLogResponse.From(
|
|
x.log,
|
|
user != null ? user.FirstName + " " + user.LastName : null))
|
|
.ToListAsync(ct);
|
|
|
|
return new PagedResult<AuditLogResponse>(total, items);
|
|
}
|
|
} |