forked from QuorumProof/QuorumProof
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
129 lines (121 loc) · 5.06 KB
/
Copy pathindex.ts
File metadata and controls
129 lines (121 loc) · 5.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* Issue #1309 — OpenAPI 3.1 spec generator.
*
* `buildOpenApiSpec()` assembles the full document from the typed path
* fragments in `./paths/*` and the shared `./components`. It is called
* fresh on every request to `/api-docs/openapi.json` (see
* `../routes/docs.ts`) rather than reading a static file, so the served
* spec can never go stale relative to what's in source control — and the
* same function is what `scripts/generate-openapi-spec.ts` calls to write
* the on-disk snapshot consumed by the TypeScript client generator.
*
* Only routers that are actually mounted in `index.ts` are represented
* here. A handful of route modules exist in the codebase but are not
* wired into the running app (only exercised directly by their unit
* tests) — documenting those would describe endpoints that 404 in
* practice, so they're intentionally excluded.
*/
import type { OpenAPIObject, PathItemObject } from 'openapi3-ts/oas31';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { components, tags } from './components.js';
import type { PathsFragment } from './paths/types.js';
import { healthPaths } from './paths/health.js';
import { verifyPaths } from './paths/verify.js';
import { slicesPaths } from './paths/slices.js';
import { apiKeysPaths } from './paths/apiKeys.js';
import { webhooksPaths } from './paths/webhooks.js';
import { gdprPaths } from './paths/gdpr.js';
import { oauth2Paths } from './paths/oauth2.js';
import { attestorsPaths } from './paths/attestors.js';
import { notificationsPaths } from './paths/notifications.js';
import { issuerPaths } from './paths/issuer.js';
import { analyticsPaths } from './paths/analytics.js';
import { issuerAnalyticsPaths } from './paths/issuerAnalytics.js';
import { dashboardPaths } from './paths/dashboard.js';
import { credentialsPaths } from './paths/credentials.js';
import { credentialExportPaths } from './paths/credentialExport.js';
import { consentPaths } from './paths/consent.js';
import { shareLinksPaths } from './paths/shareLinks.js';
import { recoveryPaths } from './paths/recovery.js';
import { privilegeEscalationPaths } from './paths/privilegeEscalation.js';
import { tracingPaths } from './paths/tracing.js';
import { metricsPaths } from './paths/metrics.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8')) as { version: string };
/** Re-mounts every operation in `fragment` under `newPrefix` instead of `oldPrefix`. */
function remount(fragment: PathsFragment, oldPrefix: string, newPrefix: string): PathsFragment {
const out: PathsFragment = {};
for (const [path, item] of Object.entries(fragment)) {
if (!path.startsWith(oldPrefix)) {
throw new Error(`remount: path "${path}" does not start with "${oldPrefix}"`);
}
out[newPrefix + path.slice(oldPrefix.length)] = item;
}
return out;
}
function mergePaths(...fragments: PathsFragment[]): Record<string, PathItemObject> {
const merged: Record<string, PathItemObject> = {};
for (const fragment of fragments) {
for (const [path, item] of Object.entries(fragment)) {
if (merged[path]) {
throw new Error(`buildOpenApiSpec: duplicate path definition for "${path}"`);
}
merged[path] = item;
}
}
return merged;
}
export function buildOpenApiSpec(): OpenAPIObject {
const paths = mergePaths(
healthPaths,
verifyPaths,
slicesPaths,
apiKeysPaths,
// apiKeysRouter is mounted twice in index.ts: /api/api-keys (legacy)
// and /auth/api-keys (issue #1297's spec-mandated path). Same handlers,
// same behaviour — re-derive the second path set instead of hand
// duplicating every operation object.
remount(apiKeysPaths, '/api/api-keys', '/auth/api-keys'),
webhooksPaths,
gdprPaths,
oauth2Paths,
attestorsPaths,
notificationsPaths,
issuerPaths,
analyticsPaths,
issuerAnalyticsPaths,
dashboardPaths,
credentialsPaths,
credentialExportPaths,
consentPaths,
shareLinksPaths,
recoveryPaths,
privilegeEscalationPaths,
tracingPaths,
metricsPaths,
);
const baseUrl = (process.env.PUBLIC_APP_BASE_URL ?? 'http://localhost:3000').replace(/\/+$/, '');
return {
openapi: '3.1.0',
info: {
title: 'QuorumProof API',
version: pkg.version,
description:
'REST API for the QuorumProof credential issuance, attestation and verification ' +
'platform, backed by Soroban smart contracts on Stellar.\n\n' +
'This document is generated from typed operation definitions in ' +
'`api-server/src/openapi/paths/*` — see `docs/API_DOCUMENTATION.md` for how to ' +
'regenerate it and the TypeScript client after changing a route.',
contact: { name: 'QuorumProof', url: 'https://github.com/QuorumProof/QuorumProof' },
},
servers: [
{ url: baseUrl, description: 'Configured server (PUBLIC_APP_BASE_URL)' },
{ url: 'http://localhost:3000', description: 'Local development' },
],
tags,
paths,
components,
};
}