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:
@@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { httpResource } from '@angular/common/http';
|
||||
import { Result, ok, err } from '@shared/kernel/fp';
|
||||
import { DuoLookupDto, DuoDiplomaDto, PolicyQuestionDto, ManualDiplomaPolicyDto } from '@registratie/contracts/duo-diplomas.dto';
|
||||
|
||||
const EMPTY: DuoLookupDto = { diplomas: [], handmatig: { beroepen: [], policyQuestions: [] } };
|
||||
|
||||
/**
|
||||
* Infrastructure adapter for the DUO diploma lookup, reached only through our own
|
||||
* ("BFF-lite") endpoint — the anti-corruption boundary. The response carries the
|
||||
* user's diplomas (each with its server-computed beroep + policy questions) and
|
||||
* the manual-entry fallback policy. The frontend renders; it does not derive. In
|
||||
* this POC the endpoint is a static mock.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DuoAdapter {
|
||||
diplomasResource() {
|
||||
return httpResource<DuoLookupDto>(() => 'mock/duo-diplomas.json', { defaultValue: EMPTY });
|
||||
}
|
||||
}
|
||||
|
||||
function parseQuestions(json: unknown): PolicyQuestionDto[] | null {
|
||||
if (!Array.isArray(json)) return null;
|
||||
const out: PolicyQuestionDto[] = [];
|
||||
for (const q of json) {
|
||||
if (typeof q !== 'object' || q === null) return null;
|
||||
const p = q as Partial<PolicyQuestionDto>;
|
||||
if (typeof p.id !== 'string' || typeof p.vraag !== 'string' || (p.type !== 'ja-nee' && p.type !== 'tekst')) return null;
|
||||
out.push({ id: p.id, vraag: p.vraag, type: p.type });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Trust-boundary parse: validate the untrusted response shape (diplomas, each
|
||||
with derived beroep + policy questions, plus the manual-entry fallback). */
|
||||
export function parseDuoLookup(json: unknown): Result<string, DuoLookupDto> {
|
||||
if (typeof json !== 'object' || json === null) return err('duo-lookup: not an object');
|
||||
const dto = json as Partial<DuoLookupDto>;
|
||||
if (!Array.isArray(dto.diplomas)) return err('duo-lookup: missing diplomas');
|
||||
|
||||
const diplomas: DuoDiplomaDto[] = [];
|
||||
for (const item of dto.diplomas) {
|
||||
if (typeof item !== 'object' || item === null) return err('duo-lookup: invalid diploma');
|
||||
const d = item as Partial<DuoDiplomaDto>;
|
||||
const vragen = parseQuestions(d.policyQuestions);
|
||||
if (typeof d.id !== 'string' || typeof d.naam !== 'string' || typeof d.beroep !== 'string' || vragen === null) {
|
||||
return err('duo-lookup: missing/invalid diploma fields');
|
||||
}
|
||||
diplomas.push({ id: d.id, naam: d.naam, instelling: d.instelling ?? '', jaar: typeof d.jaar === 'number' ? d.jaar : 0, beroep: d.beroep, policyQuestions: vragen });
|
||||
}
|
||||
|
||||
const hm = dto.handmatig;
|
||||
const hmVragen = hm ? parseQuestions(hm.policyQuestions) : null;
|
||||
if (!hm || !Array.isArray(hm.beroepen) || hmVragen === null) {
|
||||
return err('duo-lookup: missing/invalid handmatig fallback');
|
||||
}
|
||||
const handmatig: ManualDiplomaPolicyDto = { beroepen: hm.beroepen, policyQuestions: hmVragen };
|
||||
|
||||
return ok({ diplomas, handmatig });
|
||||
}
|
||||
Reference in New Issue
Block a user