@5minds/node-red-contrib-processcube 2.0.0-feature-629c78-m2dq1ygt → 7.6.0-develop-51b534-mjy3s4sm

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.
Files changed (68) hide show
  1. package/README.md +3 -58
  2. package/check-authorization.html +138 -0
  3. package/check-authorization.js +27 -0
  4. package/dataobject-instance-query.html +141 -0
  5. package/dataobject-instance-query.js +45 -0
  6. package/endevent-finished-listener.js +14 -25
  7. package/examples/Check-Authorization-Sample.json +109 -0
  8. package/examples/Dataobject-Instance-Query-Sample.json +109 -0
  9. package/externaltask-error.html +8 -3
  10. package/externaltask-error.js +43 -29
  11. package/externaltask-event-listener.js +11 -28
  12. package/externaltask-input.html +48 -3
  13. package/externaltask-input.js +581 -114
  14. package/externaltask-output.js +20 -16
  15. package/icons/data-object-query.svg +5 -0
  16. package/message-event-trigger.html +1 -1
  17. package/message-event-trigger.js +8 -7
  18. package/package.json +74 -67
  19. package/process-event-listener.js +166 -225
  20. package/process-start.html +6 -0
  21. package/process-start.js +29 -6
  22. package/process-terminate.html +1 -1
  23. package/process-terminate.js +7 -5
  24. package/processcube-engine-config.html +25 -7
  25. package/processcube-engine-config.js +25 -135
  26. package/processcube-google-docs-mail-template.html +150 -0
  27. package/processcube-google-docs-mail-template.js +158 -0
  28. package/processdefinition-deploy.html +44 -0
  29. package/processdefinition-deploy.js +28 -0
  30. package/processdefinition-query.html +18 -13
  31. package/processdefinition-query.js +33 -31
  32. package/processinstance-delete-advanced.html +82 -0
  33. package/processinstance-delete-advanced.js +33 -0
  34. package/processinstance-delete.html +60 -8
  35. package/processinstance-delete.js +84 -30
  36. package/processinstance-query.html +116 -109
  37. package/processinstance-query.js +28 -5
  38. package/signal-event-trigger.js +8 -6
  39. package/usertask-event-listener.html +123 -1
  40. package/usertask-event-listener.js +30 -45
  41. package/usertask-input.html +119 -0
  42. package/usertask-input.js +7 -9
  43. package/usertask-output.js +15 -8
  44. package/wait-for-usertask.html +122 -6
  45. package/wait-for-usertask.js +44 -47
  46. package/.github/workflows/build-and-publish.yml +0 -72
  47. package/.processcube/authority/config/config.json +0 -36
  48. package/.processcube/authority/config/upeSeedingData.json +0 -12
  49. package/Dockerfile +0 -9
  50. package/doc_generator/_process_instances_query.md +0 -115
  51. package/doc_generator/generator.js +0 -41
  52. package/doc_generator/generator_with_swagger.js +0 -72
  53. package/doc_generator/package-lock.json +0 -176
  54. package/doc_generator/package.json +0 -15
  55. package/doc_generator/query_template.mustache +0 -20
  56. package/doc_generator/swagger.json +0 -4110
  57. package/docker-compose.yml +0 -44
  58. package/nodered/flows.json +0 -2156
  59. package/nodered/flows_cred.json +0 -3
  60. package/nodered/settings.js +0 -562
  61. package/nodered/static/ProcessCube_Logo.svg +0 -53
  62. package/processes/Call-Activity-Sample.bpmn +0 -88
  63. package/processes/External-Task-Auth-Sample.bpmn +0 -82
  64. package/processes/External-Task-Sample.bpmn +0 -94
  65. package/processes/SampleEvent.bpmn +0 -73
  66. package/processes/User-Task-Auth-Sample.bpmn +0 -63
  67. package/processes/User-Task-Sample.bpmn +0 -76
  68. package/processes/Wait-For-Usertask.bpmn +0 -74
@@ -4,11 +4,13 @@
4
4
  defaults: {
5
5
  name: { value: '' },
6
6
  url: { value: 'http://engine:8000', required: true },
7
- urlType: { type: 'str'},
7
+ urlType: { type: 'str' },
8
8
  clientId: { value: '' },
9
9
  clientIdType: { type: 'str' },
10
10
  clientSecret: { value: '' },
11
11
  clientSecretType: { type: 'str' },
12
+ scope: { value: 'engine_etw engine_read engine_write engine_observer' },
13
+ scopeType: { type: 'str' },
12
14
  },
