Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
228 lines
8.3 KiB
C#
228 lines
8.3 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using StellaOps.Notifier.Worker.Correlation;
|
|
|
|
namespace StellaOps.Notifier.WebService.Endpoints;
|
|
|
|
/// <summary>
|
|
/// API endpoints for throttle configuration management.
|
|
/// </summary>
|
|
public static class ThrottleEndpoints
|
|
{
|
|
/// <summary>
|
|
/// Maps throttle configuration endpoints.
|
|
/// </summary>
|
|
public static IEndpointRouteBuilder MapThrottleEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var group = app.MapGroup("/api/v2/throttles")
|
|
.WithTags("Throttles")
|
|
.WithOpenApi();
|
|
|
|
group.MapGet("/config", GetConfigurationAsync)
|
|
.WithName("GetThrottleConfiguration")
|
|
.WithSummary("Get throttle configuration")
|
|
.WithDescription("Returns the throttle configuration for the tenant.");
|
|
|
|
group.MapPut("/config", UpdateConfigurationAsync)
|
|
.WithName("UpdateThrottleConfiguration")
|
|
.WithSummary("Update throttle configuration")
|
|
.WithDescription("Creates or updates the throttle configuration for the tenant.");
|
|
|
|
group.MapDelete("/config", DeleteConfigurationAsync)
|
|
.WithName("DeleteThrottleConfiguration")
|
|
.WithSummary("Delete throttle configuration")
|
|
.WithDescription("Deletes the throttle configuration for the tenant, reverting to defaults.");
|
|
|
|
group.MapPost("/evaluate", EvaluateAsync)
|
|
.WithName("EvaluateThrottle")
|
|
.WithSummary("Evaluate throttle duration")
|
|
.WithDescription("Returns the effective throttle duration for an event kind.");
|
|
|
|
return app;
|
|
}
|
|
|
|
private static async Task<IResult> GetConfigurationAsync(
|
|
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
|
[FromServices] IThrottleConfigurationService throttleService,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tenantId))
|
|
{
|
|
return Results.BadRequest(new { error = "X-Tenant-Id header is required." });
|
|
}
|
|
|
|
var config = await throttleService.GetConfigurationAsync(tenantId, cancellationToken);
|
|
|
|
if (config is null)
|
|
{
|
|
return Results.Ok(new ThrottleConfigurationApiResponse
|
|
{
|
|
TenantId = tenantId,
|
|
DefaultDurationSeconds = 900, // 15 minutes default
|
|
Enabled = true,
|
|
EventKindOverrides = new Dictionary<string, int>(),
|
|
IsDefault = true
|
|
});
|
|
}
|
|
|
|
return Results.Ok(MapToApiResponse(config));
|
|
}
|
|
|
|
private static async Task<IResult> UpdateConfigurationAsync(
|
|
[FromBody] ThrottleConfigurationApiRequest request,
|
|
[FromHeader(Name = "X-Tenant-Id")] string? tenantIdHeader,
|
|
[FromHeader(Name = "X-Actor")] string? actor,
|
|
[FromServices] IThrottleConfigurationService throttleService,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var tenantId = request.TenantId ?? tenantIdHeader;
|
|
if (string.IsNullOrWhiteSpace(tenantId))
|
|
{
|
|
return Results.BadRequest(new { error = "Tenant ID is required via X-Tenant-Id header or request body." });
|
|
}
|
|
|
|
if (request.DefaultDurationSeconds is <= 0)
|
|
{
|
|
return Results.BadRequest(new { error = "Default duration must be a positive value in seconds." });
|
|
}
|
|
|
|
var config = new ThrottleConfiguration
|
|
{
|
|
TenantId = tenantId,
|
|
DefaultDuration = TimeSpan.FromSeconds(request.DefaultDurationSeconds ?? 900),
|
|
EventKindOverrides = request.EventKindOverrides?
|
|
.ToDictionary(kvp => kvp.Key, kvp => TimeSpan.FromSeconds(kvp.Value)),
|
|
MaxEventsPerWindow = request.MaxEventsPerWindow,
|
|
BurstWindowDuration = request.BurstWindowDurationSeconds.HasValue
|
|
? TimeSpan.FromSeconds(request.BurstWindowDurationSeconds.Value)
|
|
: null,
|
|
Enabled = request.Enabled ?? true
|
|
};
|
|
|
|
var updated = await throttleService.UpsertConfigurationAsync(config, actor, cancellationToken);
|
|
|
|
return Results.Ok(MapToApiResponse(updated));
|
|
}
|
|
|
|
private static async Task<IResult> DeleteConfigurationAsync(
|
|
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
|
[FromHeader(Name = "X-Actor")] string? actor,
|
|
[FromServices] IThrottleConfigurationService throttleService,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tenantId))
|
|
{
|
|
return Results.BadRequest(new { error = "X-Tenant-Id header is required." });
|
|
}
|
|
|
|
var deleted = await throttleService.DeleteConfigurationAsync(tenantId, actor, cancellationToken);
|
|
|
|
if (!deleted)
|
|
{
|
|
return Results.NotFound(new { error = "No throttle configuration exists for this tenant." });
|
|
}
|
|
|
|
return Results.NoContent();
|
|
}
|
|
|
|
private static async Task<IResult> EvaluateAsync(
|
|
[FromBody] ThrottleEvaluateApiRequest request,
|
|
[FromHeader(Name = "X-Tenant-Id")] string? tenantIdHeader,
|
|
[FromServices] IThrottleConfigurationService throttleService,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var tenantId = request.TenantId ?? tenantIdHeader;
|
|
if (string.IsNullOrWhiteSpace(tenantId))
|
|
{
|
|
return Results.BadRequest(new { error = "Tenant ID is required via X-Tenant-Id header or request body." });
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(request.EventKind))
|
|
{
|
|
return Results.BadRequest(new { error = "Event kind is required." });
|
|
}
|
|
|
|
var duration = await throttleService.GetEffectiveThrottleDurationAsync(
|
|
tenantId,
|
|
request.EventKind,
|
|
cancellationToken);
|
|
|
|
return Results.Ok(new ThrottleEvaluateApiResponse
|
|
{
|
|
EventKind = request.EventKind,
|
|
EffectiveDurationSeconds = (int)duration.TotalSeconds
|
|
});
|
|
}
|
|
|
|
private static ThrottleConfigurationApiResponse MapToApiResponse(ThrottleConfiguration config) => new()
|
|
{
|
|
TenantId = config.TenantId,
|
|
DefaultDurationSeconds = (int)config.DefaultDuration.TotalSeconds,
|
|
EventKindOverrides = config.EventKindOverrides?
|
|
.ToDictionary(kvp => kvp.Key, kvp => (int)kvp.Value.TotalSeconds)
|
|
?? new Dictionary<string, int>(),
|
|
MaxEventsPerWindow = config.MaxEventsPerWindow,
|
|
BurstWindowDurationSeconds = config.BurstWindowDuration.HasValue
|
|
? (int)config.BurstWindowDuration.Value.TotalSeconds
|
|
: null,
|
|
Enabled = config.Enabled,
|
|
CreatedAt = config.CreatedAt,
|
|
CreatedBy = config.CreatedBy,
|
|
UpdatedAt = config.UpdatedAt,
|
|
UpdatedBy = config.UpdatedBy,
|
|
IsDefault = false
|
|
};
|
|
}
|
|
|
|
#region API Request/Response Models
|
|
|
|
/// <summary>
|
|
/// Request to create or update throttle configuration.
|
|
/// </summary>
|
|
public sealed class ThrottleConfigurationApiRequest
|
|
{
|
|
public string? TenantId { get; set; }
|
|
public int? DefaultDurationSeconds { get; set; }
|
|
public Dictionary<string, int>? EventKindOverrides { get; set; }
|
|
public int? MaxEventsPerWindow { get; set; }
|
|
public int? BurstWindowDurationSeconds { get; set; }
|
|
public bool? Enabled { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request to evaluate throttle duration.
|
|
/// </summary>
|
|
public sealed class ThrottleEvaluateApiRequest
|
|
{
|
|
public string? TenantId { get; set; }
|
|
public string? EventKind { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Response for throttle configuration.
|
|
/// </summary>
|
|
public sealed class ThrottleConfigurationApiResponse
|
|
{
|
|
public required string TenantId { get; set; }
|
|
public required int DefaultDurationSeconds { get; set; }
|
|
public required Dictionary<string, int> EventKindOverrides { get; set; }
|
|
public int? MaxEventsPerWindow { get; set; }
|
|
public int? BurstWindowDurationSeconds { get; set; }
|
|
public required bool Enabled { get; set; }
|
|
public DateTimeOffset CreatedAt { get; set; }
|
|
public string? CreatedBy { get; set; }
|
|
public DateTimeOffset UpdatedAt { get; set; }
|
|
public string? UpdatedBy { get; set; }
|
|
public bool IsDefault { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Response for throttle evaluation.
|
|
/// </summary>
|
|
public sealed class ThrottleEvaluateApiResponse
|
|
{
|
|
public required string EventKind { get; set; }
|
|
public required int EffectiveDurationSeconds { get; set; }
|
|
}
|
|
|
|
#endregion
|