feat(domain,bff): current-registration lookup for self-service resume (refs #111)
Backend half of S-26. The domain gains IRegistrationStore.FindOpenByBsnAsync (the citizen's non-terminal INGEDIEND/IN_BEHANDELING registration) + GET /registrations/current?bsn=; the BFF adds owner-scoped GET /self-service/registrations (bsn from the DigiD token) returning that registration or 204 when none. Regenerated services/bff/openapi.json for the new endpoint (the api-client is generated from it). Store + BFF endpoint unit tests; acceptance/BFF fakes updated for the new members. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,10 @@ namespace Bff.Api;
|
||||
/// <summary>What the self-service submit returns to the portal (the domain's registration id + status).</summary>
|
||||
public sealed record SubmitAccepted(string RegistrationId, string Status);
|
||||
|
||||
/// <summary>The caller's current open registration, for resuming the self-service portal after a
|
||||
/// refresh (S-26): the reference (registration id) + its status.</summary>
|
||||
public sealed record CurrentRegistration(string RegistrationId, string Status);
|
||||
|
||||
/// <summary>A projection row as the projection-api serves it. <c>Bsn</c>/<c>NaamPlaceholder</c> are
|
||||
/// read but never surfaced by the openbaar endpoint (public-safe filtering, ADR-0010/S-09).
|
||||
/// <c>Reference</c> is the public-safe citizen reference (the zaak identificatie, #78).</summary>
|
||||
@@ -22,6 +26,10 @@ public interface IDomainClient
|
||||
{
|
||||
Task<SubmitAccepted> SubmitRegistrationAsync(string bsn, CancellationToken ct = default);
|
||||
|
||||
/// <summary>The caller's current open registration (resume after refresh, S-26), or <c>null</c>
|
||||
/// when they have none in flight. Owner-scoped by <paramref name="bsn"/>.</summary>
|
||||
Task<CurrentRegistration?> GetCurrentRegistrationAsync(string bsn, CancellationToken ct = default);
|
||||
|
||||
/// <summary>Withdraw the caller's own registration ("trek aanvraag in"). Owner-scoped by
|
||||
/// <paramref name="bsn"/>. Returns <c>false</c> when the domain reports the registration is
|
||||
/// unknown or not the caller's (404), so the BFF can relay a 404 rather than a 500.</summary>
|
||||
@@ -58,6 +66,18 @@ public sealed class DomainClient(HttpClient http) : IDomainClient
|
||||
return new SubmitAccepted(dto.RegistrationId, dto.Status);
|
||||
}
|
||||
|
||||
public async Task<CurrentRegistration?> GetCurrentRegistrationAsync(string bsn, CancellationToken ct = default)
|
||||
{
|
||||
using var response = await http.GetAsync($"registrations/current?bsn={Uri.EscapeDataString(bsn)}", ct);
|
||||
// The domain 404s when the citizen has no open registration — that's "none", not an error.
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return null;
|
||||
response.EnsureSuccessStatusCode();
|
||||
var dto = await response.Content.ReadFromJsonAsync<DomainResponse>(ct)
|
||||
?? throw new InvalidOperationException("The Domain Service returned an empty registration response.");
|
||||
return new CurrentRegistration(dto.RegistrationId, dto.Status);
|
||||
}
|
||||
|
||||
public async Task<bool> WithdrawRegistrationAsync(string registrationId, string bsn, CancellationToken ct = default)
|
||||
{
|
||||
using var response = await http.PostAsJsonAsync(
|
||||
|
||||
@@ -86,6 +86,24 @@ app.MapPost("/self-service/registrations", async (ClaimsPrincipal user, IDomainC
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status401Unauthorized);
|
||||
|
||||
// Self-service resume (S-26): the signed-in zorgprofessional's current open registration, so the
|
||||
// portal can restore its reference + actions after a page refresh. The bsn comes from the DigiD token;
|
||||
// 204 when the citizen has none in flight (so the portal shows the submit form).
|
||||
app.MapGet("/self-service/registrations", async (ClaimsPrincipal user, IDomainClient domain, CancellationToken ct) =>
|
||||
{
|
||||
var bsn = user.FindFirstValue("bsn");
|
||||
if (string.IsNullOrWhiteSpace(bsn))
|
||||
return Results.BadRequest("The token carries no bsn claim.");
|
||||
|
||||
var current = await domain.GetCurrentRegistrationAsync(bsn, ct);
|
||||
return current is null ? Results.NoContent() : Results.Ok(current);
|
||||
})
|
||||
.RequireAuthorization()
|
||||
.Produces<CurrentRegistration>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status401Unauthorized);
|
||||
|
||||
// Self-service withdrawal (S-11): the signed-in zorgprofessional withdraws their own registration.
|
||||
// The bsn comes from the DigiD token and is forwarded to the domain, which owner-scopes the action;
|
||||
// a registration that is unknown or not the caller's comes back 404 (ownership is not revealed).
|
||||
|
||||
Reference in New Issue
Block a user