13
15
  label: function () {
14
16
  return this.name || this.url;
@@ -16,17 +18,22 @@
16
18
  oneditprepare: function () {
17
19
  $('#node-config-input-url').typedInput({
18
20
  default: 'str',
19
- types: ['str', 'global', 'flow', 'env', 'msg', 'cred'],
21
+ types: ['str', 'env', 'cred'],
20
22
  });
21
23
 
22
24
  $('#node-config-input-clientId').typedInput({
23
25
  default: 'str',
24
- types: ['str', 'global', 'flow', 'env', 'msg', 'cred'],
26
+ types: ['str', 'env', 'cred'],
25
27
  });
26
28
 
27
29
  $('#node-config-input-clientSecret').typedInput({
28
30
  default: 'str',
29
- types: ['str', 'global', 'flow', 'env', 'msg', 'cred'],
31
+ types: ['str', 'env', 'cred'],
32
+ });
33
+
34
+ $('#node-config-input-scope').typedInput({
35
+ default: 'str',
36
+ types: ['str', 'env', 'cred'],
30
37
  });
31
38
 
32
39
  $('#node-config-input-url').typedInput('value', this.url);
@@ -37,6 +44,9 @@
37
44
 
38
45
  $('#node-config-input-clientSecret').typedInput('value', this.clientSecret);
39
46
  $('#node-config-input-clientSecret').typedInput('type', this.clientSecretType);
47
+
48
+ $('#node-config-input-scope').typedInput('value', this.scope);
49
+ $('#node-config-input-scope').typedInput('type', this.scopeType);
40
50
  },
41
51
  oneditsave: function () {
42
52
  this.url = $('#node-config-input-url').typedInput('value');
@@ -47,6 +57,9 @@
47
57
 
48
58
  this.clientSecret = $('#node-config-input-clientSecret').typedInput('value');
49
59
  this.clientSecretType = $('#node-config-input-clientSecret').typedInput('type');
60
+
61
+ this.scope = $('#node-config-input-scope').typedInput('value');
62
+ this.scopeType = $('#node-config-input-scope').typedInput('type');
50
63
  },
51
64
  });
52
65
  </script>
@@ -66,7 +79,11 @@
66
79
  </div>
67
80
  <div class="form-row">
68
81
  <label for="node-config-input-clientSecret"><i class="fa fa-bookmark"></i> Client secret</label>
69
- <input type="password" id="node-config-input-clientSecret" />
82
+ <input type="text" id="node-config-input-clientSecret" />
83
+ </div>
84
+ <div class="form-row">
85
+ <label for="node-config-input-scope"><i class="fa fa-bookmark"></i> Scope</label>
86
+ <input type="text" id="node-config-input-scope" />
70
87
  </div>
71
88
  </script>
72
89
 
@@ -78,9 +95,10 @@ The configuration for the ProcessCube engine.
78
95
  : url (String) : The URL of the ProcessCube engine.
79
96
  : clientId (String) : The client id for the ProcessCube engine.
80
97
  : clientSecret (String) : The client secret for the ProcessCube engine.
98
+ : scope (String|Secret|ENV) : The scope for the ProcessCube engine.
81
99
 
82
100
  ### References
83
101
 
