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>
76 lines
3.0 KiB
JavaScript
76 lines
3.0 KiB
JavaScript
#!/usr/bin/env node
|
|
// Serve the localized production build so the language switcher actually works locally.
|
|
// `ng build --localize` emits dist/.../browser/{nl,en}/ (each with base href /nl/ or /en/).
|
|
// Plain `ng serve` (npm start) serves only nl at /, so switching 404s there — this static
|
|
// server serves both locale subdirs with per-locale SPA fallback (a miss under /<locale>/…
|
|
// serves that locale's index.html), so deep-link switches resolve. Demo only, not prod infra.
|
|
import { createServer, request as httpRequest } from 'node:http';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { join, extname, normalize } from 'node:path';
|
|
|
|
// WP-67: two apps now share this script — APP_DIST_ROOT picks which one (defaults to the SSP).
|
|
const ROOT = process.env.APP_DIST_ROOT ?? 'dist/ssp/browser';
|
|
const PORT = Number(process.env.PORT ?? 4300);
|
|
// Reverse-proxy /api → backend so the served app is the whole demo, not just static pages.
|
|
// Docker sets API_PROXY_TARGET=http://api:5000; locally it defaults to the dev backend.
|
|
const API_TARGET = new URL(process.env.API_PROXY_TARGET ?? 'http://localhost:5000');
|
|
const MIME = {
|
|
'.html': 'text/html',
|
|
'.js': 'text/javascript',
|
|
'.mjs': 'text/javascript',
|
|
'.css': 'text/css',
|
|
'.json': 'application/json',
|
|
'.svg': 'image/svg+xml',
|
|
'.ico': 'image/x-icon',
|
|
'.woff2': 'font/woff2',
|
|
'.png': 'image/png',
|
|
};
|
|
|
|
const send = (res, status, body, type) => {
|
|
res.writeHead(status, { 'content-type': type });
|
|
res.end(body);
|
|
};
|
|
|
|
createServer(async (req, res) => {
|
|
const rawUrl = req.url ?? '/';
|
|
// Proxy API calls to the backend (keep the raw url incl. query).
|
|
if (rawUrl.startsWith('/api')) {
|
|
const proxied = httpRequest(
|
|
{
|
|
hostname: API_TARGET.hostname,
|
|
port: API_TARGET.port,
|
|
path: rawUrl,
|
|
method: req.method,
|
|
headers: req.headers,
|
|
},
|
|
(pr) => {
|
|
res.writeHead(pr.statusCode ?? 502, pr.headers);
|
|
pr.pipe(res);
|
|
},
|
|
);
|
|
proxied.on('error', () => send(res, 502, 'Bad gateway (API)', 'text/plain'));
|
|
req.pipe(proxied);
|
|
return;
|
|
}
|
|
const url = decodeURIComponent(rawUrl.split('?')[0]);
|
|
const rel = normalize(url).replace(/^(\.\.[/\\])+/, ''); // no path traversal
|
|
// nl (source) is served at the ROOT (subPath ''); en lives under /en/.
|
|
const isEn = url === '/en' || url.startsWith('/en/');
|
|
const indexPath = isEn ? join(ROOT, 'en', 'index.html') : join(ROOT, 'index.html');
|
|
try {
|
|
// A real asset (nl at root, en under /en/) — otherwise fall through to the SPA index.
|
|
if (url === '/' || url === '/en' || url === '/en/') throw new Error('serve index');
|
|
const file = await readFile(join(ROOT, rel));
|
|
send(res, 200, file, MIME[extname(rel)] ?? 'application/octet-stream');
|
|
} catch {
|
|
// SPA fallback to the requested locale's index.html.
|
|
try {
|
|
send(res, 200, await readFile(indexPath), 'text/html');
|
|
} catch {
|
|
send(res, 404, 'Not found', 'text/plain');
|
|
}
|
|
}
|
|
}).listen(PORT, () => {
|
|
console.log(`Serving ${ROOT} at http://localhost:${PORT}/ (nl at /, en at /en/)`);
|
|
});
|