feat(stamdata): admin stamdata maintenance editor (beheer)
Realizes ADR-0004's "future low-code editor that commits a PR": an
admin-only stamdata maintenance editor built on the stamdata-as-code
foundation.
Backend: `professions` moves from a hardcoded C# dictionary to an embedded
`professions.json` data-file (typed as `ProfessionMapping`) with valid-time
(geldigVan/geldigTot, half-open). A generic, reflection-driven
StamdataCatalog/StamdataTable/StamdataFile describes every table so one
endpoint pair + one grid editor serve all of them; add a table in one line.
Two read-only, admin-gated endpoints (GET /stamdata, GET /stamdata/{table}
?peildatum=) — no runtime write path. Generic build gate
`Every_catalog_table_is_valid` (keys non-blank, no overlapping validity,
well-formed windows).
Frontend: new `beheer` context (route beheer/stamdata, capabilityGuard
'stamdata:edit'). A schema-driven grid editor edits rows locally; download()
emits {table}.json for the admin to commit as a reviewed PR (no mutation
command — the CI build + StamdataValidationTests stay the authority).
Full gate GREEN both sides; gen:api leaves no drift; new stamdata story
passes axe. See WP-29 + ADR-0004.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ using BigRegister.Domain.Intake;
|
||||
using BigRegister.Domain.Letters;
|
||||
using BigRegister.Domain.Registrations;
|
||||
using BigRegister.Domain.Submissions;
|
||||
using BigRegister.Stamdata;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
@@ -100,6 +101,33 @@ api.MapGet("/duo/diplomas", () => new DuoLookupDto(
|
||||
|
||||
api.MapGet("/intake/policy", () => new IntakePolicyDto(IntakePolicy.ScholingThreshold));
|
||||
|
||||
// --- Stamdata maintenance (ADR-0004): generic, schema-driven reads for the admin editor.
|
||||
// One pair of endpoints serves every business-editable table; the editor renders from the
|
||||
// reflected column schema and produces an edited JSON file the admin drops into the repo
|
||||
// (the existing CI build + StamdataValidationTests stay the authority — no write endpoint).
|
||||
// Admin-gated, mirroring OrgAdmin. ---
|
||||
|
||||
api.MapGet("/stamdata", (HttpContext ctx) => StamdataAdmin(ctx, () =>
|
||||
Results.Ok(StamdataCatalog.All.Select(t =>
|
||||
new StamdataTableSummaryDto(t.Id, t.Label, t.Columns.Select(ToColumnDto).ToList(), t.Temporal)).ToList())))
|
||||
.WithName("stamdataTables")
|
||||
.Produces<List<StamdataTableSummaryDto>>()
|
||||
.ProducesProblem(StatusCodes.Status403Forbidden);
|
||||
|
||||
// peildatum (optional): omitted = all rows (edit view); given = only rows valid on that
|
||||
// date (the temporal preview — "which mappings applied on date X").
|
||||
api.MapGet("/stamdata/{table}", (string table, string? peildatum, HttpContext ctx) => StamdataAdmin(ctx, () =>
|
||||
{
|
||||
var t = StamdataCatalog.Find(table);
|
||||
if (t is null) return Results.NotFound();
|
||||
var rows = peildatum is { Length: > 0 } p ? t.RowsOn(DateOnly.Parse(p)) : t.Rows();
|
||||
return Results.Ok(new StamdataTableDto(t.Id, t.Label, t.Columns.Select(ToColumnDto).ToList(), t.Temporal, rows));
|
||||
}))
|
||||
.WithName("stamdataTable")
|
||||
.Produces<StamdataTableDto>()
|
||||
.ProducesProblem(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
// --- POST: submits. The server is the authority; it re-validates and decides. ---
|
||||
|
||||
api.MapPost("/registrations", (RegistratieRequest req, HttpContext ctx) =>
|
||||
@@ -471,6 +499,19 @@ IResult OrgAdmin(HttpContext ctx, Func<IResult> action)
|
||||
statusCode: StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
// One gate for every stamdata read endpoint — the enforce twin of the `stamdata:edit`
|
||||
// capability RoleCapabilities emits (single Authz source). A denial is audited.
|
||||
IResult StamdataAdmin(HttpContext ctx, Func<IResult> action)
|
||||
{
|
||||
var principal = Authz.ResolvePrincipal(ctx);
|
||||
if (Authz.CanEditStamdata(principal)) return action();
|
||||
AuditAuthz(ctx, "stamdata:edit", "stamdata", false, principal);
|
||||
return Results.Problem(detail: "Alleen een beheerder mag stamdata onderhouden.",
|
||||
statusCode: StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
static StamdataColumnDto ToColumnDto(StamdataColumn c) => new(c.Name, c.Type, c.IsKey, c.Options);
|
||||
|
||||
// Authorization audit (PRD-0002 §8): access-relevant decisions recorded with NO PII —
|
||||
// action, resource ref, allow/deny, acting role, correlation id. Never the value that
|
||||
// was (or wasn't) revealed. Mirrors the no-PII Submit audit below.
|
||||
|
||||
Reference in New Issue
Block a user