using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using StellaOps.Notifier.Worker.StormBreaker; namespace StellaOps.Notifier.WebService.Endpoints; /// /// REST API endpoints for storm breaker operations. /// public static class StormBreakerEndpoints { /// /// Maps storm breaker API endpoints. /// public static RouteGroupBuilder MapStormBreakerEndpoints(this IEndpointRouteBuilder endpoints) { var group = endpoints.MapGroup("/api/v2/storm-breaker") .WithTags("Storm Breaker") .WithOpenApi(); // List active storms for tenant group.MapGet("/storms", async ( HttpContext context, IStormBreaker stormBreaker, CancellationToken cancellationToken) => { var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? "default"; var storms = await stormBreaker.GetActiveStormsAsync(tenantId, cancellationToken); return Results.Ok(new { tenantId, activeStorms = storms.Select(s => new { s.TenantId, s.StormKey, s.StartedAt, eventCount = s.EventIds.Count, s.SuppressedCount, s.LastActivityAt, s.IsActive }).ToList(), count = storms.Count }); }) .WithName("ListActiveStorms") .WithSummary("Lists all active notification storms for a tenant"); // Get specific storm state group.MapGet("/storms/{stormKey}", async ( string stormKey, HttpContext context, IStormBreaker stormBreaker, CancellationToken cancellationToken) => { var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? "default"; var state = await stormBreaker.GetStateAsync(tenantId, stormKey, cancellationToken); if (state is null) { return Results.NotFound(new { error = $"No storm found with key '{stormKey}'" }); } return Results.Ok(new { state.TenantId, state.StormKey, state.StartedAt, eventCount = state.EventIds.Count, state.SuppressedCount, state.LastActivityAt, state.LastSummaryAt, state.IsActive, sampleEventIds = state.EventIds.Take(10).ToList() }); }) .WithName("GetStormState") .WithSummary("Gets the current state of a specific storm"); // Generate storm summary group.MapPost("/storms/{stormKey}/summary", async ( string stormKey, HttpContext context, IStormBreaker stormBreaker, CancellationToken cancellationToken) => { var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? "default"; var summary = await stormBreaker.GenerateSummaryAsync(tenantId, stormKey, cancellationToken); if (summary is null) { return Results.NotFound(new { error = $"No storm found with key '{stormKey}'" }); } return Results.Ok(summary); }) .WithName("GenerateStormSummary") .WithSummary("Generates a summary for an active storm"); // Clear storm state group.MapDelete("/storms/{stormKey}", async ( string stormKey, HttpContext context, IStormBreaker stormBreaker, CancellationToken cancellationToken) => { var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? "default"; await stormBreaker.ClearAsync(tenantId, stormKey, cancellationToken); return Results.Ok(new { message = $"Storm '{stormKey}' cleared successfully" }); }) .WithName("ClearStorm") .WithSummary("Clears a storm state manually"); return group; } }