@cap-js/ord 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ord.js CHANGED
@@ -1,218 +1,22 @@
1
+ const _ = require("lodash");
2
+ const cds = require("@sap/cds");
3
+
4
+ const Logger = require("./logger");
5
+ const defaults = require("./defaults");
6
+ const { createAuthConfig } = require("./auth/authentication");
7
+ const Configuration = require("./configuration");
8
+ const { getIntegrationDependencies } = require("./integration-dependency");
1
9
  const {
2
- BLOCKED_SERVICE_NAME,
3
- CDS_ELEMENT_KIND,
4
- CONTENT_MERGE_KEY,
5
- ENTITY_RELATIONSHIP_ANNOTATION,
6
- ORD_ODM_ENTITY_NAME_ANNOTATION,
7
- } = require("./constants");
10
+ getCustomORDContent,
11
+ compareAndHandleCustomORDContentWithExistingContent,
12
+ } = require("./extend-ord-with-custom");
8
13
  const {
9
14
  createAPIResourceTemplate,
10
15
  createEntityTypeTemplate,
11
- createEntityTypeMappingsItemTemplate,
12
16
  createEventResourceTemplate,
13
17
  createGroupsTemplateForService,
14
18
  _propagateORDVisibility,
15
19
  } = require("./templates");
16
- const { getIntegrationDependencies } = require("./integrationDependency");
17
- const { extendCustomORDContentIfExists } = require("./extendOrdWithCustom");
18
- const { getRFC3339Date } = require("./date");
19
- const { createAuthConfig } = require("./auth/authentication");
20
-
21
- const Logger = require("./logger");
22
- const _ = require("lodash");
23
- const cds = require("@sap/cds");
24
- const defaults = require("./defaults");
25
- const path = require("path");
26
-
27
- const initializeAppConfig = (csn) => {
28
- const packageJson = _loadPackageJson();
29
- const packageName = packageJson.name;
30
- const appName = _formatAppName(packageName);
31
- const lastUpdate = getRFC3339Date();
32
-
33
- const ordNamespace = _getORDNamespace(packageName);
34
- const eventApplicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
35
-
36
- _validateNamespaces(ordNamespace, eventApplicationNamespace);
37
-
38
- const { serviceNames, apiResourceNames, apiEndpoints, eventServiceNames, entityTypeTargets } =
39
- _triageCsnDefinitions(csn);
40
-
41
- return {
42
- env: cds.env["ord"],
43
- lastUpdate,
44
- appName,
45
- apiEndpoints: Array.from(apiEndpoints),
46
- eventServiceNames,
47
- serviceNames,
48
- apiResourceNames,
49
- entityTypeTargets: _.uniqBy(entityTypeTargets, CONTENT_MERGE_KEY),
50
- ordNamespace,
51
- eventApplicationNamespace,
52
- packageName,
53
- };
54
- };
55
-
56
- function _loadPackageJson() {
57
- const packageJsonPath = path.join(cds.root, "package.json");
58
- if (!cds.utils.exists(packageJsonPath)) {
59
- throw new Error(`package.json not found in the project root directory`);
60
- }
61
- return require(packageJsonPath);
62
- }
63
-
64
- function _formatAppName(packageName) {
65
- return packageName.replace(/^[@]/, "").replace(/[@/]/g, "-");
66
- }
67
-
68
- function _getORDNamespace(packageName) {
69
- const vendorNamespace = "customer";
70
- return cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
71
- }
72
-
73
- function _validateNamespaces(ordNamespace, eventApplicationNamespace) {
74
- if (eventApplicationNamespace && ordNamespace !== eventApplicationNamespace) {
75
- Logger.warn("ORD and AsyncAPI namespaces should be the same.");
76
- }
77
- }
78
-
79
- function _triageCsnDefinitions(csn) {
80
- const pendingApiResourceNames = [];
81
- const apiEndpoints = new Set();
82
- const pendingEventServiceNames = new Set();
83
- const entityTypeTargets = [];
84
- const serviceNames = Object.keys(csn.definitions).filter((key) => _isValidService(key, csn.definitions[key]));
85
-
86
- for (const definitionKey of Object.keys(csn.definitions)) {
87
- const definitionObj = csn.definitions[definitionKey];
88
- if (
89
- definitionKey.includes(BLOCKED_SERVICE_NAME.MTXServices) ||
90
- definitionKey.includes(BLOCKED_SERVICE_NAME.OpenResourceDiscoveryService)
91
- ) {
92
- Logger.warn("ORD service name", definitionKey, "is blocked.");
93
- continue;
94
- }
95
- switch (definitionObj.kind) {
96
- case CDS_ELEMENT_KIND.service: {
97
- const apiResourceName = _handleApiResource(definitionKey, definitionObj);
98
- if (apiResourceName) pendingApiResourceNames.push(apiResourceName);
99
- break;
100
- }
101
- case CDS_ELEMENT_KIND.entity: {
102
- const result = _handleEntity(definitionKey, definitionObj);
103
- if (result) {
104
- if (result.apiEndpoint) apiEndpoints.add(result.apiEndpoint);
105
- if (result.entityTypeTarget) entityTypeTargets.push(...result.entityTypeTarget);
106
- }
107
- break;
108
- }
109
- case CDS_ELEMENT_KIND.event: {
110
- const event = _handleEvent(serviceNames, definitionKey, definitionObj);
111
- if (event) pendingEventServiceNames.add(event);
112
- break;
113
- }
114
- case CDS_ELEMENT_KIND.action:
115
- case CDS_ELEMENT_KIND.function: {
116
- const apiEndpoint = _handleActionOrFunction(definitionKey, definitionObj);
117
- if (apiEndpoint) apiEndpoints.add(apiEndpoint);
118
- break;
119
- }
120
- }
121
- }
122
-
123
- return {
124
- serviceNames,
125
- apiResourceNames: pendingApiResourceNames,
126
- apiEndpoints: Array.from(apiEndpoints),
127
- eventServiceNames: [...pendingEventServiceNames],
128
- entityTypeTargets,
129
- };
130
- }
131
-
132
- function _handleApiResource(apiResourceName, serviceDefinition) {
133
- if (
134
- _shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) ||
135
- !_isValidService(apiResourceName, serviceDefinition)
136
- ) {
137
- return null;
138
- }
139
- return apiResourceName;
140
- }
141
-
142
- function _shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) {
143
- const isActionsNotContained = !serviceDefinition.actions || Object.keys(serviceDefinition.actions).length === 0;
144
- const isFunctionsNotContained =
145
- !serviceDefinition.functions || Object.keys(serviceDefinition.functions).length === 0;
146
- const isEntitiesNotContained = !serviceDefinition.entities || Object.keys(serviceDefinition.entities).length === 0;
147
- const isEventsContained = serviceDefinition.events && Object.keys(serviceDefinition.events).length > 0;
148
- if (isActionsNotContained && isFunctionsNotContained && isEntitiesNotContained && isEventsContained) {
149
- return true;
150
- }
151
- return false;
152
- }
153
-
154
- function _shouldNotSkipIfServiceProtocolIsNone(keyDefinition) {
155
- if (keyDefinition["_service"] && keyDefinition["_service"]["@protocol"] === "none") {
156
- return false;
157
- }
158
- return true;
159
- }
160
-
161
- function _isBlockedServiceName(key) {
162
- const blockedServices = [BLOCKED_SERVICE_NAME.MTXServices, BLOCKED_SERVICE_NAME.OpenResourceDiscoveryService];
163
- return blockedServices.some((blocked) => key.includes(blocked));
164
- }
165
-
166
- function _isValidService(key, definition) {
167
- const isExternalService = Object.keys(cds).includes("requires") ? Object.keys(cds.requires).includes(key) : false;
168
-
169
- return (
170
- definition.kind === CDS_ELEMENT_KIND.service &&
171
- !definition["@cds.external"] &&
172
- definition["@protocol"] !== "none" &&
173
- !isExternalService &&
174
- !_isBlockedServiceName(key)
175
- );
176
- }
177
-
178
- function _handleEntity(key, keyDefinition) {
179
- if (!key.includes(".texts") && _shouldNotSkipIfServiceProtocolIsNone(keyDefinition)) {
180
- const apiEndpoint = key;
181
- let entityTypeTarget = null;
182
- if (keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION] || keyDefinition[ENTITY_RELATIONSHIP_ANNOTATION]) {
183
- const mapping = createEntityTypeMappingsItemTemplate(keyDefinition);
184
- if (Array.isArray(mapping)) entityTypeTarget = mapping;
185
- else if (mapping) entityTypeTarget = [mapping];
186
- }
187
- return { apiEndpoint, entityTypeTarget };
188
- }
189
- return null;
190
- }
191
-
192
- function _handleEvent(serviceNames, key, keyDefinition) {
193
- if (_shouldNotSkipIfServiceProtocolIsNone(keyDefinition)) {
194
- for (const serviceName of serviceNames) {
195
- if (key.startsWith(serviceName + ".")) {
196
- return serviceName;
197
- }
198
- }
199
- }
200
- return null;
201
- }
202
-
203
- function _handleActionOrFunction(key, keyDefinition) {
204
- if (_shouldNotSkipIfServiceProtocolIsNone(keyDefinition)) {
205
- return key;
206
- }
207
- return null;
208
- }
209
-
210
- const _getPolicyLevels = (appConfig) =>
211
- appConfig.env?.policyLevels ||
212
- (appConfig.env?.policyLevel && [appConfig.env?.policyLevel]) ||
213
- defaults.policyLevels;
214
-
215
- const _getDescription = (appConfig) => appConfig.env?.description || defaults.description;
216
20
 
