## What & why S-15a, the first of the S-15 (#16) split. A new **beheer** portal (medewerker realm, like behandel) shows the ZTC catalogus — the published zaaktypen — **read-only**. A beheerder logs in and sees the seeded BIG-REGISTRATIE zaaktype. Closes #130 ### The vertical portal → BFF `GET /beheer/catalogi/zaaktypen` (medewerker realm + `beheerder` role) → ACL `GET /catalogi/zaaktypen` → ZGW Catalogi API. - **ACL**: new read-only `GET /catalogi/zaaktypen` listing published zaaktypen (reuses the ADR-0021 Catalogi client; public-safe `identificatie`/`omschrijving`). - **BFF**: new typed `IAclClient` + `Downstream:Acl:BaseUrl`, and `GET /beheer/catalogi/zaaktypen` behind a new `beheerder` policy (reuses the medewerker bearer scheme + realm-role lifting). OpenAPI spec + generated Angular client regenerated. - **Keycloak**: `beheerder` realm role + `bram-beheerder` test user in the medewerker realm. - **Frontend**: new `apps/beheer` Angular app (copied from behandel) with a read-only catalogus page; `SECURE_API_ROUTES=['/beheer/']`. - **Infra**: `beheer` compose service (port 8143), added to `WAIT_SVCS` + CI log-dump; a Playwright e2e (beheerder login → catalogus shows BIG-REGISTRATIE). ### New boundary → ADR-0025 The BFF now reaches the **ACL directly** for the catalogus read — a new service-to-service edge (§14). The catalogus is neither a domain nor a projection concern, and §8.1 means only the ACL may read ZGW; routing through the domain would pollute it with a non-domain passthrough. §8.1/§8.3 stay intact. Recorded in **ADR-0025**. ## Definition of Done - [x] Failing test committed before each implementation (red→green per layer: ACL, BFF, frontend). - [x] Conventional Commits referencing #130. - [ ] CI green — pending Gitea Actions run. - [x] `docker compose up` brings up `beheer` (health-gated in `WAIT_SVCS`). - [x] Docs — ADR-0025 + demo-script S-15a note. - [x] Demo note in `docs/demo-script.md`. ## Verified locally lint (`dotnet format`) ✓ · .NET unit (Acl 57 / Big 152 / EventSubscriber 19 / Bff 40) ✓ · frontend lint+test (8 projects) ✓ · frontend build (4 apps) ✓. Mutation ratchet: added a gateway unit test for the new `ListZaaktypenAsync` mapping so the ACL score holds. verify-stack (compose smoke + e2e) runs in CI. ## Notes for reviewers - The BFF drops the ZGW URL from `BeheerZaaktype` (public-safe: identificatie + omschrijving only). - The catalogus e2e asserts on the stable seeded `BIG-REGISTRATIE` (not a per-test reference), safe on the shared verify stack. - Follow-ups: **S-15b** (#131) default-fill CRUD, **S-15c** (#132) medewerker-realm MFA. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #133
450 lines
15 KiB
TypeScript
450 lines
15 KiB
TypeScript
/**
|
|
* Generated by orval v8.19.0 🍺
|
|
* Do not edit manually.
|
|
* Bff.Api | v1
|
|
* OpenAPI spec version: 1.0.0
|
|
*/
|
|
import {
|
|
HttpClient,
|
|
HttpHeaders,
|
|
HttpResponse as AngularHttpResponse
|
|
} from '@angular/common/http';
|
|
import type {
|
|
HttpContext,
|
|
HttpEvent,
|
|
HttpParams
|
|
} from '@angular/common/http';
|
|
|
|
import {
|
|
Injectable,
|
|
inject
|
|
} from '@angular/core';
|
|
|
|
import {
|
|
Observable
|
|
} from 'rxjs';
|
|
|
|
export interface BeheerZaaktype {
|
|
identificatie: string;
|
|
omschrijving: string;
|
|
}
|
|
|
|
export interface CurrentRegistration {
|
|
registrationId: string;
|
|
status: string;
|
|
}
|
|
|
|
export interface DecideRequest {
|
|
besluit: string;
|
|
}
|
|
|
|
export interface OpenbaarEntry {
|
|
id: string;
|
|
status: string;
|
|
/** @nullable */
|
|
reference: string | null;
|
|
}
|
|
|
|
export interface ProvideDocumentsRequest {
|
|
contentBase64: string;
|
|
/** @nullable */
|
|
fileName?: string | null;
|
|
/** @nullable */
|
|
contentType?: string | null;
|
|
}
|
|
|
|
export interface SubmitAccepted {
|
|
registrationId: string;
|
|
status: string;
|
|
}
|
|
|
|
export interface WerkbakItem {
|
|
registrationId: string;
|
|
bsn: string;
|
|
status: string;
|
|
}
|
|
|
|
export type GetOpenbaarRegisterParams = {
|
|
q?: string;
|
|
};
|
|
|
|
interface HttpClientOptions {
|
|
readonly headers?: HttpHeaders | Record<string, string | string[]>;
|
|
readonly context?: HttpContext;
|
|
readonly params?:
|
|
| HttpParams
|
|
| Record<string, string | number | boolean | Array<string | number | boolean>>;
|
|
readonly reportProgress?: boolean;
|
|
readonly withCredentials?: boolean;
|
|
readonly credentials?: RequestCredentials;
|
|
readonly keepalive?: boolean;
|
|
readonly priority?: RequestPriority;
|
|
readonly cache?: RequestCache;
|
|
readonly mode?: RequestMode;
|
|
readonly redirect?: RequestRedirect;
|
|
readonly referrer?: string;
|
|
readonly integrity?: string;
|
|
readonly referrerPolicy?: ReferrerPolicy;
|
|
readonly transferCache?: {includeHeaders?: string[]} | boolean;
|
|
readonly timeout?: number;
|
|
}
|
|
|
|
type HttpClientBodyOptions = HttpClientOptions & {
|
|
readonly observe?: 'body';
|
|
};
|
|
|
|
type HttpClientEventOptions = HttpClientOptions & {
|
|
readonly observe: 'events';
|
|
};
|
|
|
|
type HttpClientResponseOptions = HttpClientOptions & {
|
|
readonly observe: 'response';
|
|
};
|
|
|
|
type HttpClientObserveOptions = HttpClientOptions & {
|
|
readonly observe?: 'body' | 'events' | 'response';
|
|
};
|
|
|
|
type AngularHttpParamValue = string | number | boolean | Array<string | number | boolean>;
|
|
type AngularHttpParamValueWithNullable = AngularHttpParamValue | null;
|
|
|
|
function filterParams(
|
|
params: Record<string, unknown>,
|
|
requiredNullableKeys?: ReadonlySet<string>,
|
|
preserveRequiredNullables?: false,
|
|
passthroughKeys?: undefined,
|
|
): Record<string, AngularHttpParamValue>;
|
|
function filterParams(
|
|
params: Record<string, unknown>,
|
|
requiredNullableKeys: ReadonlySet<string> | undefined,
|
|
preserveRequiredNullables: true,
|
|
passthroughKeys?: undefined,
|
|
): Record<string, AngularHttpParamValueWithNullable>;
|
|
function filterParams(
|
|
params: Record<string, unknown>,
|
|
requiredNullableKeys: ReadonlySet<string> | undefined,
|
|
preserveRequiredNullables: boolean | undefined,
|
|
passthroughKeys: ReadonlySet<string>,
|
|
): Record<string, unknown>;
|
|
function filterParams(
|
|
params: Record<string, unknown>,
|
|
requiredNullableKeys: ReadonlySet<string> = new Set(),
|
|
preserveRequiredNullables = false,
|
|
passthroughKeys: ReadonlySet<string> = new Set(),
|
|
): Record<string, unknown> {
|
|
const filteredParams: Record<string, unknown> = {};
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (passthroughKeys.has(key)) {
|
|
if (value !== undefined) {
|
|
filteredParams[key] = value;
|
|
}
|
|
continue;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
const filtered = value.filter(
|
|
(item) =>
|
|
item != null &&
|
|
(typeof item === 'string' ||
|
|
typeof item === 'number' ||
|
|
typeof item === 'boolean'),
|
|
) as Array<string | number | boolean>;
|
|
if (filtered.length) {
|
|
filteredParams[key] = filtered;
|
|
}
|
|
} else if (
|
|
preserveRequiredNullables &&
|
|
value === null &&
|
|
requiredNullableKeys.has(key)
|
|
) {
|
|
filteredParams[key] = null;
|
|
} else if (
|
|
value != null &&
|
|
(typeof value === 'string' ||
|
|
typeof value === 'number' ||
|
|
typeof value === 'boolean')
|
|
) {
|
|
filteredParams[key] = value;
|
|
}
|
|
}
|
|
return filteredParams;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class BffApiV1Service {
|
|
private readonly http = inject(HttpClient);
|
|
postSelfServiceRegistrations<TData = SubmitAccepted>( options?: HttpClientBodyOptions): Observable<TData>;
|
|
postSelfServiceRegistrations<TData = SubmitAccepted>( options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
postSelfServiceRegistrations<TData = SubmitAccepted>( options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
postSelfServiceRegistrations<TData = SubmitAccepted>(
|
|
options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
if (options?.observe === 'events') {
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations`,
|
|
undefined,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations`,
|
|
undefined,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
}
|
|
);
|
|
}
|
|
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations`,
|
|
undefined,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
}
|
|
);
|
|
}
|
|
|
|
getSelfServiceRegistrations<TData = CurrentRegistration | void>( options?: HttpClientBodyOptions): Observable<TData>;
|
|
getSelfServiceRegistrations<TData = CurrentRegistration | void>( options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
getSelfServiceRegistrations<TData = CurrentRegistration | void>( options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
getSelfServiceRegistrations<TData = CurrentRegistration | void>(
|
|
options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
if (options?.observe === 'events') {
|
|
return this.http.get<TData>(
|
|
`/self-service/registrations`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.get<TData>(
|
|
`/self-service/registrations`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
}
|
|
);
|
|
}
|
|
|
|
return this.http.get<TData>(
|
|
`/self-service/registrations`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
}
|
|
);
|
|
}
|
|
|
|
postSelfServiceRegistrationsIdWithdraw<TData = void>(id: string, options?: HttpClientBodyOptions): Observable<TData>;
|
|
postSelfServiceRegistrationsIdWithdraw<TData = void>(id: string, options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
postSelfServiceRegistrationsIdWithdraw<TData = void>(id: string, options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
postSelfServiceRegistrationsIdWithdraw<TData = void>(
|
|
id: string, options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
if (options?.observe === 'events') {
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations/${id}/withdraw`,
|
|
undefined,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations/${id}/withdraw`,
|
|
undefined,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
}
|
|
);
|
|
}
|
|
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations/${id}/withdraw`,
|
|
undefined,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
}
|
|
);
|
|
}
|
|
|
|
postSelfServiceRegistrationsIdDocuments<TData = void>(id: string,
|
|
provideDocumentsRequest: ProvideDocumentsRequest, options?: HttpClientBodyOptions): Observable<TData>;
|
|
postSelfServiceRegistrationsIdDocuments<TData = void>(id: string,
|
|
provideDocumentsRequest: ProvideDocumentsRequest, options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
postSelfServiceRegistrationsIdDocuments<TData = void>(id: string,
|
|
provideDocumentsRequest: ProvideDocumentsRequest, options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
postSelfServiceRegistrationsIdDocuments<TData = void>(
|
|
id: string,
|
|
provideDocumentsRequest: ProvideDocumentsRequest, options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
if (options?.observe === 'events') {
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations/${id}/documents`,
|
|
provideDocumentsRequest,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations/${id}/documents`,
|
|
provideDocumentsRequest,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
}
|
|
);
|
|
}
|
|
|
|
return this.http.post<TData>(
|
|
`/self-service/registrations/${id}/documents`,
|
|
provideDocumentsRequest,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
}
|
|
);
|
|
}
|
|
|
|
getOpenbaarRegister<TData = OpenbaarEntry[]>(params?: GetOpenbaarRegisterParams, options?: HttpClientBodyOptions): Observable<TData>;
|
|
getOpenbaarRegister<TData = OpenbaarEntry[]>(params?: GetOpenbaarRegisterParams, options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
getOpenbaarRegister<TData = OpenbaarEntry[]>(params?: GetOpenbaarRegisterParams, options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
getOpenbaarRegister<TData = OpenbaarEntry[]>(
|
|
params?: GetOpenbaarRegisterParams, options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
const filteredParams = filterParams({...params, ...options?.params}, new Set<string>([]));
|
|
|
|
if (options?.observe === 'events') {
|
|
return this.http.get<TData>(
|
|
`/openbaar/register`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
params: filteredParams,}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.get<TData>(
|
|
`/openbaar/register`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
params: filteredParams,}
|
|
);
|
|
}
|
|
|
|
return this.http.get<TData>(
|
|
`/openbaar/register`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
params: filteredParams,}
|
|
);
|
|
}
|
|
|
|
getBehandelWerkbak<TData = WerkbakItem[]>( options?: HttpClientBodyOptions): Observable<TData>;
|
|
getBehandelWerkbak<TData = WerkbakItem[]>( options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
getBehandelWerkbak<TData = WerkbakItem[]>( options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
getBehandelWerkbak<TData = WerkbakItem[]>(
|
|
options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
if (options?.observe === 'events') {
|
|
return this.http.get<TData>(
|
|
`/behandel/werkbak`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.get<TData>(
|
|
`/behandel/werkbak`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
}
|
|
);
|
|
}
|
|
|
|
return this.http.get<TData>(
|
|
`/behandel/werkbak`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
}
|
|
);
|
|
}
|
|
|
|
postBehandelRegistrationsIdDecide<TData = void>(id: string,
|
|
decideRequest: DecideRequest, options?: HttpClientBodyOptions): Observable<TData>;
|
|
postBehandelRegistrationsIdDecide<TData = void>(id: string,
|
|
decideRequest: DecideRequest, options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
postBehandelRegistrationsIdDecide<TData = void>(id: string,
|
|
decideRequest: DecideRequest, options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
postBehandelRegistrationsIdDecide<TData = void>(
|
|
id: string,
|
|
decideRequest: DecideRequest, options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
if (options?.observe === 'events') {
|
|
return this.http.post<TData>(
|
|
`/behandel/registrations/${id}/decide`,
|
|
decideRequest,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.post<TData>(
|
|
`/behandel/registrations/${id}/decide`,
|
|
decideRequest,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
}
|
|
);
|
|
}
|
|
|
|
return this.http.post<TData>(
|
|
`/behandel/registrations/${id}/decide`,
|
|
decideRequest,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
}
|
|
);
|
|
}
|
|
|
|
getBeheerCatalogiZaaktypen<TData = BeheerZaaktype[]>( options?: HttpClientBodyOptions): Observable<TData>;
|
|
getBeheerCatalogiZaaktypen<TData = BeheerZaaktype[]>( options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
getBeheerCatalogiZaaktypen<TData = BeheerZaaktype[]>( options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
getBeheerCatalogiZaaktypen<TData = BeheerZaaktype[]>(
|
|
options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
if (options?.observe === 'events') {
|
|
return this.http.get<TData>(
|
|
`/beheer/catalogi/zaaktypen`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'events',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options?.observe === 'response') {
|
|
return this.http.get<TData>(
|
|
`/beheer/catalogi/zaaktypen`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'response',
|
|
}
|
|
);
|
|
}
|
|
|
|
return this.http.get<TData>(
|
|
`/beheer/catalogi/zaaktypen`,{
|
|
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
observe: 'body',
|
|
}
|
|
);
|
|
}
|
|
|
|
};
|