Files
atomic-design-poc/src/app/shared/infrastructure/me.adapter.ts
T
ehoandClaude Opus 4.8 0e77faf351 feat(stamdata): admin stamdata maintenance editor (beheer)
Realizes ADR-0004's "future low-code editor that commits a PR": an
admin-only stamdata maintenance editor built on the stamdata-as-code
foundation.

Backend: `professions` moves from a hardcoded C# dictionary to an embedded
`professions.json` data-file (typed as `ProfessionMapping`) with valid-time
(geldigVan/geldigTot, half-open). A generic, reflection-driven
StamdataCatalog/StamdataTable/StamdataFile describes every table so one
endpoint pair + one grid editor serve all of them; add a table in one line.
Two read-only, admin-gated endpoints (GET /stamdata, GET /stamdata/{table}
?peildatum=) — no runtime write path. Generic build gate
`Every_catalog_table_is_valid` (keys non-blank, no overlapping validity,
well-formed windows).

Frontend: new `beheer` context (route beheer/stamdata, capabilityGuard
'stamdata:edit'). A schema-driven grid editor edits rows locally; download()
emits {table}.json for the admin to commit as a reviewed PR (no mutation
command — the CI build + StamdataValidationTests stay the authority).

Full gate GREEN both sides; gen:api leaves no drift; new stamdata story
passes axe. See WP-29 + ADR-0004.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 13:43:51 +02:00

40 lines
1.5 KiB
TypeScript

import { Injectable, inject, resource } from '@angular/core';
import { Result, ok, err } from '@shared/kernel/fp';
import { Capability } from '@shared/domain/capability';
import { ApiClient } from '@shared/infrastructure/api-client';
const KNOWN: readonly Capability[] = [
'brief:approve',
'brief:reject',
'brief:send',
'orgtemplate:edit',
'stamdata:edit',
];
/**
* Infrastructure adapter for `GET /me` (PRD-0002 §6): the current principal's
* coarse, role-derived capabilities — nav/menu-level, not tied to any one screen's
* live status (contrast a screen's own decision DTO, e.g. `BriefViewDto.decisions`).
*/
@Injectable({ providedIn: 'root' })
export class MeAdapter {
private client = inject(ApiClient);
meResource() {
return resource({ loader: () => this.client.me() });
}
}
/**
* Trust-boundary parse. An unrecognized capability string is dropped rather than
* rejecting the whole response — deny-by-default already covers it (AccessStore.can
* returns false for anything not in the set), and it lets the backend grow the
* capability list without breaking an older FE build.
*/
export function parseMe(json: unknown): Result<string, Capability[]> {
if (typeof json !== 'object' || json === null) return err('me: not an object');
const dto = json as { capabilities?: unknown };
if (!Array.isArray(dto.capabilities)) return err('me: missing/invalid capabilities');
return ok(dto.capabilities.filter((c): c is Capability => KNOWN.includes(c as Capability)));
}