@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/cds-plugin.js +4 -14
- package/lib/auth/mtls-endpoint-service.js +1 -1
- package/lib/build.js +6 -10
- package/lib/common/register-compile-targets.js +31 -0
- package/lib/configuration.js +161 -0
- package/lib/constants.js +11 -10
- package/lib/defaults.js +73 -42
- package/lib/{extendOrdWithCustom.js → extend-ord-with-custom.js} +2 -8
- package/lib/index.js +1 -1
- package/lib/{integrationDependency.js → integration-dependency.js} +0 -2
- package/lib/{interopCsn.js → interop-csn.js} +4 -4
- package/lib/meta-data.js +90 -0
- package/lib/ord.js +60 -290
- package/lib/services/mtx-ord-provider-service.cds +21 -0
- package/lib/services/mtx-ord-provider-service.js +43 -0
- package/lib/services/ord-service.js +115 -0
- package/lib/templates.js +35 -44
- package/lib/threads/compile.js +4 -1
- package/package.json +12 -6
- package/lib/metaData.js +0 -108
- package/lib/ord-service.js +0 -54
- package/lib/utils.js +0 -12
- /package/lib/{ord-service.cds → services/ord-service.cds} +0 -0
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
|
|
274
59
|
return productsObj;
|
|
275
60
|
};
|
|
276
61
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
108
|
+
const _getEntityTypes = (appConfig, packageIds) => {
|
|
109
|
+
return appConfig.entityTypeTargets.flatMap((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
|
|
110
|
+
};
|
|
327
111
|
|
|
328
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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
|
-
|
|
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 };
|