fix(brief): make GET /brief a pure query, 404 when absent (RB-23)
GET /brief allocated a row on first call (BriefStore.GetOrCreate) — the one endpoint in the backend where a read performed a persisted write. The FE retries GETs automatically, so a transient failure could enter the create path more than once; a lock prevented a duplicate row, but the safety depended on the lock, not on the endpoint being a query. Split GetOrCreate into Get (a pure query) and the already-existing ResetAndCreate (POST /brief/reset owns creation). GET /brief now 404s when the owner has no brief yet. GET /brief/preview used GetOrCreate too, so it gets the same Get + 404 treatment, forced by the split. RB-22 already made BriefStore.load() on the FE tolerate a 404 by calling reset() once; this ticket is what makes that branch live. Updated the brief/preview/org-template backend tests that assumed GET seeded a brief on first call to create one explicitly first, and added a test that GET 404s and writes no row without the fix (verified red beforehand). Regenerated the API client (npm run gen:api). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -675,10 +675,15 @@ api.MapPut("/admin/flags/{key}", (string key, SetFeatureFlagRequest req, HttpCon
|
||||
|
||||
api.MapGet("/brief", (HttpContext ctx) =>
|
||||
{
|
||||
var e = BriefStore.GetOrCreate(ctx.Zorgverlener().Bsn);
|
||||
return ToView(ctx, e);
|
||||
// RB-23/CQ-007: a read that used to allocate a row on first call. The owner's first
|
||||
// draft now comes only from the explicit POST /brief/reset (BriefStore.ResetAndCreate)
|
||||
// — this GET is a pure query and 404s when there is nothing to read yet.
|
||||
var e = BriefStore.Get(ctx.Zorgverlener().Bsn);
|
||||
if (e is null) return Results.NotFound();
|
||||
return Results.Ok(ToView(ctx, e));
|
||||
})
|
||||
.Produces<BriefViewDto>();
|
||||
.Produces<BriefViewDto>()
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
api.MapPut("/brief", (SaveBriefRequest req, HttpContext ctx) =>
|
||||
{
|
||||
@@ -766,7 +771,12 @@ api.MapPost("/brief/reveal-bignummer", (HttpContext ctx) =>
|
||||
// letters serve their frozen archive; anything else renders live with a watermark.
|
||||
api.MapGet("/brief/preview", (HttpContext ctx) =>
|
||||
{
|
||||
var e = BriefStore.GetOrCreate(ctx.Zorgverlener().Bsn);
|
||||
// RB-23: BriefStore.GetOrCreate is gone (split into Get + ResetAndCreate). This GET
|
||||
// must not create a brief as a side effect either, so it 404s under the same
|
||||
// precondition as GET /brief — in the running app the FE only reaches this endpoint
|
||||
// from the brief page, which has already loaded (and, if needed, reset) a brief.
|
||||
var e = BriefStore.Get(ctx.Zorgverlener().Bsn);
|
||||
if (e is null) return Results.NotFound();
|
||||
if (e.Status.Tag == "sent" && e.ArchivedHtml is { } archived)
|
||||
return Results.Content(archived, "text/html");
|
||||
var template = OrgTemplateStore.TemplateForBrief(e.SubOrgId, null);
|
||||
|
||||
Reference in New Issue
Block a user