Adding Admin API

This commit is contained in:
2026-04-08 14:25:10 -07:00
parent 0ba076b650
commit b9a04df861
67 changed files with 3316 additions and 74 deletions

View File

@@ -0,0 +1,31 @@
using MicCheck.Api.Data;
using Microsoft.EntityFrameworkCore;
namespace MicCheck.Api.Audit;
public class AuditLogQueryService(MicCheckDbContext db)
{
public async Task<IReadOnlyList<AuditLog>> ListByOrganizationAsync(int organizationId, CancellationToken ct = default)
{
return await db.AuditLogs
.Where(l => l.OrganizationId == organizationId)
.OrderByDescending(l => l.CreatedAt)
.ToListAsync(ct);
}
public async Task<IReadOnlyList<AuditLog>> ListByProjectAsync(int projectId, CancellationToken ct = default)
{
return await db.AuditLogs
.Where(l => l.ProjectId == projectId)
.OrderByDescending(l => l.CreatedAt)
.ToListAsync(ct);
}
public async Task<IReadOnlyList<AuditLog>> ListByEnvironmentAsync(int environmentId, CancellationToken ct = default)
{
return await db.AuditLogs
.Where(l => l.EnvironmentId == environmentId)
.OrderByDescending(l => l.CreatedAt)
.ToListAsync(ct);
}
}

View File

@@ -0,0 +1,27 @@
namespace MicCheck.Api.Audit;
public record AuditLogResponse(
int Id,
string ResourceType,
string ResourceId,
string Action,
string? Changes,
int OrganizationId,
int? ProjectId,
int? EnvironmentId,
int? ActorUserId,
DateTimeOffset CreatedAt
)
{
public static AuditLogResponse From(AuditLog log) => new(
log.Id,
log.ResourceType,
log.ResourceId,
log.Action,
log.Changes,
log.OrganizationId,
log.ProjectId,
log.EnvironmentId,
log.ActorUserId,
log.CreatedAt);
}

View File

@@ -0,0 +1,37 @@
using MicCheck.Api.Authorization;
using MicCheck.Api.Projects;
using MicCheck.Api.Organizations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
namespace MicCheck.Api.Audit;
[ApiController]
[Authorize(Policy = AuthorizationPolicies.AdminApiAccess)]
[EnableRateLimiting("AdminApi")]
public class AuditLogsController(
AuditLogQueryService auditLogQueryService,
OrganizationService organizationService,
ProjectService projectService) : ControllerBase
{
[HttpGet("api/v1/organisations/{id}/audit-logs")]
public async Task<ActionResult<IReadOnlyList<AuditLogResponse>>> ListByOrganization(int id, CancellationToken ct)
{
var org = await organizationService.FindByIdAsync(id, ct);
if (org is null) return NotFound();
var logs = await auditLogQueryService.ListByOrganizationAsync(id, ct);
return Ok(logs.Select(AuditLogResponse.From).ToList());
}
[HttpGet("api/v1/projects/{projectId}/audit-logs")]
public async Task<ActionResult<IReadOnlyList<AuditLogResponse>>> ListByProject(int projectId, CancellationToken ct)
{
var project = await projectService.FindByIdAsync(projectId, ct);
if (project is null) return NotFound();
var logs = await auditLogQueryService.ListByProjectAsync(projectId, ct);
return Ok(logs.Select(AuditLogResponse.From).ToList());
}
}

View File

@@ -0,0 +1,38 @@
using MicCheck.Api.Data;
namespace MicCheck.Api.Audit;
public class AuditService(MicCheckDbContext db, IHttpContextAccessor httpContextAccessor)
{
public virtual async Task LogAsync(
string resourceType,
string resourceId,
string action,
int organizationId,
int? projectId = null,
int? environmentId = null,
string? changes = null,
CancellationToken ct = default)
{
int? actorUserId = null;
var userIdClaim = httpContextAccessor.HttpContext?.User.FindFirst("UserId")?.Value;
if (int.TryParse(userIdClaim, out var parsedId))
actorUserId = parsedId;
db.AuditLogs.Add(new AuditLog
{
ResourceType = resourceType,
ResourceId = resourceId,
Action = action,
OrganizationId = organizationId,
ProjectId = projectId,
EnvironmentId = environmentId,
ActorUserId = actorUserId,
Changes = changes,
CreatedAt = DateTimeOffset.UtcNow
});
await db.SaveChangesAsync(ct);
}
}