Add registratie wizard, BFF dashboard-view, contracts/value-objects, and architecture docs

Checkpoint of in-progress work: the registration wizard (address prefill,
DUO diploma lookup, policy questions), decision-DTO contracts, parse-don't-
validate value objects, infrastructure adapters, plus CLAUDE.md and the
architecture/ADR docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 17:23:52 +02:00
parent 8a8a2f0f29
commit 64385999eb
58 changed files with 7271 additions and 556 deletions

View File

@@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { httpResource } from '@angular/common/http';
import { Result, ok, err } from '@shared/kernel/fp';
import { DashboardViewDto, DashboardView } from '@registratie/contracts/dashboard-view.dto';
/**
* Infrastructure adapter for the screen-shaped ("BFF-lite") dashboard endpoint.
* ONE call returns registration + person + server-computed decisions.
* (In this POC the endpoint is a static mock; the decisions are precomputed to
* stand in for what the backend would compute.)
*/
@Injectable({ providedIn: 'root' })
export class DashboardViewAdapter {
// Typed as the DTO for ergonomics, but the value is still untrusted JSON —
// parseDashboardView validates it at the boundary before the app uses it.
dashboardViewResource() {
return httpResource<DashboardViewDto>(() => 'mock/dashboard-view.json');
}
}
/**
* Trust-boundary parse: validate the untrusted response shape and map the DTO
* onto our own domain model. Hand-written on purpose — no Zod for a single
* contract. ponytail: reach for a schema lib once the contract count grows.
*/
export function parseDashboardView(json: unknown): Result<string, DashboardView> {
if (typeof json !== 'object' || json === null) return err('dashboard-view: not an object');
const dto = json as Partial<DashboardViewDto>;
const reg = dto.registration;
if (!reg || typeof reg.bigNummer !== 'string' || !reg.status || typeof reg.status.tag !== 'string') {
return err('dashboard-view: missing/invalid registration');
}
const person = dto.person;
if (!person || !person.adres || typeof person.adres.postcode !== 'string') {
return err('dashboard-view: missing/invalid person');
}
const d = dto.decisions;
if (!d || typeof d.eligibleForHerregistratie !== 'boolean') {
return err('dashboard-view: missing/invalid decisions');
}
return ok({
profile: { registration: reg, person },
decisions: { eligibleForHerregistratie: d.eligibleForHerregistratie, herregistratieReason: d.herregistratieReason },
});
}