consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.JobEngine.Core.Domain;
|
||||
using StellaOps.JobEngine.Infrastructure.Repositories;
|
||||
using StellaOps.JobEngine.WebService.Contracts;
|
||||
using StellaOps.JobEngine.WebService.Services;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.JobEngine.WebService.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// REST API endpoints for audit log operations.
|
||||
/// </summary>
|
||||
public static class AuditEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps audit endpoints to the route builder.
|
||||
/// </summary>
|
||||
public static RouteGroupBuilder MapAuditEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/v1/jobengine/audit")
|
||||
.WithTags("Orchestrator Audit")
|
||||
.RequireAuthorization(JobEnginePolicies.Read)
|
||||
.RequireTenant();
|
||||
|
||||
// List and get operations
|
||||
group.MapGet(string.Empty, ListAuditEntries)
|
||||
.WithName("Orchestrator_ListAuditEntries")
|
||||
.WithDescription(_t("orchestrator.audit.list_description"));
|
||||
|
||||
group.MapGet("{entryId:guid}", GetAuditEntry)
|
||||
.WithName("Orchestrator_GetAuditEntry")
|
||||
.WithDescription(_t("orchestrator.audit.get_description"));
|
||||
|
||||
group.MapGet("resource/{resourceType}/{resourceId:guid}", GetResourceHistory)
|
||||
.WithName("Orchestrator_GetResourceHistory")
|
||||
.WithDescription(_t("orchestrator.audit.get_resource_history_description"));
|
||||
|
||||
group.MapGet("latest", GetLatestEntry)
|
||||
.WithName("Orchestrator_GetLatestAuditEntry")
|
||||
.WithDescription(_t("orchestrator.audit.get_latest_description"));
|
||||
|
||||
group.MapGet("sequence/{startSeq:long}/{endSeq:long}", GetBySequenceRange)
|
||||
.WithName("Orchestrator_GetAuditBySequence")
|
||||
.WithDescription(_t("orchestrator.audit.get_by_sequence_description"));
|
||||
|
||||
// Summary and verification
|
||||
group.MapGet("summary", GetAuditSummary)
|
||||
.WithName("Orchestrator_GetAuditSummary")
|
||||
.WithDescription(_t("orchestrator.audit.summary_description"));
|
||||
|
||||
group.MapGet("verify", VerifyAuditChain)
|
||||
.WithName("Orchestrator_VerifyAuditChain")
|
||||
.WithDescription(_t("orchestrator.audit.verify_description"));
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static async Task<IResult> ListAuditEntries(
|
||||
HttpContext context,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IAuditRepository repository,
|
||||
[FromQuery] string? eventType = null,
|
||||
[FromQuery] string? resourceType = null,
|
||||
[FromQuery] Guid? resourceId = null,
|
||||
[FromQuery] string? actorId = null,
|
||||
[FromQuery] DateTimeOffset? startTime = null,
|
||||
[FromQuery] DateTimeOffset? endTime = null,
|
||||
[FromQuery] int? limit = null,
|
||||
[FromQuery] string? cursor = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var effectiveLimit = EndpointHelpers.GetLimit(limit);
|
||||
var offset = EndpointHelpers.ParseCursorOffset(cursor);
|
||||
|
||||
AuditEventType? parsedEventType = null;
|
||||
if (!string.IsNullOrEmpty(eventType) && Enum.TryParse<AuditEventType>(eventType, true, out var et))
|
||||
{
|
||||
parsedEventType = et;
|
||||
}
|
||||
|
||||
var entries = await repository.ListAsync(
|
||||
tenantId,
|
||||
parsedEventType,
|
||||
resourceType,
|
||||
resourceId,
|
||||
actorId,
|
||||
startTime,
|
||||
endTime,
|
||||
effectiveLimit,
|
||||
offset,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var responses = entries.Select(AuditEntryResponse.FromDomain).ToList();
|
||||
var nextCursor = EndpointHelpers.CreateNextCursor(offset, effectiveLimit, responses.Count);
|
||||
|
||||
return Results.Ok(new AuditEntryListResponse(responses, nextCursor));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetAuditEntry(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid entryId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IAuditRepository repository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var entry = await repository.GetByIdAsync(tenantId, entryId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (entry is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
return Results.Ok(AuditEntryResponse.FromDomain(entry));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetResourceHistory(
|
||||
HttpContext context,
|
||||
[FromRoute] string resourceType,
|
||||
[FromRoute] Guid resourceId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IAuditRepository repository,
|
||||
[FromQuery] int? limit = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var effectiveLimit = EndpointHelpers.GetLimit(limit);
|
||||
|
||||
var entries = await repository.GetByResourceAsync(
|
||||
tenantId,
|
||||
resourceType,
|
||||
resourceId,
|
||||
effectiveLimit,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var responses = entries.Select(AuditEntryResponse.FromDomain).ToList();
|
||||
return Results.Ok(new AuditEntryListResponse(responses, null));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetLatestEntry(
|
||||
HttpContext context,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IAuditRepository repository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var entry = await repository.GetLatestAsync(tenantId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (entry is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
return Results.Ok(AuditEntryResponse.FromDomain(entry));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetBySequenceRange(
|
||||
HttpContext context,
|
||||
[FromRoute] long startSeq,
|
||||
[FromRoute] long endSeq,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IAuditRepository repository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
|
||||
if (startSeq < 1 || endSeq < startSeq)
|
||||
{
|
||||
return Results.BadRequest(new { error = _t("orchestrator.audit.error.invalid_sequence_range") });
|
||||
}
|
||||
|
||||
var entries = await repository.GetBySequenceRangeAsync(
|
||||
tenantId,
|
||||
startSeq,
|
||||
endSeq,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var responses = entries.Select(AuditEntryResponse.FromDomain).ToList();
|
||||
return Results.Ok(new AuditEntryListResponse(responses, null));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetAuditSummary(
|
||||
HttpContext context,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IAuditRepository repository,
|
||||
[FromQuery] DateTimeOffset? since = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var summary = await repository.GetSummaryAsync(tenantId, since, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(AuditSummaryResponse.FromDomain(summary));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> VerifyAuditChain(
|
||||
HttpContext context,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IAuditRepository repository,
|
||||
[FromQuery] long? startSeq = null,
|
||||
[FromQuery] long? endSeq = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var result = await repository.VerifyChainAsync(tenantId, startSeq, endSeq, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Infrastructure.JobEngineMetrics.AuditChainVerified(tenantId, result.IsValid);
|
||||
|
||||
return Results.Ok(ChainVerificationResponse.FromDomain(result));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user