Skip to content

Commit 24a48df

Browse files
SirSimon04KoblerSschiwekM
authored
feat: multitenancy support (#39)
* add manual req * fixes * fix * fix * update to bearer * update to jwt * update for print function * update * update * fix * update to fetch * use res.jsn * fixes * fix * update * udpate * add tentant id t request * refactoring to use cache and utils * update token cache * fixes * fix * update * remove cloud sdk * fix * fix * update * update * changes * polished * comment * update lint config * Update srv/BTPPrintService.js Co-authored-by: Simon Kobler <32038731+KoblerS@users.noreply.github.com> * Update srv/BTPPrintService.js Co-authored-by: Simon Kobler <32038731+KoblerS@users.noreply.github.com> * Update srv/BTPPrintService.js Co-authored-by: Simon Kobler <32038731+KoblerS@users.noreply.github.com> * update error handling * update error handling * updates * update creds * try to use xssec * update vcap.tag * update cds.requires * update label * remove param * add cache back in * update deps * try * change name to PrintService * try * Revert "try" This reverts commit b0fd21d. * add zone id * remoce zid * add zoneid back in finally, wurde aber auch mal zeit * lint * change name to print again * only add zid when needed * make service internal * remove package update * Update srv/BTPPrintService.js Co-authored-by: Marten Schiwek <marten.schiwek@sap.com> * update error handling * update test package.json * Changelog * Update srv/localPrintService.js Co-authored-by: Simon Kobler <32038731+KoblerS@users.noreply.github.com> * prettier * update error handling match queue and file reading to print error handling behaviour --------- Co-authored-by: Simon Kobler <32038731+KoblerS@users.noreply.github.com> Co-authored-by: Marten Schiwek <marten.schiwek@sap.com>
1 parent 81c225f commit 24a48df

File tree

13 files changed

+262
-142
lines changed

13 files changed

+262
-142
lines changed

.eslintrc

Lines changed: 0 additions & 26 deletions
This file was deleted.

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@
4040
"runtimeArgs": ["run", "test"],
4141
"skipFiles": ["<node_internals>/**"],
4242
"console": "integratedTerminal"
43+
},
44+
{
45+
"type": "pwa-node",
46+
"request": "launch",
47+
"name": "watch sample hybrid",
48+
"cwd": "${workspaceFolder}/test/bookshop",
49+
"runtimeExecutable": "cds",
50+
"runtimeArgs": ["bind", "--exec", "--", "cds", "w"],
51+
"skipFiles": ["<node_internals>/**"],
52+
"console": "integratedTerminal"
4353
}
4454
]
4555
}

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ All notable changes to this project will be documented in this file.
44
This project adheres to [Semantic Versioning](http://semver.org/).
55
The format is based on [Keep a Changelog](http://keepachangelog.com/).
66

7-
## Version 0.1.0 - TBD
7+
## Version 0.2.0 - 2025-12-19
8+
9+
### Added
10+
11+
- Multitenancy support
12+
13+
### Fixed
14+
15+
- Draft enabled entities can be print enabled
16+
- Entities with composite keys can be print enabled
17+
18+
## Version 0.1.0 - 2025-11-28
819

920
### Added
1021

_i18n/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
PRINT_UNKNOWN_ERROR=An unknown error occurred while attempting to print.
2+
QUEUE_UNKNOWN_ERROR=An unknown error occurred while accessing the print queues.
3+
FILE_UNKNOWN_ERROR=An unknown error occurred while accessing the print files.
24
PRINT_JOB_SENT=Print job for file "{0}" has been sent to queue "{1}".
35
PRINT_PROVIDE_FILTER=Please provide a filter to retrieve files for the object.
46
PRINT_OBJECT_NOT_FOUND=Object '{0}' was not found in the model.

cds-plugin.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,17 @@ cds.once("served", async () => {
2323
srv.on("READ", entity, async (req) => {
2424
req.target = printer.entities.Queues;
2525
req.query.SELECT.from.ref[0] = "PrintService.Queues";
26-
return await printer.run(req.query);
26+
try {
27+
return await printer.run(req.query);
28+
} catch (error) {
29+
LOG.error(error);
30+
// Only return client errors to not show technical errors to the user
31+
if (error.code >= 400 && error.code < 500) {
32+
req.reject(error.code, error.message);
33+
} else {
34+
req.reject(500, "QUEUE_UNKNOWN_ERROR");
35+
}
36+
}
2737
}),
2838
);
2939
continue;
@@ -34,7 +44,17 @@ cds.once("served", async () => {
3444
srv.on("READ", entity, async (req) => {
3545
req.target = printer.entities.Files;
3646
req.query.SELECT.from.ref[0] = "PrintService.Files";
37-
return await printer.run(req.query);
47+
try {
48+
return await printer.run(req.query);
49+
} catch (error) {
50+
LOG.error(error);
51+
// Only return client errors to not show technical errors to the user
52+
if (error.code >= 400 && error.code < 500) {
53+
req.reject(error.code, error.message);
54+
} else {
55+
req.reject(500, "FILE_UNKNOWN_ERROR");
56+
}
57+
}
3858
}),
3959
);
4060
}
@@ -90,14 +110,19 @@ cds.once("served", async () => {
90110
],
91111
});
92112

93-
return req.info({
113+
return req.notify({
94114
status: 200,
95115
message: "PRINT_JOB_SENT",
96116
args: [object[fileNameAttribute], queueID],
97117
});
98118
} catch (error) {
99-
LOG.error("Print error:", error);
100-
return req.reject({ status: 500, message: "PRINT_UNKNOWN_ERROR" });
119+
LOG.error(error);
120+
// Only return client errors to not show technical errors to the user
121+
if (error.code >= 400 && error.code < 500) {
122+
req.reject(error.code, error.message);
123+
} else {
124+
req.reject(500, "PRINT_UNKNOWN_ERROR");
125+
}
101126
}
102127
});
103128
}