84
- - [The ProcessCube&copy; Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; platform
85
- - [ProcessCube&copy; LowCode Integration](https://processcube.io/docs/node-red) - LowCode integration in ProcessCube&copy;
102
+ - [The ProcessCube&copy; Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; platform
103
+ - [ProcessCube&copy; LowCode Integration](https://processcube.io/docs/node-red) - LowCode integration in ProcessCube&copy;
86
104
  </script>
@@ -1,72 +1,44 @@
1
1
  const engine_client = require('@5minds/processcube_engine_client');
2
- const jwt = require('jwt-decode');
3
- const oidc = require('openid-client');
4
-
5
- const DELAY_FACTOR = 0.85;
6
2
 
7
3
  module.exports = function (RED) {
8
4
  function ProcessCubeEngineNode(n) {
9
5
  RED.nodes.createNode(this, n);
10
6
  const node = this;
11
- const identityChangedCallbacks = [];
12
- this.url = RED.util.evaluateNodeProperty(n.url, n.urlType, node);
13
- this.identity = null;
14
7
 
15
- this.credentials.clientId = RED.util.evaluateNodeProperty(n.clientId, n.clientIdType, node);
16
- this.credentials.clientSecret = RED.util.evaluateNodeProperty(n.clientSecret, n.clientSecretType, node);
8
+ node.url = RED.util.evaluateNodeProperty(n.url, n.urlType, node);
9
+ node.credentials.clientId = RED.util.evaluateNodeProperty(n.clientId, n.clientIdType, node);
10
+ node.credentials.clientSecret = RED.util.evaluateNodeProperty(n.clientSecret, n.clientSecretType, node);
11
+ node.credentials.scope = RED.util.evaluateNodeProperty(n.scope, n.scopeType, node);
17
12
 
18
- this.registerOnIdentityChanged = function (callback) {
19
- identityChangedCallbacks.push(callback);
20
- };
13
+ if (!node.credentials.scope) {
14
+ node.credentials.scope = 'engine_etw engine_read engine_write engine_observer';
15
+ }
21
16
 
22
- this.isIdentityReady = function () {
23
- if (this.credentials.clientId && this.credentials.clientSecret) {
24
- return this.identity != null;
17
+ try {
18
+ if (node.credentials.clientId && node.credentials.clientSecret) {
19
+ node.log('Create Client with secrets');
20
+ node.engineClient = new engine_client.EngineClient(node.url, {
21
+ clientId: node.credentials.clientId,
22
+ clientSecret: node.credentials.clientSecret,
23
+ scope: node.credentials.scope,
24
+ });
25
25
  } else {
26
- return true;
27
- }
28
- };
29
-
30
- this.setIdentity = (identity) => {
31
- node.log(`setIdentity: ${JSON.stringify(identity)}`);
32
- this.identity = identity;
33
-
34
- for (const callback of identityChangedCallbacks) {
35
- callback(identity);
26
+ node.log('Create Client without secrets');
27
+ node.engineClient = new engine_client.EngineClient(node.url);
36
28
  }
37
- };
29
+ } catch (error) {
30
+ node.error(error, {});
31
+ }
38
32
 
39
33
  node.on('close', async () => {
40
- if (this.engineClient) {
41
- this.engineClient.dispose();
42
- this.engineClient = null;
34
+ node.log('close');
35
+ if (node.engineClient) {
36
+ node.engineClient.dispose();
37
+ node.engineClient = null;
43
38
  }
44
39
  });
45
-
46
- if (this.credentials.clientId && this.credentials.clientSecret) {
47
- this.engineClient = new engine_client.EngineClient(this.url);
48
-
49
- this.engineClient.applicationInfo
50
- .getAuthorityAddress()
51
- .then((authorityUrl) => {
52
- startRefreshingIdentityCycle(
53
- this.credentials.clientId,
54
- this.credentials.clientSecret,
55
- authorityUrl,
56
- node
57
- ).catch((reason) => {
58
- console.error(reason);
59
- node.error(reason);
60
- });
61
- })
62
- .catch((reason) => {
63
- console.error(reason);
64
- node.error(reason);
65
- });
66
- } else {
67
- this.engineClient = new engine_client.EngineClient(this.url);
68
- }
69
40
  }
41
+
70
42
  RED.nodes.registerType('processcube-engine-config', ProcessCubeEngineNode, {
71
43
  credentials: {
72
44
  clientId: { type: 'text' },
@@ -74,85 +46,3 @@ module.exports = function (RED) {
74
46
  },
75
47
  });
76
48
  };
77
-
78
- async function getFreshTokenSet(clientId, clientSecret, authorityUrl) {
79
- const issuer = await oidc.Issuer.discover(authorityUrl);
80
-
81
- const client = new issuer.Client({
82
- client_id: clientId,
83
- client_secret: clientSecret,
84
- });
85
-
86
- const tokenSet = await client.grant({
87
- grant_type: 'client_credentials',
88
- scope: 'engine_etw engine_read engine_write',
89
- });
90
-
91
- return tokenSet;
92
- }
93
-
94
- function getIdentityForExternalTaskWorkers(tokenSet) {
95
- const accessToken = tokenSet.access_token;
96
- const decodedToken = jwt.jwtDecode(accessToken);
97
-
98
- return {
99
- token: tokenSet.access_token,
100
- userId: decodedToken.sub,
101
- };
102
- }
103
-
104
- async function getExpiresInForExternalTaskWorkers(tokenSet) {
105
- let expiresIn = tokenSet.expires_in;
106
-
107
- if (!expiresIn && tokenSet.expires_at) {
108
- expiresIn = Math.floor(tokenSet.expires_at - Date.now() / 1000);
109
- }
110
-
111
- if (expiresIn === undefined) {
112
- throw new Error('Could not determine the time until the access token for external task workers expires');
113
- }
114
-
115
- return expiresIn;
116
- }
117
-
118
- /**
119
- * Start refreshing the identity in regular intervals.
120
- * @param {TokenSet | null} tokenSet The token set to refresh the identity for
121
- * @returns {Promise<void>} A promise that resolves when the timer for refreshing the identity is initialized
122
- * */
123
- async function startRefreshingIdentityCycle(clientId, clientSecret, authorityUrl, configNode) {
124
- let retries = 5;
125
-
126
- const refresh = async () => {
127
- try {
128
- const newTokenSet = await getFreshTokenSet(clientId, clientSecret, authorityUrl);
129
- const expiresIn = await getExpiresInForExternalTaskWorkers(newTokenSet);
130
- const delay = expiresIn * DELAY_FACTOR * 1000;
131
-
132
- freshIdentity = getIdentityForExternalTaskWorkers(newTokenSet);
133
-
134
- configNode.setIdentity(freshIdentity);
135
-
136
- retries = 5;
137
- setTimeout(refresh, delay);
138
- } catch (error) {
139
- if (retries === 0) {
140
- console.error(
141
- 'Could not refresh identity for external task worker processes. Stopping all external task workers.',
142
- { error }
143
- );
144
- return;
145
- }
146
- console.error('Could not refresh identity for external task worker processes.', {
147
- error,
148
- retryCount: retries,
149
- });
150
- retries--;
151
-
152
- const delay = 2 * 1000;
153
- setTimeout(refresh, delay);
154
- }
155
- };
156
-
157
- await refresh();
158
- }
@@ -0,0 +1,150 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('processcube-google-docs-mail-template', {
3
+ category: 'ProcessCube Tools',
4
+ color: '#02AFD6',
5
+ defaults: {
6
+ name: { value: '' },
7
+ template_link: { value: '', type: 'str' },
8
+ template_link_type: { value: 'str'},
9
+ },
10
+ inputs: 1,
11
+ outputs: 1,
12
+ icon: 'font-awesome/fa-sign-in',
13
+ label: function () {
14
+ return this.name || 'processcube-google-docs-mail-template';
15
+ },
16
+ oneditprepare: function () {
17
+ $('#node-input-template_link').typedInput({
18
+ default: 'msg',
19
+ types: ['msg', 'str'],
20
+ });
21
+
22
+ $('#node-input-template_link').typedInput('value', this.template_link);
23
+ $('#node-input-template_link').typedInput('type', this.template_link_type);
24
+ },
25
+ oneditsave: function () {
26
+ (this.template_link = $('#node-input-template_link').typedInput('value')),
27
+ (this.template_link_type = $('#node-input-template_link').typedInput('type'));
28
+ },
29
+ });
30
+ </script>
31
+
32
+ <script type="text/html" data-template-name="processcube-google-docs-mail-template">
33
+ <div class="form-row">
34
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
35
+ <input type="text" id="node-input-name" placeholder="Name" />
36
+ </div>
37
+ <div class="form-row">
38
+ <label for="node-input-template_link"><i class="fa fa-tag"></i> Template Link</label>
39
+ <input type="text" id="node-input-template_link" placeholder="Template Link" />
40
+ </div>
41
+ </script>
42
+
43
+ <script type="text/markdown" data-help-name="processcube-google-docs-mail-template">
44
+ # Email Template Renderer (with Google Drive/Docs Support)
45
+
46
+ This Node-RED template module downloads a ZIP archive from a URL, extracts an HTML file and embedded images, replaces placeholders, and prepares the HTML content with inline attachments (CID) for email delivery.
47
+
48
+ ## Google Drive / Docs Link Support
49
+
50
+ The ZIP file must be publicly accessible via a **shared Google Drive link** (at least “Anyone with the link can view”). The following link formats are supported:
51
+
52
+ ```js
53
+ // Format 0: Google Docs (export as ZIP)
54
+ const editMatch = link.match(/https:\/\/docs\.google\.com\/document\/d\/([^/]+)/);
55
+ if (editMatch) {
56
+ const fileId = editMatch[1];
57
+ return `https://docs.google.com/document/d/${fileId}/export?format=zip`;
58
+ }
59
+
60
+ // Format 1: Google Drive – shared file
61
+ const fileMatch = link.match(/https:\/\/drive\.google\.com\/file\/d\/([^/]+)\/view/);
62
+ if (fileMatch) {
63
+ return `https://drive.google.com/uc?export=download&id=${fileMatch[1]}`;
64
+ }
65
+
66
+ // Format 2: Google Drive – open by ID
67
+ const openMatch = link.match(/https:\/\/drive\.google\.com\/open\?id=([^&]+)/);
68
+ if (openMatch) {
69
+ return `https://drive.google.com/uc?export=download&id=${openMatch[1]}`;
70
+ }
71
+ ```
72
+
73
+ > Make sure that the file is shared with one of these formats and publicly accessible.
74
+
75
+ ---
76
+
77
+ ## Processing Steps
78
+
79
+ 1. **Download** the ZIP archive from the URL.
80
+ 2. **Extract** the contents (expects one HTML file in the root directory).
81
+ 3. **Embed images**: All images referenced in `images/` will be replaced with `cid:` links.
82
+ 4. **Replace placeholders** in the following formats:
83
+ - `{{field}}`
84
+ - `///field///`
85
+ 5. **Output**:
86
+ - rendered HTML string
87
+ - `msg.attachments[]` containing Nodemailer-compatible inline files
88
+
89
+ ---
90
+
91
+ ## Inputs
92
+
93
+ ### `template_link` (String||msg): The URL to the ZIP archive containing the HTML template and images.
94
+ ### `payload` (JSON): Field to replace placeholders in the HTML template.
95
+
96
+ Fields for placeholder replacement. Example:
97
+
98
+ ```json
99
+ {
100
+ "user_name": "Martin",
101
+ "newsletter_date": "May 5, 2025",
102
+ "announcement": "Our new album will be released soon!"
103
+ }
104
+ ```
105
+
106
+ Used to replace `{{user_name}}` or `///user_name///` in the HTML template.
107
+
108
+ ---
109
+
110
+ ## Outputs
111
+
112
+ ### `payload` (String)
113
+
114
+ The rendered HTML content with embedded CID references and replaced placeholders.
115
+
116
+ ### `attachments` (Array)
117
+
118
+ Array of objects like:
119
+
120
+ ```json
121
+ {
122
+ "filename": "image1.png",
123
+ "path": "/path/to/file/image1.png",
124
+ "cid": "image1"
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Example
131
+
132
+ ```text
133
+ &lt;p&gt;Hello &#123;&#123;user_name&#125;&#125;,&lt;/p&gt;
134
+ &lt;img src=&quot;images/image1.png&quot;&gt;
135
+ ```
136
+
137
+ Becomes:
138
+
139
+ ```text
140
+ &lt;p&gt;Hello Martin,&lt;/p&gt;
141
+ &lt;img src=&quot;cid:image1&quot;&gt;
142
+ ```
143
+
144
+ ---
145
+
146
+ ## References
147
+
148
+ - [The ProcessCube Developer Network](https://processcube.io) – All documentation for the ProcessCube&copy; platform
149
+ - [Node-RED Integration in ProcessCube&copy;](https://processcube.io/docs/node-red) – Node-RED integration in ProcessCube&copy;
150
+ </script>
@@ -0,0 +1,158 @@
1
+ module.exports = function (RED) {
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { pipeline } = require('stream');
6
+ const { promisify } = require('util');
7
+ const AdmZip = require('adm-zip');
8
+ const fetch = require('node-fetch');
9
+
10
+ const streamPipeline = promisify(pipeline);
11
+
12
+ function ProcesssCubeGoogleDocsMailTemplate(config) {
13
+ RED.nodes.createNode(this, config);
14
+ const node = this;
15
+
16
+ node.on('input', async function (msg) {
17
+
18
+ try {
19
+
20
+ async function inlineCssImport(html) {
21
+ // 1. Finde @import-Zeile
22
+ const importRegex = /@import\s+url\(([^)]+)\);?/;
23
+ const match = html.match(importRegex);
24
+ if (!match) return html; // keine @import-Zeile gefunden
25
+
26
+ const url = match[1].replace(/['"]/g, ''); // evtl. Anführungszeichen entfernen
27
+
28
+ try {
29
+ const response = await fetch(url);
30
+ if (!response.ok) throw new Error(`Fehler beim Laden von ${url}`);
31
+
32
+ const cssContent = await response.text();
33
+
34
+ // 2. Ersetze @import-Zeile durch eingebettetes CSS
35
+ const embeddedStyle = `\n/* inlined from ${url} */\n${cssContent}`;
36
+ return html.replace(importRegex, embeddedStyle);
37
+ } catch (error) {
38
+ console.error('Fehler beim Inlining der CSS-Datei:', error);
39
+ return html; // Fallback: Original belassen
40
+ }
41
+ }
42
+
43
+
44
+ function convertGoogleDriveLink(link) {
45
+ // Format 0: https://docs.google.com/document/d/FILE_ID/edit
46
+ const editMatch = link.match(/https:\/\/docs\.google\.com\/document\/d\/([^/]+)/);
47
+ if (editMatch) {
48
+ const fileId = editMatch[1];
49
+ return `https://docs.google.com/document/d/${fileId}/export?format=zip`;
50
+ }
51
+
52
+ // Format 1: https://drive.google.com/file/d/FILE_ID/view?usp=sharing
53
+ const fileMatch = link.match(/https:\/\/drive\.google\.com\/file\/d\/([^/]+)\/view/);
54
+ if (fileMatch) {
55
+ return `https://drive.google.com/uc?export=download&id=${fileMatch[1]}`;
56
+ }
57
+
58
+ // Format 2: https://drive.google.com/open?id=FILE_ID
59
+ const openMatch = link.match(/https:\/\/drive\.google\.com\/open\?id=([^&]+)/);
60
+ if (openMatch) {
61
+ return `https://drive.google.com/uc?export=download&id=${openMatch[1]}`;
62
+ }
63
+
64
+ // Anderenfalls unverändert zurückgeben
65
+ return link;
66
+ }
67
+
68
+ function renderTemplate(html, payload) {
69
+ // Ersetze {{feld}} und ///feld///
70
+ return html.replace(/({{([^}]+)}}|\/\/\/([^/]+)\/\/\/)/g, (match, _, field1, field2) => {
71
+ const key = field1 || field2;
72
+ return payload[key.trim()] ?? match; // fallback: Platzhalter bleibt bestehen
73
+ });
74
+ }
75
+
76
+ function removeGoogleRedirects(html) {
77
+ return html.replace(/https:\/\/www\.google\.com\/url\?q=([^"&]+)[^"]*/g, (match, actualUrl) => {
78
+ try {
79
+ // Google-URLs sind URL-encoded – dekodieren
80
+ return decodeURIComponent(actualUrl);
81
+ } catch {
82
+ return actualUrl;
83
+ }
84
+ });
85
+ }
86
+
87
+ const template_link = RED.util.evaluateNodeProperty(config.template_link, config.template_link_type, node, msg);
88
+
89
+ const customRoot = path.resolve(RED.settings.userDir, 'tmp/processcube-google-docs-mail-template');
90
+ fs.mkdirSync(customRoot, { recursive: true });
91
+ const tempDir = fs.mkdtempSync(path.join(customRoot, 'run-'));
92
+ const zipPath = path.join(tempDir, 'downloaded.zip');
93
+
94
+ const url = convertGoogleDriveLink(template_link);
95
+
96
+ const response = await fetch(url);
97
+ if (!response.ok) {
98
+ throw new Error(`Fehler beim Herunterladen: ${response.status} ${response.statusText}`);
99
+ }
100
+ await streamPipeline(response.body, fs.createWriteStream(zipPath));
101
+
102
+ const zip = new AdmZip(zipPath);
103
+ zip.extractAllTo(tempDir, true);
104
+
105
+ // === HTML-Datei im obersten Verzeichnis finden ===
106
+ const topLevelFiles = fs.readdirSync(tempDir, { withFileTypes: true });
107
+ const htmlEntry = topLevelFiles.find(file =>
108
+ file.isFile() && file.name.toLowerCase().endsWith('.html')
109
+ );
110
+
111
+ if (!htmlEntry) {
112
+ throw new Error('Keine HTML-Datei im ZIP-Hauptverzeichnis gefunden.');
113
+ }
114
+
115
+ const htmlPath = path.join(tempDir, htmlEntry.name);
116
+ let html = fs.readFileSync(htmlPath, 'utf8');
117
+
118
+ // === Bilder durch CID ersetzen ===
119
+ const imgRegex = /src="images\/([^"]+)"/g;
120
+ const attachments = [];
121
+ let match;
122
+
123
+ while ((match = imgRegex.exec(html)) !== null) {
124
+ const fileName = match[1];
125
+ const cidName = path.parse(fileName).name;
126
+ const imgPath = path.join(tempDir, 'images', fileName);
127
+
128
+ if (fs.existsSync(imgPath)) {
129
+ attachments.push({
130
+ filename: fileName,
131
+ path: imgPath,
132
+ cid: cidName
133
+ });
134
+
135
+ html = html.replace(`src="images/${fileName}"`, `src="cid:${cidName}"`);
136
+ }
137
+ }
138
+
139
+ html = await inlineCssImport(html);
140
+
141
+ let new_payload = renderTemplate(html, msg.payload);
142
+
143
+ // ggf. mit schalter
144
+ new_payload = removeGoogleRedirects(new_payload);
145
+
146
+ msg.payload = new_payload;
147
+ msg.attachments = attachments;
148
+
149
+
150
+ node.send(msg);
151
+ } catch (queryError) {
152
+ node.error(`Generate the content: ${queryError.message}`, msg);
153
+ }
154
+ });
155
+ }
156
+
157
+ RED.nodes.registerType('processcube-google-docs-mail-template', ProcesssCubeGoogleDocsMailTemplate);
158
+ };
@@ -0,0 +1,44 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('processdefinition-deploy', {
3
+ category: 'ProcessCube',
4
+ color: '#02AFD6',
5
+ defaults: {
6
+ name: { value: '' },
7
+ engine: { value: '', type: 'processcube-engine-config' },
8
+ },
9
+ inputs: 1,
10
+ outputs: 1,
11
+ icon: 'processdefinition_deploy.svg',
12
+ label: function () {
13
+ return this.name || 'processdefinition-deploy';
14
+ }
15
+ });
16
+ </script>
17
+
18
+ <script type="text/html" data-template-name="processdefinition-deploy">
19
+ <div class="form-row">
20
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
21
+ <input type="text" id="node-input-name" placeholder="Name" />
22
+ </div>
23
+ <div class="form-row">
24
+ <label for="node-input-engine"><i class="fa fa-tag"></i> Engine-URL</label>
25
+ <input type="text" id="node-input-engine" placeholder="http://engine:8000" />
26
+ </div>
27
+ </script>
28
+
29
+ <script type="text/markdown" data-help-name="processdefinition-deploy">
30
+ A node to deploy process definition to the ProcessCube Engine.
31
+
32
+ ## Inputs
33
+
34
+ : payload (String) : XML of the bpmn file
35
+
36
+ ## Outputs
37
+
38
+ : payload (String) : XML of the bpmn file
39
+
40
+ ### References
41
+
42
+ - [The ProcessCube&copy; Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; platform
43
+ - [ProcessCube&copy; LowCode Integration](https://processcube.io/docs/node-red) - LowCode integration in ProcessCube&copy;
44
+ </script>
@@ -0,0 +1,28 @@
1
+ module.exports = function (RED) {
2
+ function ProcessdefinitionDeploy(config) {
3
+ RED.nodes.createNode(this, config);
4
+ var node = this;
5
+
6
+ node.on('input', function (msg) {
7
+ node.engine = RED.nodes.getNode(config.engine);
8
+ const client = node.engine.engineClient;
9
+ const isUser = !!msg._client?.user && !!msg._client.user.accessToken;
10
+ const userIdentity = isUser ? { userId: msg._client.user.id, token: msg._client.user.accessToken } : null;
11
+
12
+ if (!client) {
13
+ node.error('No engine configured.');
14
+ return;
15
+ }
16
+
17
+ client.processDefinitions
18
+ .persistProcessDefinitions(msg.payload, { overwriteExisting: true, identity: userIdentity })
19
+ .then(() => {
20
+ node.send(msg);
21
+ })
22
+ .catch((error) => {
23
+ node.error(error, {});
24
+ });
25
+ });
26
+ }
27
+ RED.nodes.registerType('processdefinition-deploy', ProcessdefinitionDeploy);
28
+ };