Adding Admin API
This commit is contained in:
31
src/api/MicCheck.Api/Audit/AuditLogQueryService.cs
Normal file
31
src/api/MicCheck.Api/Audit/AuditLogQueryService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
27
src/api/MicCheck.Api/Audit/AuditLogResponse.cs
Normal file
27
src/api/MicCheck.Api/Audit/AuditLogResponse.cs
Normal 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);
|
||||
}
|
||||
37
src/api/MicCheck.Api/Audit/AuditLogsController.cs
Normal file
37
src/api/MicCheck.Api/Audit/AuditLogsController.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
38
src/api/MicCheck.Api/Audit/AuditService.cs
Normal file
38
src/api/MicCheck.Api/Audit/AuditService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user