217
21
  const _getGroups = (csn, appConfig) => {
218
22
  return appConfig.serviceNames
@@ -220,18 +24,8 @@ const _getGroups = (csn, appConfig) => {
220
24
  .filter((resource) => !!resource);
221
25
  };
222
26
 
223
- const _getPackages = (appConfig) => {
224
- return defaults.packages(appConfig);
225
- };
226
-
227
- const _getEntityTypes = (appConfig, packageIds) => {
228
- if (!appConfig.entityTypeTargets?.length) return [];
229
-
230
- return appConfig.entityTypeTargets.flatMap((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
231
- };
232
-
233
27
  const _getAPIResources = (csn, appConfig, packageIds, accessStrategies) => {
234
- const apiResources = appConfig.apiResourceNames.flatMap((apiResourceName) =>
28
+ return appConfig.apiResourceNames.flatMap((apiResourceName) =>
235
29
  createAPIResourceTemplate(
236
30
  apiResourceName,
237
31
  csn.definitions[apiResourceName],
@@ -240,8 +34,6 @@ const _getAPIResources = (csn, appConfig, packageIds, accessStrategies) => {
240
34
  accessStrategies,
241
35
  ),
242
36
  );
243
-
244
- return apiResources;
245
37
  };
246
38
 
247
39
  const _getEventResources = (csn, appConfig, packageIds, accessStrategies) => {
@@ -250,16 +42,9 @@ const _getEventResources = (csn, appConfig, packageIds, accessStrategies) => {
250
42
  );
251
43
  };
252
44
 
253
- function _getOpenResourceDiscovery(appConfig) {
254
- return appConfig.env?.openResourceDiscovery || defaults.openResourceDiscovery;
255
- }
256
-
257
- function _getConsumptionBundles(appConfig) {
258
- return appConfig.env?.consumptionBundles || defaults.consumptionBundles(appConfig);
259
- }
260
-
261
45
  const _getProducts = (appConfig) => {
262
46
  const productsObj = defaults.products(appConfig.packageName);
47
+
263
48
  if (appConfig.env?.products) {
264
49
  const customProducts = appConfig.env.products[0];
265
50
  if (customProducts?.ordId?.toLowerCase().startsWith("sap")) {
@@ -270,40 +55,39 @@ const _getProducts = (appConfig) => {
270
55
  _.assign(productsObj[0], customProducts);
271
56
  }
272
57
  }
273
- appConfig.products = productsObj;
58
+
274
59
  return productsObj;
275
60
  };
276
61
 
277
- function createDefaultORDDocument(linkedCsn, appConfig) {
278
- appConfig.policyLevels = _getPolicyLevels(appConfig);
279
- let ordDocument = {
62
+ const _createDefaultORDDocument = (linkedCsn, appConfig, authConfig) => {
63
+ const products = _getProducts(appConfig);
64
+ const packages = defaults.packages(appConfig, products);
65
+ const packageIds = packages?.map((pkg) => pkg.ordId) || [];
66
+ const entityTypes = _getEntityTypes(appConfig, packageIds);
67
+ const integrationDependencies = getIntegrationDependencies(linkedCsn, appConfig, packageIds);
68
+ const apiResources = _getAPIResources(linkedCsn, appConfig, packageIds, authConfig.accessStrategies);
69
+ const eventResources = _getEventResources(linkedCsn, appConfig, packageIds, authConfig.accessStrategies);
70
+
71
+ return {
72
+ // Unconditionally added top-level properties
280
73
  $schema: "https://open-resource-discovery.github.io/specification/spec-v1/interfaces/Document.schema.json",
281
- openResourceDiscovery: _getOpenResourceDiscovery(appConfig),
282
74
  policyLevels: appConfig.policyLevels,
283
- description: _getDescription(appConfig),
284
- consumptionBundles: _getConsumptionBundles(appConfig),
75
+ packages: packages,
76
+ description: appConfig.env?.description || defaults.description,
77
+ openResourceDiscovery: appConfig.env?.openResourceDiscovery || defaults.openResourceDiscovery,
78
+ consumptionBundles: appConfig.env?.consumptionBundles || defaults.consumptionBundles(appConfig),
79
+
80
+ // Conditionally added top-level properties
81
+ ...(!entityTypes.length ? {} : { entityTypes: entityTypes }),
82
+ ...(!apiResources.length ? {} : { apiResources: apiResources }),
83
+ ...(!eventResources.length ? {} : { eventResources: eventResources }),
84
+ ...(appConfig.existingProductORDId ? {} : { products: [products[0]] }),
85
+ ...(!appConfig.serviceNames.length ? {} : { groups: _getGroups(linkedCsn, appConfig) }),
86
+ ...(!integrationDependencies.length ? {} : { integrationDependencies: integrationDependencies }),
285
87
  };
88
+ };
286
89
 
287
- if (appConfig.serviceNames.length) {
288
- ordDocument.groups = _getGroups(linkedCsn, appConfig);
289
- }
290
-
291
- if (appConfig.env?.existingProductORDId) {
292
- appConfig.existingProductORDId = appConfig.env.existingProductORDId;
293
- } else {
294
- ordDocument.products = [_getProducts(appConfig)[0]];
295
- }
296
-
297
- ordDocument.packages = _getPackages(appConfig);
298
-
299
- return ordDocument;
300
- }
301
-
302
- function extractPackageIds(ordDocument) {
303
- return ordDocument.packages?.map((pkg) => pkg.ordId) || [];
304
- }
305
-
306
- function _filterUnusedPackages(ordDocument) {
90
+ const _filterUnusedPackages = (ordDocument) => {
307
91
  if (!ordDocument.packages?.length) return [];
308
92
 
309
93
  const usedPackageIds = new Set();
@@ -319,47 +103,33 @@ function _filterUnusedPackages(ordDocument) {
319
103
  });
320
104
 
321
105
  return ordDocument.packages.filter((pkg) => usedPackageIds.has(pkg.ordId));
322
- }
106
+ };
323
107
 
324
- module.exports = (csn) => {
325
- const linkedCsn = _propagateORDVisibility(cds.linked(csn));
326
- const appConfig = initializeAppConfig(linkedCsn);
108
+ const _getEntityTypes = (appConfig, packageIds) => {
109
+ return appConfig.entityTypeTargets.flatMap((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
110
+ };
327
111
 
328
- // Create auth config and fail-closed on configuration errors
112
+ const _createAuthConfig = () => {
329
113
  const authConfig = createAuthConfig();
114
+
115
+ // Create auth config and fail-closed on configuration errors
330
116
  if (authConfig.error) {
331
117
  throw new Error(`Authentication configuration error: ${authConfig.error}`);
332
118
  }
333
- const accessStrategies = authConfig.accessStrategies;
334
-
335
- let ordDocument = createDefaultORDDocument(linkedCsn, appConfig);
336
- const packageIds = extractPackageIds(ordDocument);
337
- const entityTypes = _getEntityTypes(appConfig, packageIds);
338
119
 
339
- if (entityTypes.length) {
340
- ordDocument.entityTypes = entityTypes;
341
- }
342
-
343
- if (appConfig.apiResourceNames.length) {
344
- const apiResources = _getAPIResources(linkedCsn, appConfig, packageIds, accessStrategies);
345
- if (apiResources.length) {
346
- ordDocument.apiResources = apiResources;
347
- }
348
- }
349
- if (appConfig.eventServiceNames.length) {
350
- const eventResources = _getEventResources(linkedCsn, appConfig, packageIds, accessStrategies);
351
- if (eventResources.length) {
352
- ordDocument.eventResources = eventResources;
353
- }
354
- }
355
-
356
- const integrationDependencies = getIntegrationDependencies(linkedCsn, appConfig, packageIds);
357
- if (integrationDependencies.length) {
358
- ordDocument.integrationDependencies = integrationDependencies;
359
- }
360
-
361
- ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument);
362
- ordDocument.packages = _filterUnusedPackages(ordDocument);
120
+ return authConfig;
121
+ };
363
122
 
364
- return ordDocument;
123
+ module.exports = (csn, extensions = []) => {
124
+ const authConfig = _createAuthConfig();
125
+ const linkedCsn = _propagateORDVisibility(cds.linked(csn));
126
+ const appConfig = new Configuration(linkedCsn);
127
+ const ordDocument = [...(extensions || []), getCustomORDContent(appConfig)]
128
+ .filter((extension) => !!extension)
129
+ .reduce(
130
+ (document, extension) => compareAndHandleCustomORDContentWithExistingContent(document, extension),
131
+ _createDefaultORDDocument(linkedCsn, appConfig, authConfig),
132
+ );
133
+
134
+ return Object.assign(ordDocument, { packages: _filterUnusedPackages(ordDocument) });
365
135
  };
@@ -0,0 +1,21 @@
1
+ @protocol: 'rest'
2
+ @requires: [ 'internal-user' ]
3
+ @path: '/-/cds/ord-provider-service'
4
+ @impl:'@cap-js/ord/lib/services/mtx-ord-provider-service.js'
5
+ service cds.xt.ord.MtxOrdProviderService {
6
+
7
+ action getOrdDocument(
8
+ tenant : String,
9
+ @cds.validate: false
10
+ toggles : array of String,
11
+ for : String enum { nodejs; java; },
12
+ ) returns {};
13
+
14
+ action getOrdResourceDefinition(
15
+ tenant : String,
16
+ @cds.validate: false
17
+ toggles : array of String,
18
+ for : String enum { nodejs; java; },
19
+ resource : String,
20
+ ) returns LargeString;
21
+ }
@@ -0,0 +1,43 @@
1
+ const cds = require("@sap/cds/lib");
2
+ const { ord, getMetadata } = require("@cap-js/ord/lib");
3
+
4
+ const defaults = require("../defaults");
5
+ const { DOCUMENT_PERSPECTIVES } = require("../constants");
6
+
7
+ module.exports = class MtxOrdProviderService extends cds.ApplicationService {
8
+ init() {
9
+ this.on("getOrdDocument", async (req) => {
10
+ req._?.res?.set("Content-Type", "application/json");
11
+
12
+ return defaults.adjustForPerspective(
13
+ ord(
14
+ await (
15
+ await cds.connect.to("cds.xt.ModelProviderService")
16
+ ).getCsn({
17
+ tenant: req.data.tenant,
18
+ toggles: req.data.toggles,
19
+ }),
20
+ ),
21
+ DOCUMENT_PERSPECTIVES.SystemInstance,
22
+ );
23
+ });
24
+
25
+ this.on("getOrdResourceDefinition", async (req) => {
26
+ const { response, contentType } = await getMetadata(
27
+ req.data.resource,
28
+ await (
29
+ await cds.connect.to("cds.xt.ModelProviderService")
30
+ ).getCsn({
31
+ tenant: req.data.tenant,
32
+ toggles: req.data.toggles,
33
+ }),
34
+ );
35
+
36
+ req._?.res?.set("Content-Type", contentType);
37
+
38
+ return response;
39
+ });
40
+
41
+ return super.init();
42
+ }
43
+ };
@@ -0,0 +1,115 @@
1
+ const cds = require("@sap/cds");
2
+
3
+ const ord = require("../ord.js");
4
+ const Logger = require("../logger.js");
5
+ const defaults = require("../defaults.js");
6
+ const { LOCAL_TENANT_ID_HEADER_KEY, DOCUMENT_PERSPECTIVES } = require("../constants");
7
+ const compileMetadata = require("../meta-data.js");
8
+ const { createAuthConfig, createAuthMiddleware } = require("../auth/authentication.js");
9
+
10
+ const validationMiddleware = (req, res, next) => {
11
+ const toggles = cds.env.requires.toggles;
12
+ const perspective = req.query.perspective;
13
+ const extensibility = cds.env.requires.extensibility;
14
+ const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
15
+
16
+ if (perspective && ![DOCUMENT_PERSPECTIVES.SystemVersion, DOCUMENT_PERSPECTIVES.SystemInstance].includes(perspective)) {
17
+ return res.status(400).send(`Required query parameter 'perspective' is invalid`);
18
+ }
19
+
20
+ if (perspective === DOCUMENT_PERSPECTIVES.SystemInstance && !(toggles || extensibility)) {
21
+ return res.status(400).send(`Unsupported query parameter 'perspective=${perspective}'`);
22
+ }
23
+
24
+ if (!tenant && perspective === DOCUMENT_PERSPECTIVES.SystemInstance) {
25
+ return res.status(400).send(`Missing required tenant context`);
26
+ }
27
+
28
+ return next();
29
+ };
30
+
31
+ const metadataResponseHandler = async (req, res) => {
32
+ try {
33
+ const perspective = req.query.perspective;
34
+ const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
35
+ const model = await resolveCdsModel(perspective, tenant);
36
+ const { contentType, response } = await compileMetadata(req.path, model);
37
+
38
+ return res.status(200).contentType(contentType).send(response);
39
+ } catch (error) {
40
+ Logger.error(error, "Error while processing the resource definition document");
41
+ return res.status(500).send(error.message);
42
+ }
43
+ };
44
+
45
+ const resolveCdsModel = async (perspective, tenant) => {
46
+ if (!tenant || perspective !== DOCUMENT_PERSPECTIVES.SystemInstance) {
47
+ Logger.info("Retrieving static CDS model...");
48
+ return cds.model;
49
+ }
50
+
51
+ Logger.info(`Retrieving dynamic CDS model for tenant ${tenant}...`);
52
+
53
+ return await (
54
+ await cds.connect.to("cds.xt.ModelProviderService")
55
+ ).getCsn({
56
+ tenant: tenant,
57
+ toggles: OpenResourceDiscoveryService.resolveFeatureToggles(tenant),
58
+ });
59
+ };
60
+
61
+ class OpenResourceDiscoveryService extends cds.ApplicationService {
62
+ async init() {
63
+ this.extensions = {};
64
+
65
+ cds.on("ord.extension.publish", ({ id, data }) => {
66
+ Logger.info(`Registering extension with id ${id}...`);
67
+
68
+ this.extensions[id] = data;
69
+ });
70
+
71
+ // Initialize authentication configuration from .cdsrc.json or environment variables
72
+ // CF mTLS validator is lazily initialized on first mTLS request
73
+ const authConfig = createAuthConfig();
74
+ if (authConfig.error) {
75
+ throw new Error(`Authentication initialization failed: ${authConfig.error}`);
76
+ }
77
+
78
+ // Create authentication middleware
79
+ const authMiddleware = createAuthMiddleware(authConfig);
80
+
81
+ // Default: /.well-known/open-resource-discovery
82
+ cds.app.get(`${this.path}`, (req, res) => {
83
+ const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
84
+
85
+ return res.status(200).send(defaults.baseTemplate(authConfig, tenant));
86
+ });
87
+
88
+ cds.app.get(`/ord/v1/documents/ord-document`, [authMiddleware, validationMiddleware], async (req, res) => {
89
+ const perspective = req.query.perspective || DOCUMENT_PERSPECTIVES.SystemVersion;
90
+ const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
91
+ const model = await resolveCdsModel(perspective, tenant);
92
+ const extensions = Array.from(Object.values(this.extensions));
93
+
94
+ return res.status(200).send(defaults.adjustForPerspective(ord(model, extensions), perspective));
95
+ });
96
+
97
+ cds.app.get(`/ord/v1/documents/:id`, [authMiddleware, validationMiddleware], async (_, res) => {
98
+ return res.status(404).send("404 Not Found");
99
+ });
100
+
101
+ // Use separate routes instead of optional parameters for path-to-regexp v8 compatibility
102
+ cds.app.get(`/ord/v1/:ordId/:service`, [authMiddleware, validationMiddleware], metadataResponseHandler);
103
+ cds.app.get(`/ord/v1/:ordId`, [authMiddleware, validationMiddleware], metadataResponseHandler);
104
+ cds.app.get(`/ord/v1`, [authMiddleware, validationMiddleware], metadataResponseHandler);
105
+
106
+ return super.init();
107
+ }
108
+
109
+ // eslint-disable-next-line no-unused-vars
110
+ static resolveFeatureToggles(tenant) {
111
+ return ["*"];
112
+ }
113
+ }
114
+
115
+ module.exports = { OpenResourceDiscoveryService };