eslint.config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ export default [
44
...cds,
55
{
66
files: ["**/*.js"],
7+
languageOptions: {
8+
globals: {
9+
SELECT: "readonly",
10+
INSERT: "readonly",
11+
UPSERT: "readonly",
12+
UPDATE: "readonly",
13+
DELETE: "readonly",
14+
CREATE: "readonly",
15+
DROP: "readonly",
16+
CDL: "readonly",
17+
CQL: "readonly",
18+
CXL: "readonly",
19+
cds: "readonly",
20+
},
21+
},
722
rules: {
823
"no-await-in-loop": "error",
924
"no-console": ["error", { allow: ["warn", "error"] }],

lib/btp-utils.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const { XsuaaService } = require("@sap/xssec");
2+
3+
const getServiceCredentials = (name) => (cds?.env?.requires[name] || [])?.credentials;
4+
5+
async function getServiceToken(serviceName) {
6+
const srvCredentials = getServiceCredentials(serviceName);
7+
if (!srvCredentials) {
8+
throw new Error(`Missing binding credentials for service "${serviceName}"`);
9+
}
10+
11+
const tenantId = cds.context?.tenant;
12+
const xsuaaService = new XsuaaService(srvCredentials.uaa);
13+
const { access_token: jwt, expires_in } = await xsuaaService.fetchClientCredentialsToken({
14+
...(tenantId && { zid: tenantId }),
15+
});
16+
17+
if (!jwt) {
18+
throw new Error(
19+
`Empty JWT returned from authorization service for bound service "${serviceName}"`,
20+
);
21+
}
22+
23+
return { jwt, expires_in };
24+
}
25+
26+
module.exports = {
27+
getServiceToken,
28+
getServiceCredentials,
29+
};

lib/token-cache.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const cds = require("@sap/cds");
2+
const LOG = cds.log("print");
3+
4+
module.exports = class TokenCache {
5+
constructor() {
6+
this.cache = new Map();
7+
}
8+
9+
set(key, token, expiresIn) {
10+
const expiresAt = Date.now() + expiresIn * 1000;
11+
this.cache.set(key, { token, expiresAt });
12+
LOG.debug(`Token set for key: ${key}, expires in ${expiresIn} seconds.`);
13+
}
14+
15+
get(key) {
16+
const entry = this.cache.get(key);
17+
if (!entry) {
18+
LOG.debug(`No token found for key: ${key}.`);
19+
return undefined;
20+
}
21+
if (Date.now() >= entry.expiresAt) {
22+
this.cache.delete(key);
23+
LOG.debug(`Token expired for key: ${key}.`);
24+
return undefined;
25+
}
26+
LOG.debug(`Token retrieved for key: ${key}.`);
27+
return entry.token;
28+
}
29+
};

package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
"plugin"
2323
],
2424
"dependencies": {
25-
"@sap-cloud-sdk/connectivity": "^3.26.4",
26-
"@sap-cloud-sdk/http-client": "^4.1.2",
27-
"@sap/xssec": "^3.6.2"
25+
"@sap-cloud-sdk/connectivity": "^4.2.0",
26+
"@sap-cloud-sdk/http-client": "^4.2.0",
27+
"@sap-cloud-sdk/resilience": "^4.2.0"
2828
},
2929
"peerDependencies": {
3030
"@sap/cds": ">= 9"
@@ -38,8 +38,7 @@
3838
],
3939
"cds": {
4040
"requires": {
41-
"destinations": true,
42-
"PrintService": {
41+
"print": {
4342
"[development]": {
4443
"kind": "print-to-console"
4544
},
@@ -53,7 +52,10 @@
5352
"kind": "print-to-service"
5453
},
5554
"vcap": {
56-
"tag": "print"
55+
"label": "print"
56+
},
57+
"subscriptionDependency": {
58+
"uaa": "xsappname"
5759
},
5860
"outbox": true
5961
},

srv/BTPPrintService.cds

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@impl: './BTPPrintService.js'
22
@requires: 'authenticated-user'
3+
@protocol: 'none'
34
service PrintService {
45

56
@UI.HeaderInfo: {

0 commit comments

Comments
 (0)