Restructures into apps/ssp + apps/behandelportal (two Angular projects) plus libs/shared + libs/beheer (cross-app libraries), replacing WP-61's separate sibling repo. That split had already produced real drift: a hand-vendored copy of the backend's OpenAPI doc, a shared/ui+layout tree forked and silently diverging (7 files), and beheer + the styles.scss token bridge duplicated byte-for-byte across both repos. - git mv the SSP's src/app/* into apps/ssp/; fold shared/, beheer/, environments/, the Storybook docs/*.mdx, and styles.scss into libs/shared + libs/beheer (all confirmed identical between the two repos before merging). auth stays deliberately duplicated per ADR-0002 (actor-specific, expected to diverge) - amended there. - One generated API client (libs/shared), no more vendored swagger.json. - .dependency-cruiser split into a base factory + one config per app, and Storybook into .storybook-ssp/.storybook-behandelportal - both forced by the @auth/* alias resolving to different directories per app. - SiteHeaderComponent/ShellComponent gained HEADER_NAV_ITEMS/ HEADER_ADMIN_LINKS/DEBUG_PANEL injection tokens so each app supplies its own nav/admin-links/dev-panel instead of one being hardcoded. - CLAUDE.md, ARCHITECTURE.md, dependencies.md, and ADR-0002 updated; WP-67 backlog entry documents the full decision trail. npm run ci green (lint, dep:check x2, 360 tests across ssp/ behandelportal/shared/beheer, both localized builds, backend tests, snippet + api-client drift); both dev servers, both Storybook instances, and docker compose verified working. The old sibling repo (/home/eho/repos/behandelportal) is left untouched, not deleted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
37 lines
1.8 KiB
TypeScript
37 lines
1.8 KiB
TypeScript
import { Role } from '@shared/domain/role';
|
|
|
|
/**
|
|
* Dev-only role stand-in (the reading MECHANISM; the `Role` type is domain). This
|
|
* POC has one faked self-service user and no real identities, so the two-person
|
|
* letter workflow (drafter vs approver) plus admin is driven by a `?role=` query
|
|
* param. The backend receives it as an `X-Role` header (see role.interceptor),
|
|
* resolves it into a `Principal` server-side, and is the sole authority on what that
|
|
* principal may do (PRD-0002 phase P1, `Authz.Can`) — the FE only renders the
|
|
* resulting decision flags, it no longer derives permission from this value itself.
|
|
*
|
|
* **Sticky within the tab (sessionStorage):** the interceptor reads this per request,
|
|
* but navigation drops the query param (login redirects to /dashboard, RouterLinks
|
|
* don't carry it), which would silently revert an admin to drafter mid-session and
|
|
* 403 the admin endpoints. So a `?role=` seen in the URL is remembered for the tab;
|
|
* later requests use the remembered value. Set `?role=drafter` (or a fresh tab) to
|
|
* reset. Dev-only — the interceptor itself is only wired under `isDevMode()`.
|
|
*/
|
|
const STORAGE_KEY = 'dev-role';
|
|
export const ROLES: readonly Role[] = ['drafter', 'approver', 'admin'];
|
|
const isRole = (v: string | null): v is Role => !!v && ROLES.includes(v as Role);
|
|
|
|
export function currentRole(): Role {
|
|
const fromUrl = new URLSearchParams(window.location.search).get('role');
|
|
if (isRole(fromUrl)) {
|
|
sessionStorage.setItem(STORAGE_KEY, fromUrl);
|
|
return fromUrl;
|
|
}
|
|
const stored = sessionStorage.getItem(STORAGE_KEY);
|
|
return isRole(stored) ? stored : 'drafter';
|
|
}
|
|
|
|
/** Dev switcher entry point: persist the chosen role for the tab (WP-33). */
|
|
export function setRole(r: Role): void {
|
|
sessionStorage.setItem(STORAGE_KEY, r);
|
|
}
|