From 385708c6264447c42a0a32105520aa1753049a6f Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Thu, 4 Jun 2026 09:48:30 +0200 Subject: [PATCH] feat(acl): implement OpenZaak default-fill + ZGW gateway (refs #5) AclService.OpenZaakAsync builds a ZaakRequest from the configured defaults (bronorganisatie, verantwoordelijkeOrganisatie, vertrouwelijkheidaanduiding, zaaktype) plus today's date from the clock, and delegates to the gateway. OpenZaakGateway POSTs the zaak to /zaken/api/v1/zaken with a ZGW Bearer JWT and the CRS headers, returning the created zaak URL. Tests pass (green). Add a root register-referentie.slnx (bff + acl) and point `make ci` at it so the gate covers every .NET service. Co-Authored-By: Claude Opus 4.8 --- Makefile | 2 +- register-referentie.slnx | 13 ++++++ services/acl/Acl.Application/AclService.cs | 13 +++++- .../acl/Acl.Infrastructure/OpenZaakGateway.cs | 42 ++++++++++++++++++- 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 register-referentie.slnx diff --git a/Makefile b/Makefile index 60a648e..49442f5 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # `make ci` locally runs exactly what the pipeline runs — no drift. Until a # self-hosted runner is registered, `make ci` is the gate (see docs/runbooks/ci.md). -SLN := services/bff/Bff.slnx +SLN := register-referentie.slnx COMPOSE := infra/docker-compose.yml HEALTH_URL := http://localhost:8080/health OZ_COMPOSE := infra/openzaak/docker-compose.yml diff --git a/register-referentie.slnx b/register-referentie.slnx new file mode 100644 index 0000000..bf80fac --- /dev/null +++ b/register-referentie.slnx @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/services/acl/Acl.Application/AclService.cs b/services/acl/Acl.Application/AclService.cs index effb09d..e285ee7 100644 --- a/services/acl/Acl.Application/AclService.cs +++ b/services/acl/Acl.Application/AclService.cs @@ -5,5 +5,16 @@ namespace Acl.Application; public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, IClock clock) { public Task OpenZaakAsync(DomainRegistration registration, CancellationToken ct = default) - => throw new NotImplementedException(); + { + ArgumentNullException.ThrowIfNull(registration); + + var request = new ZaakRequest( + defaults.Bronorganisatie, + defaults.VerantwoordelijkeOrganisatie, + defaults.Vertrouwelijkheidaanduiding, + defaults.ZaaktypeUrl, + clock.Today); + + return gateway.OpenZaakAsync(request, ct); + } } diff --git a/services/acl/Acl.Infrastructure/OpenZaakGateway.cs b/services/acl/Acl.Infrastructure/OpenZaakGateway.cs index 3332a1e..0a6e2e4 100644 --- a/services/acl/Acl.Infrastructure/OpenZaakGateway.cs +++ b/services/acl/Acl.Infrastructure/OpenZaakGateway.cs @@ -1,3 +1,6 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json.Serialization; using Acl.Application; namespace Acl.Infrastructure; @@ -5,6 +8,41 @@ namespace Acl.Infrastructure; /// The only code that talks to OpenZaak's Zaken API (ADR-0001). public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) : IZaakGateway { - public Task OpenZaakAsync(ZaakRequest request, CancellationToken ct = default) - => throw new NotImplementedException(); + public async Task OpenZaakAsync(ZaakRequest request, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(request); + + using var message = new HttpRequestMessage( + HttpMethod.Post, new Uri(options.BaseUrl, "/zaken/api/v1/zaken")) + { + Content = JsonContent.Create(new ZaakDto( + request.Bronorganisatie, + request.Zaaktype.ToString(), + request.VerantwoordelijkeOrganisatie, + request.Startdatum.ToString("yyyy-MM-dd"), + request.Vertrouwelijkheidaanduiding)), + }; + message.Headers.Authorization = + new AuthenticationHeaderValue("Bearer", ZgwToken.Mint(options.ClientId, options.Secret)); + // ZRC is a geo API; it requires the CRS headers. + message.Headers.Add("Accept-Crs", "EPSG:4326"); + message.Content.Headers.Add("Content-Crs", "EPSG:4326"); + + using var response = await http.SendAsync(message, ct); + response.EnsureSuccessStatusCode(); + + var created = await response.Content.ReadFromJsonAsync(ct) + ?? throw new InvalidOperationException("OpenZaak returned an empty zaak response"); + return new Uri(created.Url); + } + + private sealed record ZaakDto( + [property: JsonPropertyName("bronorganisatie")] string Bronorganisatie, + [property: JsonPropertyName("zaaktype")] string Zaaktype, + [property: JsonPropertyName("verantwoordelijkeOrganisatie")] string VerantwoordelijkeOrganisatie, + [property: JsonPropertyName("startdatum")] string Startdatum, + [property: JsonPropertyName("vertrouwelijkheidaanduiding")] string Vertrouwelijkheidaanduiding); + + private sealed record ZaakCreatedDto( + [property: JsonPropertyName("url")] string Url); }