save development progress
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Findings.Ledger.WebService.Contracts;
|
||||
using StellaOps.Findings.Ledger.WebService.Services;
|
||||
|
||||
namespace StellaOps.Findings.Ledger.WebService.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Webhook management endpoints.
|
||||
/// Sprint: SPRINT_8200_0012_0004 - Wave 6
|
||||
/// </summary>
|
||||
public static class WebhookEndpoints
|
||||
{
|
||||
// Authorization policy name (must match Program.cs)
|
||||
private const string ScoringAdminPolicy = "scoring.admin";
|
||||
|
||||
public static void MapWebhookEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/v1/scoring/webhooks")
|
||||
.WithTags("Webhooks");
|
||||
|
||||
// POST /api/v1/scoring/webhooks - Register webhook
|
||||
// Rate limit: 10/min (via API Gateway)
|
||||
group.MapPost("/", RegisterWebhook)
|
||||
.WithName("RegisterScoringWebhook")
|
||||
.WithDescription("Register a webhook for score change notifications")
|
||||
.Produces<WebhookResponse>(StatusCodes.Status201Created)
|
||||
.ProducesValidationProblem()
|
||||
.RequireAuthorization(ScoringAdminPolicy);
|
||||
|
||||
// GET /api/v1/scoring/webhooks - List webhooks
|
||||
// Rate limit: 10/min (via API Gateway)
|
||||
group.MapGet("/", ListWebhooks)
|
||||
.WithName("ListScoringWebhooks")
|
||||
.WithDescription("List all registered webhooks")
|
||||
.Produces<WebhookListResponse>(StatusCodes.Status200OK)
|
||||
.RequireAuthorization(ScoringAdminPolicy);
|
||||
|
||||
// GET /api/v1/scoring/webhooks/{id} - Get webhook
|
||||
// Rate limit: 10/min (via API Gateway)
|
||||
group.MapGet("/{id:guid}", GetWebhook)
|
||||
.WithName("GetScoringWebhook")
|
||||
.WithDescription("Get a specific webhook by ID")
|
||||
.Produces<WebhookResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScoringAdminPolicy);
|
||||
|
||||
// PUT /api/v1/scoring/webhooks/{id} - Update webhook
|
||||
// Rate limit: 10/min (via API Gateway)
|
||||
group.MapPut("/{id:guid}", UpdateWebhook)
|
||||
.WithName("UpdateScoringWebhook")
|
||||
.WithDescription("Update a webhook configuration")
|
||||
.Produces<WebhookResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.ProducesValidationProblem()
|
||||
.RequireAuthorization(ScoringAdminPolicy);
|
||||
|
||||
// DELETE /api/v1/scoring/webhooks/{id} - Delete webhook
|
||||
// Rate limit: 10/min (via API Gateway)
|
||||
group.MapDelete("/{id:guid}", DeleteWebhook)
|
||||
.WithName("DeleteScoringWebhook")
|
||||
.WithDescription("Delete a webhook")
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScoringAdminPolicy);
|
||||
}
|
||||
|
||||
private static Results<Created<WebhookResponse>, ValidationProblem> RegisterWebhook(
|
||||
[FromBody] RegisterWebhookRequest request,
|
||||
[FromServices] IWebhookStore store)
|
||||
{
|
||||
// Validate URL
|
||||
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) ||
|
||||
(uri.Scheme != "http" && uri.Scheme != "https"))
|
||||
{
|
||||
return TypedResults.ValidationProblem(new Dictionary<string, string[]>
|
||||
{
|
||||
["url"] = ["Invalid webhook URL. Must be an absolute HTTP or HTTPS URL."]
|
||||
});
|
||||
}
|
||||
|
||||
var registration = store.Register(request);
|
||||
var response = MapToResponse(registration);
|
||||
|
||||
return TypedResults.Created($"/api/v1/scoring/webhooks/{registration.Id}", response);
|
||||
}
|
||||
|
||||
private static Ok<WebhookListResponse> ListWebhooks(
|
||||
[FromServices] IWebhookStore store)
|
||||
{
|
||||
var webhooks = store.List();
|
||||
var response = new WebhookListResponse
|
||||
{
|
||||
Webhooks = webhooks.Select(MapToResponse).ToList(),
|
||||
TotalCount = webhooks.Count
|
||||
};
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
private static Results<Ok<WebhookResponse>, NotFound> GetWebhook(
|
||||
Guid id,
|
||||
[FromServices] IWebhookStore store)
|
||||
{
|
||||
var webhook = store.Get(id);
|
||||
if (webhook is null || !webhook.IsActive)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
return TypedResults.Ok(MapToResponse(webhook));
|
||||
}
|
||||
|
||||
private static Results<Ok<WebhookResponse>, NotFound, ValidationProblem> UpdateWebhook(
|
||||
Guid id,
|
||||
[FromBody] RegisterWebhookRequest request,
|
||||
[FromServices] IWebhookStore store)
|
||||
{
|
||||
// Validate URL
|
||||
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) ||
|
||||
(uri.Scheme != "http" && uri.Scheme != "https"))
|
||||
{
|
||||
return TypedResults.ValidationProblem(new Dictionary<string, string[]>
|
||||
{
|
||||
["url"] = ["Invalid webhook URL. Must be an absolute HTTP or HTTPS URL."]
|
||||
});
|
||||
}
|
||||
|
||||
if (!store.Update(id, request))
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
var updated = store.Get(id);
|
||||
return TypedResults.Ok(MapToResponse(updated!));
|
||||
}
|
||||
|
||||
private static Results<NoContent, NotFound> DeleteWebhook(
|
||||
Guid id,
|
||||
[FromServices] IWebhookStore store)
|
||||
{
|
||||
if (!store.Delete(id))
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
|
||||
private static WebhookResponse MapToResponse(WebhookRegistration registration)
|
||||
{
|
||||
return new WebhookResponse
|
||||
{
|
||||
Id = registration.Id,
|
||||
Url = registration.Url,
|
||||
HasSecret = !string.IsNullOrEmpty(registration.Secret),
|
||||
FindingPatterns = registration.FindingPatterns,
|
||||
MinScoreChange = registration.MinScoreChange,
|
||||
TriggerOnBucketChange = registration.TriggerOnBucketChange,
|
||||
CreatedAt = registration.CreatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for listing webhooks.
|
||||
/// </summary>
|
||||
public sealed record WebhookListResponse
|
||||
{
|
||||
/// <summary>List of webhooks.</summary>
|
||||
public required IReadOnlyList<WebhookResponse> Webhooks { get; init; }
|
||||
|
||||
/// <summary>Total count of webhooks.</summary>
|
||||
public required int TotalCount { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user