feat(acl): write the INGEDIEND record on submit and read records back (refs #153)
- OpenZaakAsync upserts a RegisterRecord with status INGEDIEND after opening the zaak, keyed on the same zaak id approval later upserts to INGESCHREVEN. The reference comes from the registration, so this path needs no ZGW read-back. - ObjectenGateway.GetAsync fetches an object by the URL a notification carried — no objecttype resolution, no search — and reads 404 as "no record" rather than an error. - POST /register-records/read exposes it to the Event Subscriber, which may not talk to Objecten itself (§8.1).
This commit is contained in:
@@ -90,6 +90,16 @@ app.MapPost("/zaken/reference", async (ZaakReferenceRequest body, AclService acl
|
||||
return Results.Ok(new { reference });
|
||||
});
|
||||
|
||||
// Read the register record an object in Objecten holds. The Event Subscriber projects a register
|
||||
// write from the notification NRC delivers, which carries only the object URL, and may not talk to
|
||||
// Objecten itself (§8.1, ADR-0028/ADR-0030). 404 when the object holds no record — the subscriber
|
||||
// treats that as "nothing to project" rather than an error (§8.6).
|
||||
app.MapPost("/register-records/read", async (RegisterRecordReadRequest body, AclService acl, CancellationToken ct) =>
|
||||
{
|
||||
var record = await acl.GetRegisterRecordAsync(new Uri(body.ObjectUrl), ct);
|
||||
return record is null ? Results.NotFound() : Results.Ok(record);
|
||||
});
|
||||
|
||||
// Store an uploaded diploma against a zaak (S-10b): the domain sends the file as base64; the ACL
|
||||
// creates the ZGW enkelvoudiginformatieobject and relates it to the zaak (§8.1). Returns its URL.
|
||||
app.MapPost("/documenten", async (StoreDocumentRequest body, AclService acl, CancellationToken ct) =>
|
||||
@@ -131,6 +141,9 @@ public sealed record CancelZaakRequest(string ZaakUrl);
|
||||
|
||||
public sealed record ZaakReferenceRequest(string ZaakUrl);
|
||||
|
||||
/// <summary>The object whose register record the Event Subscriber wants read back (S-19b-2).</summary>
|
||||
public sealed record RegisterRecordReadRequest(string ObjectUrl);
|
||||
|
||||
public sealed record StoreDocumentRequest(string ZaakUrl, string ContentBase64, string FileName, string ContentType);
|
||||
|
||||
public partial class Program;
|
||||
|
||||
@@ -24,7 +24,16 @@ public sealed class AclService(
|
||||
clock.Today,
|
||||
registration.Reference);
|
||||
|
||||
return await gateway.OpenZaakAsync(request, ct);
|
||||
var zaakUrl = await gateway.OpenZaakAsync(request, ct);
|
||||
|
||||
// The register — not ZGW — is what the read projection is sourced from (ADR-0028/ADR-0030),
|
||||
// so the record exists from submission, not only from approval. Same two-writes-converging
|
||||
// posture as ApproveZaakAsync: the upsert is keyed on the zaak id, so a retried submit
|
||||
// updates the record rather than adding a second one (§8.6).
|
||||
await register.UpsertAsync(
|
||||
new RegisterRecord(ZaakId(zaakUrl), RegisterRecordStatus.Ingediend, registration.Reference), ct);
|
||||
|
||||
return zaakUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -38,8 +39,29 @@ public sealed class ObjectenGateway(HttpClient http, ObjectenOptions options, IC
|
||||
"Updating the register record", ct);
|
||||
}
|
||||
|
||||
public Task<RegisterRecord?> GetAsync(Uri objectUrl, CancellationToken ct = default)
|
||||
=> throw new NotImplementedException();
|
||||
public async Task<RegisterRecord?> GetAsync(Uri objectUrl, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(objectUrl);
|
||||
|
||||
// Fetched by the URL the notification carried, so no objecttype resolution and no search —
|
||||
// unlike a write, which has to find the object for a registration id.
|
||||
using var message = new HttpRequestMessage(HttpMethod.Get, objectUrl);
|
||||
message.Headers.Authorization = new AuthenticationHeaderValue("Token", options.Token);
|
||||
message.Headers.Add("Accept-Crs", "EPSG:4326");
|
||||
|
||||
using var response = await http.SendAsync(message, ct);
|
||||
// The object may be gone by the time a (possibly redelivered) notification is handled —
|
||||
// there is simply nothing to project, which is not a failure (§8.6).
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await EnsureSuccessAsync(response, "Reading the register record", ct);
|
||||
|
||||
var body = await response.Content.ReadFromJsonAsync<ReadObjectDto>(ct)
|
||||
?? throw new InvalidOperationException("Objecten returned an empty object response");
|
||||
var data = body.Record?.Data;
|
||||
return data is null ? null : new RegisterRecord(data.Id, data.Status, data.Reference);
|
||||
}
|
||||
|
||||
private RecordDto NewRecord(int typeVersion, RecordDataDto data) =>
|
||||
new(typeVersion, data, clock.Today.ToString("yyyy-MM-dd"));
|
||||
@@ -144,6 +166,12 @@ public sealed class ObjectenGateway(HttpClient http, ObjectenOptions options, IC
|
||||
private sealed record ObjectDto(
|
||||
[property: JsonPropertyName("url")] string Url);
|
||||
|
||||
private sealed record ReadObjectDto(
|
||||
[property: JsonPropertyName("record")] ReadRecordDto? Record);
|
||||
|
||||
private sealed record ReadRecordDto(
|
||||
[property: JsonPropertyName("data")] RecordDataDto? Data);
|
||||
|
||||
private sealed record CreateObjectDto(
|
||||
[property: JsonPropertyName("type")] string Type,
|
||||
[property: JsonPropertyName("record")] RecordDto Record);
|
||||
|
||||
Reference in New Issue
Block a user