74 lines
3.0 KiB
C#
74 lines
3.0 KiB
C#
using ControlPlane.Api.Services;
|
|
using ControlPlane.Core.Models;
|
|
using System.Text.Json;
|
|
|
|
namespace ControlPlane.Api.Endpoints;
|
|
|
|
public static class PromotionEndpoints
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOpts = new(JsonSerializerDefaults.Web);
|
|
|
|
public static IEndpointRouteBuilder MapPromotionEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var g = app.MapGroup("/api/promotions").WithTags("Promotions");
|
|
|
|
// GET /api/promotions/ladder — branch status for all 4 ladder branches
|
|
g.MapGet("/ladder", async (PromotionService svc, CancellationToken ct) =>
|
|
Results.Ok(await svc.GetLadderStatusAsync(ct)));
|
|
|
|
// GET /api/promotions/history
|
|
g.MapGet("/history", async (PromotionService svc) =>
|
|
Results.Ok(await svc.GetHistoryAsync()));
|
|
|
|
// POST /api/promotions/promote — body: { from, to, requestedBy, note }
|
|
// Streams SSE log lines then sends {done, promotion} when complete
|
|
g.MapPost("/promote", async (
|
|
HttpContext ctx,
|
|
PromotionService svc,
|
|
PromoteRequest req,
|
|
CancellationToken ct) =>
|
|
{
|
|
// Validate ladder step
|
|
var ladder = PromotionService.Ladder;
|
|
var fi = Array.IndexOf(ladder, req.From);
|
|
var ti = Array.IndexOf(ladder, req.To);
|
|
if (fi < 0 || ti < 0 || ti != fi + 1)
|
|
{
|
|
ctx.Response.StatusCode = 400;
|
|
await ctx.Response.WriteAsJsonAsync(
|
|
new { error = $"Invalid promotion step: {req.From} → {req.To}. Must be adjacent in ladder." }, ct);
|
|
return;
|
|
}
|
|
|
|
ctx.Response.Headers.ContentType = "text/event-stream";
|
|
ctx.Response.Headers.CacheControl = "no-cache";
|
|
ctx.Response.Headers.Connection = "keep-alive";
|
|
|
|
var channel = System.Threading.Channels.Channel.CreateUnbounded<string?>(
|
|
new System.Threading.Channels.UnboundedChannelOptions { SingleWriter = true, SingleReader = true });
|
|
|
|
void OnLine(string line) => channel.Writer.TryWrite(line);
|
|
|
|
var promoteTask = Task.Run(() =>
|
|
svc.PromoteAsync(req.From, req.To, req.RequestedBy ?? "system", req.Note, OnLine, ct), ct)
|
|
.ContinueWith(t => channel.Writer.TryComplete(t.Exception), TaskScheduler.Default);
|
|
|
|
await foreach (var line in channel.Reader.ReadAllAsync(ct))
|
|
{
|
|
var json = JsonSerializer.Serialize(new { line }, JsonOpts);
|
|
await ctx.Response.WriteAsync($"data: {json}\n\n", ct);
|
|
await ctx.Response.Body.FlushAsync(ct);
|
|
}
|
|
|
|
var promotion = await promoteTask;
|
|
var doneJson = JsonSerializer.Serialize(new { done = true, promotion }, JsonOpts);
|
|
await ctx.Response.WriteAsync($"data: {doneJson}\n\n", ct);
|
|
await ctx.Response.Body.FlushAsync(ct);
|
|
});
|
|
|
|
return app;
|
|
}
|
|
}
|
|
|
|
public record PromoteRequest(string From, string To, string? RequestedBy, string? Note);
|