data-primals-engine 1.1.1 → 1.1.3
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/.github/workflows/node.js.yml +2 -7
- package/README.md +2 -2
- package/client/src/App.jsx +33 -16
- package/client/src/App.scss +21 -0
- package/client/src/Dashboard.scss +5 -1
- package/client/src/DashboardFlexViewItem.jsx +1 -2
- package/client/src/DisplayFlexNodeRenderer.jsx +120 -4
- package/client/src/FlexBuilder.jsx +2 -2
- package/client/src/FlexBuilder.scss +5 -0
- package/client/src/FlexBuilderControls.jsx +201 -22
- package/client/src/FlexBuilderPreview.jsx +43 -1
- package/client/src/FlexNode.jsx +93 -5
- package/client/src/FlexTreeUtils.js +42 -11
- package/client/src/ModelList.jsx +33 -20
- package/client/src/constants.js +4 -4
- package/package.json +3 -3
- package/server.js +1 -7
- package/src/constants.js +165 -16
- package/src/defaultModels.js +49 -0
- package/src/email.js +1 -1
- package/src/engine.js +17 -0
- package/src/modules/data.js +146 -13
- package/src/modules/workflow.js +86 -56
- package/test/data.integration.test.js +2 -2
package/client/src/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
export const seoTitle = '
|
|
2
|
+
export const seoTitle = 'Self Data Hosting';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// Dans ConditionBuilder.jsx ou un fichier de constantes partagé
|
|
@@ -51,9 +51,9 @@ export const MONGO_OPERATORS = {
|
|
|
51
51
|
|
|
52
52
|
export const profiles = {
|
|
53
53
|
'personal': ['contact', 'location', 'imageGallery', 'budget', 'currency', 'taxonomy'], // budget,
|
|
54
|
-
'developer': ['alert','request','webpage', 'content', 'taxonomy', 'resource', 'translation', 'contact', 'location', 'channel', 'lang', 'token', 'message', 'ticket', 'user', 'permission', 'role'],
|
|
54
|
+
'developer': ['alert','endpoint','request','webpage', 'content', 'taxonomy', 'resource', 'translation', 'contact', 'location', 'channel', 'lang', 'token', 'message', 'ticket', 'user', 'permission', 'role'],
|
|
55
55
|
'company': ['alert','request','location', 'campaign', 'order', 'currency', 'product', 'cart', 'cartItem', 'invoice', 'messaging', 'user', 'role', 'permission', 'token','translation', 'lang', 'webpage', 'content', 'taxonomy', 'contact', 'resource', 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'kpi', 'dashboard'],
|
|
56
|
-
'engineer': ['alert','request','dashboard', 'kpi', 'user', 'role', 'token', 'permission', 'workflow', 'workflowRun', 'workflowStep', "channel", "message", 'workflowAction', 'workflowTrigger']
|
|
56
|
+
'engineer': ['alert','endpoint','request','dashboard', 'kpi', 'user', 'role', 'token', 'permission', 'workflow', 'workflowRun', 'workflowStep', "channel", "message", 'workflowAction', 'workflowTrigger']
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export const OPERAND_TYPES = {
|
|
@@ -64,5 +64,5 @@ export const OPERAND_TYPES = {
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
export const getHost = () => {
|
|
67
|
-
return process.env.HOST || 'localhost';
|
|
67
|
+
return process.env.HOST || host || 'localhost';
|
|
68
68
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-primals-engine",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "data-primals-engine is the package responsible from handling large amount of data in a practical and performant way. It can handle large amount of data in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation.",
|
|
5
5
|
"main": "src/engine.js",
|
|
6
6
|
"type": "module",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"tar": "^7.4.3",
|
|
91
91
|
"uniqid": "^5.4.0",
|
|
92
92
|
"vitest": "^3.2.4",
|
|
93
|
-
"
|
|
93
|
+
"isolated-vm": "^4.7.2",
|
|
94
94
|
"yaml": "^2.8.0"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
@@ -115,6 +115,6 @@
|
|
|
115
115
|
"author": "anonympins",
|
|
116
116
|
"license": "MIT",
|
|
117
117
|
"engines": {
|
|
118
|
-
"node": ">=
|
|
118
|
+
"node": ">=20.0.0"
|
|
119
119
|
}
|
|
120
120
|
}
|
package/server.js
CHANGED
|
@@ -25,17 +25,11 @@ if (process.argv.length === 3) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
|
|
28
29
|
const port = process.env.PORT || 7633;
|
|
29
30
|
engine.start(port, async (r) => {
|
|
30
31
|
const logger = engine.getComponent(Logger);
|
|
31
32
|
console.log("Server started on port" + port);
|
|
32
33
|
timer.stop();
|
|
33
34
|
|
|
34
|
-
// 2. Use sirv to serve static files from 'client/dist'
|
|
35
|
-
// The 'single: true' option is key for SPAs. It will serve 'index.html'
|
|
36
|
-
// for any route that doesn't match a file, enabling client-side routing.
|
|
37
|
-
engine.use(sirv('client/dist', {
|
|
38
|
-
single: true,
|
|
39
|
-
dev: process.env.NODE_ENV === 'development'
|
|
40
|
-
}));
|
|
41
35
|
});
|
package/src/constants.js
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enables auto-installation at startup
|
|
3
|
+
* @type {boolean}
|
|
4
|
+
*/
|
|
5
|
+
export const install = true;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Database name
|
|
9
|
+
* @type {string}
|
|
10
|
+
*/
|
|
1
11
|
export const dbName = "engine";
|
|
2
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Web server host (for cookie domain)
|
|
15
|
+
* @type {string}
|
|
16
|
+
*/
|
|
17
|
+
export const host = 'localhost'; // or myhost.tld
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Cookie secret key (if COOKIES_SECRET is set, it will override this variable)
|
|
21
|
+
* @type {string}
|
|
22
|
+
*/
|
|
3
23
|
export const cookiesSecret = 'hoaivuymzovyoznllmafivpzaovphlejvalwjvelfhqochakfesv';
|
|
4
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Available languages of the system
|
|
27
|
+
* You need to translate the i18n file, or translations ones.
|
|
28
|
+
* @type {string[]}
|
|
29
|
+
*/
|
|
5
30
|
export const availableLangs = [
|
|
6
31
|
"en",
|
|
7
32
|
"fa",
|
|
@@ -17,63 +42,180 @@ export const availableLangs = [
|
|
|
17
42
|
"sv"
|
|
18
43
|
];
|
|
19
44
|
|
|
20
|
-
|
|
45
|
+
/**
|
|
46
|
+
* AWS default configuration (overrided by AWS_* environment variables)
|
|
47
|
+
* @type {{bucketName: string, region: string}}
|
|
48
|
+
*/
|
|
21
49
|
export const awsDefaultConfig = {
|
|
22
50
|
bucketName : 'bucket-primals',
|
|
23
51
|
region: 'eu-north-1'
|
|
24
52
|
}
|
|
25
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Email default configuration (overrided by SMTP_* environment variables)
|
|
56
|
+
* @type {{from: string}}
|
|
57
|
+
*/
|
|
26
58
|
export const emailDefaultConfig = {
|
|
27
|
-
from: "Support - data@primals.net <data@primals.net>"
|
|
59
|
+
from: "Support - data@primals.net <data@primals.net>",
|
|
60
|
+
host: 'smtp.mydomain.tld',
|
|
61
|
+
port: 2500,
|
|
62
|
+
secure: false,
|
|
63
|
+
user: 'user',
|
|
64
|
+
pass: 'password'
|
|
28
65
|
}
|
|
29
66
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Maximum number of models per user
|
|
69
|
+
* @type {number}
|
|
70
|
+
*/
|
|
34
71
|
export const maxModelsPerUser = 1000;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Maximum number of data per user
|
|
75
|
+
* @type {number}
|
|
76
|
+
*/
|
|
35
77
|
export const maxTotalDataPerUser = 500000;
|
|
36
|
-
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Maximum length of a string
|
|
81
|
+
* @type {number}
|
|
82
|
+
*/
|
|
37
83
|
export const maxStringLength = 4096;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Maximum length of a password
|
|
87
|
+
* @type {number}
|
|
88
|
+
*/
|
|
38
89
|
export const maxPasswordLength = 100000;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Maximum length of a rich text
|
|
93
|
+
* @type {number}
|
|
94
|
+
*/
|
|
39
95
|
export const maxRichTextLength = 100000;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Maximum number of exportable data per user per request
|
|
99
|
+
* @type {number}
|
|
100
|
+
*/
|
|
40
101
|
export const maxExportCount = 50000;
|
|
102
|
+
|
|
41
103
|
export const maxMagnetsDataPerModel = 100;
|
|
42
104
|
export const maxMagnetsModels = 20;
|
|
43
|
-
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Maximum number of data per request
|
|
108
|
+
* @type {number}
|
|
109
|
+
*/
|
|
44
110
|
export const maxRequestData = 2500;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Maximum number of data per post
|
|
114
|
+
* @type {number}
|
|
115
|
+
*/
|
|
45
116
|
export const maxPostData = 500;
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Maximum number of relations per data
|
|
121
|
+
* @type {number}
|
|
122
|
+
*/
|
|
46
123
|
export const maxRelationsPerData = 1500;
|
|
47
|
-
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Maximum number of filters per request
|
|
127
|
+
* @type {number}
|
|
128
|
+
*/
|
|
48
129
|
export const maxFilterDepth = 8;
|
|
49
130
|
export const elementsPerPage = 30;
|
|
50
131
|
|
|
51
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Maximum number of alerts per user
|
|
134
|
+
* @type {number}
|
|
135
|
+
*/
|
|
52
136
|
export const maxAlertsPerUser = 15;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Storage safety margin (between 0 and 1)
|
|
140
|
+
* @type {number}
|
|
141
|
+
*/
|
|
53
142
|
export const storageSafetyMargin = 0.95;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Number of bytes in Kilobytes constant
|
|
146
|
+
* @type {number}
|
|
147
|
+
*/
|
|
54
148
|
export const kilobytes = 1024;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Number of bytes in Megabytes constant
|
|
152
|
+
* @type {number}
|
|
153
|
+
*/
|
|
55
154
|
export const megabytes = 1024*1024;
|
|
56
155
|
|
|
57
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Maximum bytes per second for data throttling
|
|
158
|
+
* @type {number}
|
|
159
|
+
*/
|
|
58
160
|
export const maxBytesPerSecondThrottleData = 200*kilobytes; // 200Ko/s
|
|
59
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Search request timeout (in milliseconds)
|
|
164
|
+
* @type {number}
|
|
165
|
+
*/
|
|
60
166
|
export const searchRequestTimeout = 15000;
|
|
61
167
|
|
|
62
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Maximum model name length
|
|
170
|
+
* @type {number}
|
|
171
|
+
*/
|
|
63
172
|
export const maxModelNameLength = 150;
|
|
64
173
|
|
|
65
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Maximum file size (in bytes)
|
|
176
|
+
* @type {number}
|
|
177
|
+
*/
|
|
66
178
|
export const maxFileSize = 20 * 1024 * 1024; // 20 Mo
|
|
67
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Main fields types
|
|
182
|
+
* @type {string[]}
|
|
183
|
+
*/
|
|
68
184
|
export const mainFieldsTypes = ['string_t', 'string', 'url', 'enum', 'email', 'phone', 'date', 'datetime'];
|
|
69
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Maximum number of executions per step in workflows
|
|
188
|
+
* @type {number}
|
|
189
|
+
*/
|
|
70
190
|
export const maxExecutionsByStep = 5;
|
|
191
|
+
/**
|
|
192
|
+
* Maximum number of steps per workflow
|
|
193
|
+
* @type {number}
|
|
194
|
+
*/
|
|
71
195
|
export const maxWorkflowSteps = 15;
|
|
72
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Maximum number of private files per user
|
|
199
|
+
* @type {number}
|
|
200
|
+
*/
|
|
73
201
|
export const maxPrivateFileSize = 20 * megabytes; // Taille max par fichier privé (20 Mo)
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Maximum total size of private files (in bytes)
|
|
205
|
+
* @type {number}
|
|
206
|
+
*/
|
|
74
207
|
export const maxTotalPrivateFilesSize = 250 * megabytes;
|
|
75
208
|
|
|
76
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Timeout for Javacript execution in VMs (in milliseconds)
|
|
211
|
+
* @type {number}
|
|
212
|
+
*/
|
|
213
|
+
export const timeoutVM = 5000;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Options for the HTML sanitizer
|
|
217
|
+
* @type {{allowedSchemesByTag: {}, selfClosing: string[], allowedSchemes: string[], enforceHtmlBoundary: boolean, disallowedTagsMode: string, allowProtocolRelative: boolean, allowedAttributes: {a: string[], img: string[], code: string[]}, allowedTags: string[], allowedSchemesAppliedToAttributes: string[]}}
|
|
218
|
+
*/
|
|
77
219
|
export const optionsSanitizer = {
|
|
78
220
|
allowedTags: [
|
|
79
221
|
"img",
|
|
@@ -102,8 +244,12 @@ export const optionsSanitizer = {
|
|
|
102
244
|
}
|
|
103
245
|
|
|
104
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Meta models are arranging models in groups
|
|
249
|
+
* May evolve in the future with the use of packs
|
|
250
|
+
* @type {{}}
|
|
251
|
+
*/
|
|
105
252
|
export const metaModels = {};
|
|
106
|
-
|
|
107
253
|
metaModels['common'] = { load: ['contact', 'location', 'request'] };
|
|
108
254
|
metaModels['personal'] = { load: ['budget', 'imageGallery'] };
|
|
109
255
|
metaModels['users'] = { load: ['permission', 'role', 'user', 'token'], 'require': ['i18n', 'common'] };
|
|
@@ -119,7 +265,10 @@ metaModels['workflow'] = { load: ['env', 'workflow', 'workflowRun', 'workflowAct
|
|
|
119
265
|
metaModels['erp'] = { load: [ 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'dashboard', 'kpi'] };
|
|
120
266
|
|
|
121
267
|
|
|
122
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Available model field attributes
|
|
270
|
+
* @type {string[]}
|
|
271
|
+
*/
|
|
123
272
|
export const allowedFields = ['locked', 'hiddenable', 'anonymized', 'condition', 'color', 'index', 'type', 'required', 'hint', 'default', 'validate', 'unique', 'name', 'placeholder', 'asMain'];
|
|
124
273
|
|
|
125
274
|
|
package/src/defaultModels.js
CHANGED
|
@@ -1394,5 +1394,54 @@ export const defaultModels = {
|
|
|
1394
1394
|
"hint": "List of sponsors or partners involved in the event."
|
|
1395
1395
|
}
|
|
1396
1396
|
]
|
|
1397
|
+
},
|
|
1398
|
+
endpoint: {
|
|
1399
|
+
name: "endpoint",
|
|
1400
|
+
description: "Defines custom API endpoints that execute a server-side script.",
|
|
1401
|
+
fields: [
|
|
1402
|
+
{
|
|
1403
|
+
name: "name",
|
|
1404
|
+
type: "string",
|
|
1405
|
+
required: true,
|
|
1406
|
+
asMain: true,
|
|
1407
|
+
hint: "A human-readable name to identify the endpoint."
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: "isActive",
|
|
1411
|
+
type: "boolean",
|
|
1412
|
+
default: true,
|
|
1413
|
+
hint: "If checked, the endpoint is active and can be called."
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
name: "path",
|
|
1417
|
+
type: "string",
|
|
1418
|
+
required: true,
|
|
1419
|
+
hint: "The URL path after /api/actions/ (e.g., 'send-welcome-email'). Do not include '/'.",
|
|
1420
|
+
placeholder: "my-custom-action"
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
name: "method",
|
|
1424
|
+
type: "enum",
|
|
1425
|
+
items: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
1426
|
+
required: true,
|
|
1427
|
+
default: "POST",
|
|
1428
|
+
hint: "The HTTP method required to call this endpoint."
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
name: "code",
|
|
1432
|
+
type: "code",
|
|
1433
|
+
language: "javascript",
|
|
1434
|
+
required: true,
|
|
1435
|
+
hint: "The script to execute. Must return a value or an object that will be the JSON response.",
|
|
1436
|
+
default: `// The script can access 'db', 'logger', 'env'.
|
|
1437
|
+
// request.body, request.query, request.params, request.headers available
|
|
1438
|
+
// The returned value will be the API's JSON response.
|
|
1439
|
+
|
|
1440
|
+
logger.info('Custom endpoint executed with body:', request.body);
|
|
1441
|
+
|
|
1442
|
+
return { success: true, message: 'Endpoint executed!', received: request.body };
|
|
1443
|
+
`
|
|
1444
|
+
}
|
|
1445
|
+
]
|
|
1397
1446
|
}
|
|
1398
1447
|
};
|
package/src/email.js
CHANGED
|
@@ -50,7 +50,7 @@ export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl =
|
|
|
50
50
|
if (emails.length === 0) return;
|
|
51
51
|
|
|
52
52
|
// Choisir le transporteur à utiliser
|
|
53
|
-
const transporter = smtpConfig ? createTransporter(smtpConfig) : defaultTransporter;
|
|
53
|
+
const transporter = smtpConfig ? createTransporter(smtpConfig||emailDefaultConfig) : defaultTransporter;
|
|
54
54
|
|
|
55
55
|
if (tpl === null) tpl = event_trigger("sendEmail:template",data, lang);
|
|
56
56
|
let html = tpl;
|
package/src/engine.js
CHANGED
|
@@ -12,6 +12,7 @@ import {createModel, deleteModels, getModels, validateModelStructure} from "./mo
|
|
|
12
12
|
import {defaultModels} from "./defaultModels.js";
|
|
13
13
|
import {DefaultUserProvider} from "./providers.js";
|
|
14
14
|
import formidableMiddleware from 'express-formidable';
|
|
15
|
+
import sirv from "sirv";
|
|
15
16
|
|
|
16
17
|
// Constants
|
|
17
18
|
const isProduction = process.env.NODE_ENV === 'production'
|
|
@@ -37,6 +38,7 @@ export const Engine = {
|
|
|
37
38
|
engine.getComponent(Logger).info(`Custom UserProvider '${providerInstance.constructor.name}' has been set.`);
|
|
38
39
|
};
|
|
39
40
|
|
|
41
|
+
|
|
40
42
|
const app = express();
|
|
41
43
|
// Allows you to set port in the project properties.
|
|
42
44
|
app.set('port', process.env.PORT || 3000);
|
|
@@ -68,6 +70,9 @@ export const Engine = {
|
|
|
68
70
|
engine.put = (...args) => {
|
|
69
71
|
return app.put(...args);
|
|
70
72
|
};
|
|
73
|
+
engine.all = (...args) => {
|
|
74
|
+
return app.all(...args);
|
|
75
|
+
};
|
|
71
76
|
engine.getModule = (module) => {
|
|
72
77
|
return engine._modules.find(m => m.module === module);
|
|
73
78
|
};
|
|
@@ -102,6 +107,12 @@ export const Engine = {
|
|
|
102
107
|
return Promise.resolve();
|
|
103
108
|
});
|
|
104
109
|
let server;
|
|
110
|
+
|
|
111
|
+
app.use(sirv('client/dist', {
|
|
112
|
+
single: true,
|
|
113
|
+
dev: process.env.NODE_ENV === 'development'
|
|
114
|
+
}));
|
|
115
|
+
|
|
105
116
|
engine.start = async (port, cb) =>{
|
|
106
117
|
// Use connect method to connect to the server
|
|
107
118
|
|
|
@@ -160,6 +171,12 @@ export const Engine = {
|
|
|
160
171
|
engine.resetModels = async () => {
|
|
161
172
|
await deleteModels();
|
|
162
173
|
};
|
|
174
|
+
engine.get('/api/health', (req, res) => {
|
|
175
|
+
res.status(200).json({
|
|
176
|
+
status: 'ok',
|
|
177
|
+
timestamp: new Date().toISOString()
|
|
178
|
+
});
|
|
179
|
+
});
|
|
163
180
|
return engine;
|
|
164
181
|
}
|
|
165
182
|
}
|
package/src/modules/data.js
CHANGED
|
@@ -66,6 +66,7 @@ import schedule from "node-schedule";
|
|
|
66
66
|
import {middleware} from "../middlewares/middleware-mongodb.js";
|
|
67
67
|
import i18n from "data-primals-engine/i18n";
|
|
68
68
|
import {
|
|
69
|
+
executeSafeJavascript,
|
|
69
70
|
runScheduledJobWithDbLock,
|
|
70
71
|
scheduleWorkflowTriggers,
|
|
71
72
|
triggerWorkflows
|
|
@@ -87,6 +88,7 @@ import {assistantGlobalLimiter} from "./assistant.js";
|
|
|
87
88
|
import {getAllPacks} from "../packs.js";
|
|
88
89
|
import {throttleMiddleware} from "../middlewares/throttle.js";
|
|
89
90
|
import {Config} from "../config.js";
|
|
91
|
+
import {profiles} from "../../client/src/constants.js";
|
|
90
92
|
|
|
91
93
|
// Obtenir le chemin du répertoire courant de manière fiable avec ES Modules
|
|
92
94
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -1216,6 +1218,80 @@ export const editModel = async (user, id, data) => {
|
|
|
1216
1218
|
};
|
|
1217
1219
|
|
|
1218
1220
|
|
|
1221
|
+
export async function handleCustomEndpointRequest(req, res) {
|
|
1222
|
+
const { path } = req.params;
|
|
1223
|
+
const method = req.method.toUpperCase();
|
|
1224
|
+
|
|
1225
|
+
const user = req.me;
|
|
1226
|
+
if (!user) {
|
|
1227
|
+
return res.status(401).json({ success: false, message: 'Authentication required.' });
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
try {
|
|
1231
|
+
// 1. Trouver l'endpoint correspondant dans la base de données
|
|
1232
|
+
const endpointSearch = await searchData({
|
|
1233
|
+
user,
|
|
1234
|
+
query: {
|
|
1235
|
+
model: 'endpoint',
|
|
1236
|
+
filter: {
|
|
1237
|
+
path: path,
|
|
1238
|
+
method: method,
|
|
1239
|
+
isActive: true
|
|
1240
|
+
},
|
|
1241
|
+
limit: 1
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
if (endpointSearch.count === 0) {
|
|
1246
|
+
logger.warn(`[Endpoint] 404 - No active endpoint found for user '${user.username}', path '${path}', method '${method}'.`);
|
|
1247
|
+
return res.status(404).json({ success: false, message: 'Endpoint not found.' });
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
const endpointDef = endpointSearch.data[0];
|
|
1251
|
+
|
|
1252
|
+
// 2. Préparer le contexte pour le script
|
|
1253
|
+
// On donne au script accès au corps, aux paramètres de la requête, etc.
|
|
1254
|
+
const contextData = {
|
|
1255
|
+
request:{
|
|
1256
|
+
body: req.fields,
|
|
1257
|
+
query: req.query,
|
|
1258
|
+
params: req.params,
|
|
1259
|
+
headers: req.headers
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
// 3. Exécuter le code de l'endpoint en utilisant notre sandbox sécurisé
|
|
1264
|
+
logger.info(`[Endpoint] Executing endpoint '${endpointDef.name}' for user '${user.username}'.`);
|
|
1265
|
+
const result = await executeSafeJavascript(
|
|
1266
|
+
{ script: endpointDef.code }, // On passe la définition du script
|
|
1267
|
+
contextData,
|
|
1268
|
+
user
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
// 4. Envoyer la réponse
|
|
1272
|
+
if (result.success) {
|
|
1273
|
+
// Le script a réussi, on retourne sa sortie
|
|
1274
|
+
res.status(200).json(result.data);
|
|
1275
|
+
} else {
|
|
1276
|
+
// Le script a échoué, on retourne une erreur 500 avec les logs
|
|
1277
|
+
logger.error(`[Endpoint] Execution failed for '${endpointDef.name}'. Error: ${result.message}`);
|
|
1278
|
+
|
|
1279
|
+
const r = {
|
|
1280
|
+
success: false,
|
|
1281
|
+
message: 'Endpoint script execution failed.',
|
|
1282
|
+
// On peut choisir d'exposer les logs pour le débogage
|
|
1283
|
+
details: result.message
|
|
1284
|
+
};
|
|
1285
|
+
r.logs = result.logs;
|
|
1286
|
+
res.status(500).json(r);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
logger.error(`[Endpoint] Critical error handling request for path '${path}': ${error.message}`, process.env.NODE_ENV === 'development'? error.stack : error.stack[0]);
|
|
1291
|
+
res.status(500).json({ success: false, message: 'An internal server error occurred.' });
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1219
1295
|
export async function onInit(defaultEngine) {
|
|
1220
1296
|
engine = defaultEngine;
|
|
1221
1297
|
logger = engine.getComponent(Logger);
|
|
@@ -1322,18 +1398,9 @@ export async function onInit(defaultEngine) {
|
|
|
1322
1398
|
await scheduleWorkflowTriggers();
|
|
1323
1399
|
|
|
1324
1400
|
await scheduleAlerts();
|
|
1325
|
-
// Dans onInit(defaultEngine) { ... }
|
|
1326
|
-
// ...
|
|
1327
1401
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
const primalsDb = MongoClient.db("primals");
|
|
1331
|
-
|
|
1332
|
-
let usersCollection = primalsDb.collection("users");
|
|
1333
|
-
return await usersCollection.updateOne({ username: user.username }, { $set: data }, { upsert: true })
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// Dans C:/Dev/hackersonline-engine/server/src/modules/data.js, dans onInit()
|
|
1402
|
+
engine.all('/api/actions/:path', [middlewareAuthenticator, userInitiator], handleCustomEndpointRequest);
|
|
1403
|
+
engine.post('/api/demo/initialize', [middlewareAuthenticator, userInitiator], handleDemoInitialization);
|
|
1337
1404
|
|
|
1338
1405
|
engine.post('/api/magnets', [middlewareAuthenticator, userInitiator], async (req, res) => {
|
|
1339
1406
|
const user = req.me;
|
|
@@ -2567,7 +2634,7 @@ export async function onInit(defaultEngine) {
|
|
|
2567
2634
|
return res.status(403).json({ success: false, error: i18n.t('api.permission.installPack') });
|
|
2568
2635
|
}
|
|
2569
2636
|
|
|
2570
|
-
const result = await installPack(
|
|
2637
|
+
const result = await installPack(id, user, lang);
|
|
2571
2638
|
|
|
2572
2639
|
if (result.success) {
|
|
2573
2640
|
res.status(200).json({ success: true, message: `Pack installed successfully.`, summary: result.summary });
|
|
@@ -5365,7 +5432,7 @@ async function logApiRequest(req, res, user, startTime, responseBody = null, err
|
|
|
5365
5432
|
* @param {string} lang - Le code de langue pour les données spécifiques.
|
|
5366
5433
|
* @returns {Promise<{success: boolean, summary: object, errors: Array, modifiedCount: number}>}
|
|
5367
5434
|
*/
|
|
5368
|
-
export async function installPack(
|
|
5435
|
+
export async function installPack(packId, user, lang) {
|
|
5369
5436
|
const packsCollection = getCollection('packs');
|
|
5370
5437
|
const pack = await packsCollection.findOne({ _id: new ObjectId(packId) });
|
|
5371
5438
|
|
|
@@ -5603,4 +5670,70 @@ export const installAllPacks = async () => {
|
|
|
5603
5670
|
console.log(util.inspect(packs, false, 20, true));
|
|
5604
5671
|
await packsCollection.deleteMany({ _user: { $exists : false }});
|
|
5605
5672
|
await packsCollection.insertMany(packs);
|
|
5673
|
+
}
|
|
5674
|
+
|
|
5675
|
+
// Dans C:/Dev/data-primals-engine/src/modules/data.js
|
|
5676
|
+
// Dans C:/Dev/data-primals-engine/src/modules/data.js
|
|
5677
|
+
|
|
5678
|
+
// ... (imports inchangés)
|
|
5679
|
+
|
|
5680
|
+
export async function handleDemoInitialization(req, res) {
|
|
5681
|
+
const user = req.me;
|
|
5682
|
+
const body = req.fields;
|
|
5683
|
+
const models = (Object.keys(profiles).includes(body.profile) && profiles[body.profile]) || '';
|
|
5684
|
+
if (!isDemoUser(user)) {
|
|
5685
|
+
return res.status(403).json({ success: false, error: "This action is only for demo users." });
|
|
5686
|
+
}
|
|
5687
|
+
if (!Array.isArray(models) || models.length === 0) {
|
|
5688
|
+
return res.status(400).json({ success: false, error: "A valid 'models' array is required." });
|
|
5689
|
+
}
|
|
5690
|
+
|
|
5691
|
+
logger.info(`[Demo Init] Starting initialization for user '${user.username}' with ${models.length} models.`);
|
|
5692
|
+
|
|
5693
|
+
try {
|
|
5694
|
+
// 1. Nettoyage de l'environnement (inchangé)
|
|
5695
|
+
const datasCollection = getCollection("datas");
|
|
5696
|
+
const modelsCollection = getCollection("models");
|
|
5697
|
+
const filesCollection = getCollection("files");
|
|
5698
|
+
|
|
5699
|
+
await datasCollection.deleteMany({ _user: user.username });
|
|
5700
|
+
await modelsCollection.deleteMany({ _user: user.username });
|
|
5701
|
+
const files = await filesCollection.find({ user: user.username }).toArray();
|
|
5702
|
+
for (const file of files) {
|
|
5703
|
+
await removeFile(file.guid, user).catch(e => logger.error(e.message));
|
|
5704
|
+
}
|
|
5705
|
+
await cancelAlerts(user);
|
|
5706
|
+
logger.info(`[Demo Init] Environment cleaned for user '${user.username}'.`);
|
|
5707
|
+
|
|
5708
|
+
const packToInstall = {
|
|
5709
|
+
name: `dynamic-pack-for-${user.username}-${Date.now()}`,
|
|
5710
|
+
description: `Dynamically generated pack for profile models.`,
|
|
5711
|
+
models: models,
|
|
5712
|
+
data: {}
|
|
5713
|
+
};
|
|
5714
|
+
|
|
5715
|
+
logger.info(`[Demo Init] Installing dynamically generated pack with models: [${models.join(', ')}].`);
|
|
5716
|
+
|
|
5717
|
+
// Create and install pack
|
|
5718
|
+
const packsCollection = getCollection('packs');
|
|
5719
|
+
const tempPack = { ...packToInstall, _user: 'system_temp' };
|
|
5720
|
+
const tempInsert = await packsCollection.insertOne(tempPack);
|
|
5721
|
+
const packId = tempInsert.insertedId;
|
|
5722
|
+
|
|
5723
|
+
const result = await installPack(packId, user, req.query.lang || 'en');
|
|
5724
|
+
|
|
5725
|
+
await packsCollection.deleteOne({ _id: packId });
|
|
5726
|
+
|
|
5727
|
+
if (result.success || result.modifiedCount > 0) {
|
|
5728
|
+
logger.info(`[Demo Init] Pack installed successfully for user '${user.username}'.`);
|
|
5729
|
+
res.status(200).json({ success: true, message: "Demo environment initialized successfully.", summary: result.summary });
|
|
5730
|
+
} else {
|
|
5731
|
+
logger.error(`[Demo Init] Pack installation failed for user '${user.username}'.`);
|
|
5732
|
+
res.status(500).json({ success: false, error: 'Demo pack installation failed.', errors: result.errors });
|
|
5733
|
+
}
|
|
5734
|
+
|
|
5735
|
+
} catch (error) {
|
|
5736
|
+
logger.error(`[Demo Init] Critical error during initialization for user '${user.username}':`, error);
|
|
5737
|
+
res.status(500).json({ success: false, error: 'An internal server error occurred during initialization.' });
|
|
5738
|
+
}
|
|
5606
5739
|
}
|