Files
git.stella-ops.org/src/JobEngine/StellaOps.JobEngine/StellaOps.JobEngine.WebService/Endpoints/ReleaseDashboardEndpoints.cs

77 lines
3.4 KiB
C#

using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.JobEngine.WebService.Services;
namespace StellaOps.JobEngine.WebService.Endpoints;
/// <summary>
/// Release dashboard endpoints consumed by the Console control plane.
/// </summary>
public static class ReleaseDashboardEndpoints
{
public static IEndpointRouteBuilder MapReleaseDashboardEndpoints(this IEndpointRouteBuilder app)
{
MapForPrefix(app, "/api/v1/release-orchestrator", includeRouteNames: true);
MapForPrefix(app, "/api/release-orchestrator", includeRouteNames: false);
return app;
}
private static void MapForPrefix(IEndpointRouteBuilder app, string prefix, bool includeRouteNames)
{
var group = app.MapGroup(prefix)
.WithTags("ReleaseDashboard")
.RequireAuthorization(JobEnginePolicies.ReleaseRead)
.RequireTenant();
var dashboard = group.MapGet("/dashboard", GetDashboard)
.WithDescription("Return a consolidated release dashboard snapshot for the Console control plane, including pending approvals, active promotions, recent deployments, and environment health indicators. Used by the UI to populate the main release management view.");
if (includeRouteNames)
{
dashboard.WithName("ReleaseDashboard_Get");
}
var approve = group.MapPost("/promotions/{id}/approve", ApprovePromotion)
.WithDescription("Record an approval decision on the specified pending promotion request, allowing the associated release to advance to the next environment. The calling principal must hold the release approval scope. Returns 404 when the promotion ID does not exist.")
.RequireAuthorization(JobEnginePolicies.ReleaseApprove);
if (includeRouteNames)
{
approve.WithName("ReleaseDashboard_ApprovePromotion");
}
var reject = group.MapPost("/promotions/{id}/reject", RejectPromotion)
.WithDescription("Record a rejection decision on the specified pending promotion request with an optional rejection reason, blocking the release from advancing. The calling principal must hold the release approval scope. Returns 404 when the promotion ID does not exist.")
.RequireAuthorization(JobEnginePolicies.ReleaseApprove);
if (includeRouteNames)
{
reject.WithName("ReleaseDashboard_RejectPromotion");
}
}
private static IResult GetDashboard()
{
return Results.Ok(ReleaseDashboardSnapshotBuilder.Build());
}
private static IResult ApprovePromotion(string id)
{
var exists = ApprovalEndpoints.SeedData.Approvals
.Any(approval => string.Equals(approval.Id, id, StringComparison.OrdinalIgnoreCase));
return exists
? Results.NoContent()
: Results.NotFound(new { message = $"Promotion '{id}' was not found." });
}
private static IResult RejectPromotion(string id, [FromBody] RejectPromotionRequest? request)
{
var exists = ApprovalEndpoints.SeedData.Approvals
.Any(approval => string.Equals(approval.Id, id, StringComparison.OrdinalIgnoreCase));
return exists
? Results.NoContent()
: Results.NotFound(new { message = $"Promotion '{id}' was not found." });
}
public sealed record RejectPromotionRequest(string? Reason);
}