work
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
@@ -31,6 +32,8 @@ if (!isTesting)
|
||||
builder.Services.AddNotifyMongoStorage(mongoSection);
|
||||
builder.Services.AddHostedService<MongoInitializationHostedService>();
|
||||
builder.Services.AddHostedService<PackApprovalTemplateSeeder>();
|
||||
builder.Services.AddHostedService<AttestationTemplateSeeder>();
|
||||
builder.Services.AddHostedService<RiskTemplateSeeder>();
|
||||
}
|
||||
|
||||
// Fallback no-op event queue for environments that do not configure a real backend.
|
||||
@@ -173,6 +176,122 @@ app.MapPost("/api/v1/notify/pack-approvals", async (
|
||||
return Results.Accepted();
|
||||
});
|
||||
|
||||
app.MapPost("/api/v1/notify/attestation-events", async (
|
||||
HttpContext context,
|
||||
AttestationEventRequest request,
|
||||
INotifyEventQueue? eventQueue,
|
||||
TimeProvider timeProvider) =>
|
||||
{
|
||||
var tenantId = context.Request.Headers["X-StellaOps-Tenant"].ToString();
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return Results.BadRequest(Error("tenant_missing", "X-StellaOps-Tenant header is required.", context));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Kind))
|
||||
{
|
||||
return Results.BadRequest(Error("invalid_request", "kind is required.", context));
|
||||
}
|
||||
|
||||
var eventId = request.EventId != Guid.Empty ? request.EventId : Guid.NewGuid();
|
||||
var ts = request.Timestamp is { } tsValue && tsValue != default ? tsValue : timeProvider.GetUtcNow();
|
||||
|
||||
if (eventQueue is not null)
|
||||
{
|
||||
var payload = request.Payload ?? new JsonObject();
|
||||
|
||||
var notifyEvent = NotifyEvent.Create(
|
||||
eventId: eventId,
|
||||
kind: request.Kind!,
|
||||
tenant: tenantId,
|
||||
ts: ts,
|
||||
payload: payload,
|
||||
attributes: request.Attributes ?? new Dictionary<string, string>(),
|
||||
actor: request.Actor,
|
||||
version: "1");
|
||||
|
||||
var idempotencyKey = context.Request.Headers["Idempotency-Key"].ToString();
|
||||
if (string.IsNullOrWhiteSpace(idempotencyKey))
|
||||
{
|
||||
idempotencyKey = $"attestation|{tenantId}|{notifyEvent.Kind}|{notifyEvent.EventId}";
|
||||
}
|
||||
|
||||
await eventQueue.PublishAsync(
|
||||
new NotifyQueueEventMessage(
|
||||
notifyEvent,
|
||||
stream: "notify:events",
|
||||
idempotencyKey: idempotencyKey,
|
||||
partitionKey: tenantId,
|
||||
traceId: context.TraceIdentifier),
|
||||
context.RequestAborted).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.ResumeToken))
|
||||
{
|
||||
context.Response.Headers["X-Resume-After"] = request.ResumeToken;
|
||||
}
|
||||
|
||||
return Results.Accepted();
|
||||
});
|
||||
|
||||
app.MapPost("/api/v1/notify/risk-events", async (
|
||||
HttpContext context,
|
||||
RiskEventRequest request,
|
||||
INotifyEventQueue? eventQueue,
|
||||
TimeProvider timeProvider) =>
|
||||
{
|
||||
var tenantId = context.Request.Headers["X-StellaOps-Tenant"].ToString();
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return Results.BadRequest(Error("tenant_missing", "X-StellaOps-Tenant header is required.", context));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Kind))
|
||||
{
|
||||
return Results.BadRequest(Error("invalid_request", "kind is required.", context));
|
||||
}
|
||||
|
||||
var eventId = request.EventId != Guid.Empty ? request.EventId : Guid.NewGuid();
|
||||
var ts = request.Timestamp is { } tsValue && tsValue != default ? tsValue : timeProvider.GetUtcNow();
|
||||
|
||||
if (eventQueue is not null)
|
||||
{
|
||||
var payload = request.Payload ?? new JsonObject();
|
||||
|
||||
var notifyEvent = NotifyEvent.Create(
|
||||
eventId: eventId,
|
||||
kind: request.Kind!,
|
||||
tenant: tenantId,
|
||||
ts: ts,
|
||||
payload: payload,
|
||||
attributes: request.Attributes ?? new Dictionary<string, string>(),
|
||||
actor: request.Actor,
|
||||
version: "1");
|
||||
|
||||
var idempotencyKey = context.Request.Headers["Idempotency-Key"].ToString();
|
||||
if (string.IsNullOrWhiteSpace(idempotencyKey))
|
||||
{
|
||||
idempotencyKey = $"risk|{tenantId}|{notifyEvent.Kind}|{notifyEvent.EventId}";
|
||||
}
|
||||
|
||||
await eventQueue.PublishAsync(
|
||||
new NotifyQueueEventMessage(
|
||||
notifyEvent,
|
||||
stream: "notify:events",
|
||||
idempotencyKey: idempotencyKey,
|
||||
partitionKey: tenantId,
|
||||
traceId: context.TraceIdentifier),
|
||||
context.RequestAborted).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.ResumeToken))
|
||||
{
|
||||
context.Response.Headers["X-Resume-After"] = request.ResumeToken;
|
||||
}
|
||||
|
||||
return Results.Accepted();
|
||||
});
|
||||
|
||||
app.MapPost("/api/v1/notify/pack-approvals/{packId}/ack", async (
|
||||
HttpContext context,
|
||||
string packId,
|
||||
|
||||
Reference in New Issue
Block a user