Remove FluentValidation; validator/DI cleanup (#7)
## Summary - Remove FluentValidation dependency; replace with plain IModelValidator<T>/ValidationResult pattern (ported from BuyEngine's Guard/validation approach), wired via a ModelValidationActionFilter - Move FeatureUsage code into MicCheck.Api.Features.Usage namespace - Convert logic-free data classes to records per CLAUDE.md (WebhookEvent, FeatureStateResult, ProjectPermissionRequirement, AuditLog, Tag, FeatureUsageDaily) - Extract IAuditService interface, drop virtual-method mock seam on AuditService - Split Program.cs DI registrations into per-namespace DependencyRegistration classes ## Test plan - [x] dotnet build (API + tests) clean - [x] dotnet test MicCheck.Api.Tests.Unit — 646/646 passing - [x] pre-push hook (full solution build/test + admin build) passed Co-authored-by: miccheck-ci <ci@miccheck.local> Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
namespace MicCheck.Api.Audit;
|
||||
|
||||
public class AuditLog
|
||||
public record AuditLog
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public required string ResourceType { get; init; }
|
||||
|
||||
@@ -6,29 +6,25 @@ namespace MicCheck.Api.Audit;
|
||||
|
||||
public class AuditLogQueryService(IMicCheckDbContext db)
|
||||
{
|
||||
public async Task<PagedResult<AuditLogResponse>> ListByOrganizationAsync(
|
||||
int organizationId, AuditLogFilter filter, CancellationToken ct = default)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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);
|
||||
@@ -66,4 +62,4 @@ public class AuditLogQueryService(IMicCheckDbContext db)
|
||||
|
||||
return new PagedResult<AuditLogResponse>(total, items);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using MicCheck.Api.Webhooks;
|
||||
|
||||
namespace MicCheck.Api.Audit;
|
||||
|
||||
public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContextAccessor, WebhookQueue webhookQueue)
|
||||
public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContextAccessor, WebhookQueue webhookQueue) : IAuditService
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -12,7 +12,7 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public virtual async Task RecordAsync(
|
||||
public async Task RecordAsync(
|
||||
string resourceType,
|
||||
string resourceId,
|
||||
string action,
|
||||
@@ -33,7 +33,7 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex
|
||||
}, JsonOptions);
|
||||
}
|
||||
|
||||
var actorUserId = ResolveActorUserId();
|
||||
var actorUserId = GetCurrentUserId();
|
||||
|
||||
var log = new AuditLog
|
||||
{
|
||||
@@ -68,7 +68,7 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex
|
||||
}
|
||||
|
||||
// Backward-compatible overload used by existing callers
|
||||
public virtual async Task LogAsync(
|
||||
public async Task LogAsync(
|
||||
string resourceType,
|
||||
string resourceId,
|
||||
string action,
|
||||
@@ -78,7 +78,7 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex
|
||||
string? changes = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var actorUserId = ResolveActorUserId();
|
||||
var actorUserId = GetCurrentUserId();
|
||||
|
||||
var log = new AuditLog
|
||||
{
|
||||
@@ -112,9 +112,33 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex
|
||||
});
|
||||
}
|
||||
|
||||
private int? ResolveActorUserId()
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var claim = httpContextAccessor.HttpContext?.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
return int.TryParse(claim, out var id) ? id : null;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAuditService
|
||||
{
|
||||
Task RecordAsync(
|
||||
string resourceType,
|
||||
string resourceId,
|
||||
string action,
|
||||
int organizationId,
|
||||
int? projectId = null,
|
||||
int? environmentId = null,
|
||||
object? before = null,
|
||||
object? after = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task LogAsync(
|
||||
string resourceType,
|
||||
string resourceId,
|
||||
string action,
|
||||
int organizationId,
|
||||
int? projectId = null,
|
||||
int? environmentId = null,
|
||||
string? changes = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
12
src/api/MicCheck.Api/Audit/DependencyRegistration.cs
Normal file
12
src/api/MicCheck.Api/Audit/DependencyRegistration.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace MicCheck.Api.Audit;
|
||||
|
||||
public static class DependencyRegistration
|
||||
{
|
||||
public static IServiceCollection AddAuditServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAuditService, AuditService>();
|
||||
services.AddScoped<AuditLogQueryService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user