Angular + Go + PostgreSQL sample with strict per-tenant isolation. - Backend: Go API (basic auth, tenant-scoped repository layer, pgx pool) - Frontend: Angular 22 SPA (search, checkout, bookings, dashboard) - Database: schema, seed data and demo bookings migrations - Tests: 311 across Go (integration), Angular (unit) and Playwright (e2e), plus a generated evidence report with PDF export - Security: whitebox VAPT report under security/ Secrets (.env, tokens, DB credentials) are git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
3.1 KiB
JavaScript
103 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Serves the evidence report over HTTP.
|
|
*
|
|
* The report is a plain folder of files and opens perfectly well from disk, but
|
|
* a localhost URL is easier to share with a colleague on the same machine, and
|
|
* some browsers treat file:// pages more suspiciously than http://.
|
|
*
|
|
* node scripts/serve-report.mjs http://localhost:9400
|
|
* node scripts/serve-report.mjs --port 8123
|
|
* node scripts/serve-report.mjs --no-open
|
|
*/
|
|
|
|
import { createServer } from 'node:http';
|
|
import { createReadStream, existsSync, statSync } from 'node:fs';
|
|
import { spawn } from 'node:child_process';
|
|
import { dirname, extname, join, normalize, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const DIR = join(ROOT, 'e2e', 'evidence-report');
|
|
|
|
const args = process.argv.slice(2);
|
|
const portFlag = args.indexOf('--port');
|
|
const PORT = portFlag !== -1 ? Number(args[portFlag + 1]) : 9400;
|
|
const shouldOpen = !args.includes('--no-open');
|
|
|
|
if (!existsSync(join(DIR, 'index.html'))) {
|
|
console.error(`No report at ${DIR}\nRun the suite first: npm run test:e2e`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const TYPES = {
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.md': 'text/markdown; charset=utf-8',
|
|
'.json': 'application/json; charset=utf-8',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.pdf': 'application/pdf',
|
|
'.css': 'text/css; charset=utf-8',
|
|
'.js': 'text/javascript; charset=utf-8',
|
|
};
|
|
|
|
const server = createServer((req, res) => {
|
|
const requested = decodeURIComponent((req.url ?? '/').split('?')[0]);
|
|
const relative = requested === '/' ? 'index.html' : requested.replace(/^\/+/, '');
|
|
|
|
// normalize collapses any ".." before it can escape the report directory.
|
|
const target = join(DIR, normalize(relative));
|
|
if (!target.startsWith(DIR)) {
|
|
res.writeHead(403).end('Forbidden');
|
|
return;
|
|
}
|
|
|
|
if (!existsSync(target) || statSync(target).isDirectory()) {
|
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
res.end(`Not found: ${relative}\n\nThis server only holds the evidence report.`);
|
|
return;
|
|
}
|
|
|
|
res.writeHead(200, {
|
|
'Content-Type': TYPES[extname(target).toLowerCase()] ?? 'application/octet-stream',
|
|
'Cache-Control': 'no-store',
|
|
});
|
|
createReadStream(target).pipe(res);
|
|
});
|
|
|
|
server.on('error', (err) => {
|
|
if (err.code === 'EADDRINUSE') {
|
|
console.error(
|
|
`Port ${PORT} is already in use.\n` +
|
|
`Try another: npm run report:serve -- --port ${PORT + 1}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
throw err;
|
|
});
|
|
|
|
server.listen(PORT, () => {
|
|
const url = `http://localhost:${PORT}/`;
|
|
console.log(
|
|
[
|
|
'',
|
|
` Test report: ${url}`,
|
|
` PDF: ${url}TravelHub-test-report.pdf`,
|
|
` Markdown: ${url}report.md`,
|
|
'',
|
|
' Press Ctrl+C to stop.',
|
|
'',
|
|
].join('\n'),
|
|
);
|
|
|
|
if (shouldOpen) {
|
|
const [cmd, cmdArgs] =
|
|
process.platform === 'win32'
|
|
? ['cmd', ['/c', 'start', '', url]]
|
|
: process.platform === 'darwin'
|
|
? ['open', [url]]
|
|
: ['xdg-open', [url]];
|
|
spawn(cmd, cmdArgs, { detached: true, stdio: 'ignore' }).unref();
|
|
}
|
|
});
|