Files
atomic-design-poc/backend
ehoandClaude Sonnet 5 ebf1f8f8b4
CI / changes (push) Successful in 8s
CI / lint (push) Successful in 10s
CI / frontend (push) Successful in 13s
CI / storybook-a11y (push) Successful in 17s
CI / backend (push) Successful in 1m45s
CI / semgrep (push) Successful in 1m9s
CI / e2e (push) Successful in 2m53s
CI / api-client-drift (push) Successful in 1m46s
fix(backend): run the prod image as non-root (semgrep, live Gitea finding)
The pushed WP-30 item-5 Dockerfile predated the semgrep triage's local run —
CI's now-blocking semgrep gate caught what local verification couldn't:
dockerfile.security.missing-user-entrypoint (no USER, container runs as root).

mcr.microsoft.com/dotnet/aspnet:10.0 ships a pre-created non-root user for
exactly this ($APP_UID, uid/gid 1654) — switched to it, with --chown on both
COPY layers so the app can still create/write bigregister.db (WP-22, a
relative-path SQLite connection string resolved against the container's /app
cwd) as that user.

Verified for real: rebuilt, confirmed `whoami` is `app` inside the container,
ran it and curled a live GET /api/v1/brief/preview (200), confirmed
bigregister.db was created and is actually owned by app:app. Full semgrep
re-run (this file didn't exist during the original triage) is now 0 findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 10:11:54 +02:00
..

BIG-register BFF (ASP.NET Core)

The backend that hosts the business rules for the BIG-register portal. The frontend renders the decisions this service computes; it does not recompute them (BFF-lite + decision DTOs — see ../docs/reference/architecture/0001-bff-lite-decision-dtos.md).

No real BRP/DUO: the reference data they'd return (registration, person, diplomas, notes — Data/SeedData.cs) is in-memory and seeded, but the endpoints, DTOs, status codes and error envelope are production-shaped.

Applications, documents and the brief persist to a SQLite file (src/BigRegister.Api/bigregister.db, EF Core-backed — Data/AppDbContext.cs, Data/Db.cs) created and migrated on first run; restarting the process (or docker compose restart api — the existing ./backend:/src bind mount already covers it, see docker-compose.yml) does not lose data. Delete the file to reset demo data back to empty, the same state a fresh clone starts from. This is a deliberate, right-sized choice for a POC (SQLite, no external DB service) — see docs/project/backlog/WP-22-durable-persistence.md.

Run

Everything (docker-compose, from repo root)

docker compose up

Backend only (local)

cd backend
dotnet run --project src/BigRegister.Api
# → http://localhost:5000/swagger

Frontend against a local backend

npm start          # ng serve, proxies /api → http://localhost:5000 (proxy.conf.json)

Tests

cd backend && dotnet test      # rule unit tests + endpoint integration tests

API

Method Route Purpose
GET /api/dashboard-view registration + person + computed herregistratie decision
GET /api/notes specialisms / aantekeningen
GET /api/brp/address BRP address lookup (gevonden:false = no address)
GET /api/duo/diplomas diplomas with derived profession + applicable policy questions, + manual fallback
GET /api/intake/policy scholing threshold (config value)
POST /api/registrations submit registration → reference, or 422 (manual diploma)
POST /api/herregistraties submit re-registration → reference, or 422 (0 hours)
POST /api/intakes submit intake → reference, or 422 (0 hours)

Rejections use ProblemDetails (RFC 7807) with status 422. Every request carries an X-Correlation-Id (set by the FE fetch adapter); the backend echoes it into a no-PII submit-audit log line (kind, outcome, reference, correlation id) — the seam for real structured logging / an audit store.

Versioning

Endpoints live under /api/v1. Additive changes (a new optional field) stay on v1: the NSwag-generated client and the FE parse* boundary ignore unknown fields, so old clients keep working. A breaking change (renamed/removed field, changed semantics) is introduced as /api/v2 served alongside v1 until clients migrate.

Where the rules live (src/BigRegister.Api/Domain/)

  • Diplomas/DiplomaRules.cs — profession derivation + which policy questions apply.
  • Registrations/HerregistratieRule.cs — eligibility + reason + status invariant.
  • Intake/IntakePolicy.cs — scholing threshold.
  • Submissions/SubmissionRules.cs — submit rejections + reference generation.

Typed client (NSwag)

The frontend calls this API through a generated TypeScript client. Regenerate it from the contract after a shape change:

npm run gen:api    # builds backend → swagger.json → src/app/shared/infrastructure/api-client.ts

Maintainability: changing a policy is one backend change

Goal: require every Verpleegkundige diploma to confirm a Dutch skills assessment. This is a new policy question on a diploma type.

Edit one fileDomain/Diplomas/DiplomaRules.cs:

 public static IReadOnlyList<PolicyQuestion> QuestionsFor(Diploma d)
 {
     var questions = new List<PolicyQuestion>();
     if (d.Engelstalig)
         questions.Add(NlTaalEngelstalig);
+    if (d.Opleiding == "verpleegkunde")
+        questions.Add(new PolicyQuestion(
+            "bekwaamheid",
+            "Heeft u in de afgelopen vijf jaar een bekwaamheidstoets afgelegd?",
+            QuestionType.JaNee));
     return questions;
 }

Rebuild the backend (docker compose up or dotnet run). The new question now appears in the registration wizard for HBO-Verpleegkunde.

  • No frontend change. The FE renders whatever questions the API returns.
  • No client regeneration. The wire shape (PolicyQuestionDto) is unchanged — only the data behind it. npm run gen:api is only needed when a DTO shape changes.

Add a unit test for the new rule in tests/BigRegister.Tests/RuleTests.cs and you're done.