import { Injectable, inject, resource } from '@angular/core'; import { Result, ok, err } from '@shared/kernel/fp'; import { DuoLookupDto, DuoDiplomaDto, PolicyQuestionDto, ManualDiplomaPolicyDto, } from '@registratie/contracts/duo-diplomas.dto'; import { ApiClient } from '@shared/infrastructure/api-client'; /** * 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. * Data comes from the .NET backend (`GET /api/duo/diplomas`) via the typed client. */ @Injectable({ providedIn: 'root' }) export class DuoAdapter { private client = inject(ApiClient); diplomasResource() { return resource({ loader: () => this.client.diplomas() }); } } 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; 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 { if (typeof json !== 'object' || json === null) return err('duo-lookup: not an object'); const dto = json as Partial; 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; 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 }); }