feat(event-subscriber): NRC event subscriber + rebuildable read projection (closes #7) #59

Merged
not merged 7 commits from feat/7-event-subscriber-projection into main 2026-06-30 13:57:00 +00:00
Showing only changes of commit 06d8d13e19 - Show all commits

View File

@@ -1,3 +1,4 @@
using System.Text.Json;
using EventSubscriber.Application;
using Projection.ReadModel;
@@ -22,18 +23,23 @@ await app.Services.MigrateProjectionAsync();
app.MapGet("/health", () => "Healthy");
// The NRC abonnement callback. Open Notificaties POSTs a notification here; we project it.
// Auth-on-callback is mandatory: without the configured Authorization, return 401 (NRC's
// registration probe depends on this — ADR-0007).
// Auth-on-callback is mandatory: the auth check runs *before* the body is read, so NRC's
// registration probe (a POST without the configured Authorization, and without a valid
// notification body) gets the 401 it requires rather than a 400 (ADR-0007).
app.MapPost("/notifications", async (
HttpRequest request,
NotificationDto body,
NotificationProjector projector,
CancellationToken ct) =>
{
if (request.Headers.Authorization != webhookToken)
return Results.Unauthorized();
await projector.HandleAsync(body.ToNotification(), ct);
var dto = await JsonSerializer.DeserializeAsync<NotificationDto>(
request.Body, SerializerOptions, ct);
if (dto is null)
return Results.BadRequest();
await projector.HandleAsync(dto.ToNotification(), ct);
return Results.NoContent();
});
@@ -53,4 +59,8 @@ public sealed record NotificationDto(string Kanaal, string Resource, string Acti
public Notification ToNotification() => new(Kanaal, Resource, Actie, ResourceUrl);
}
public partial class Program;
public partial class Program
{
// NRC sends camelCase JSON; match it case-insensitively.
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
}