data-primals-engine 1.1.1 → 1.1.2

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.
@@ -1,5 +1,5 @@
1
1
 
2
- export const seoTitle = 'Your data is here';
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.1",
3
+ "version": "1.1.2",
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",
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
- // 10000 tiny users
31
- // 1000 modern users
32
- // 100 mega utilisateurs potentiality
33
- // 250 000 entrées par utilisateur
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
- export const maxDataPerModelPerUser = 10000;
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
- export const defaultMaxRequestData = 500;
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
- export const install = true;
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
- export const maxBytesPerSecondThrottleFile = 800*kilobytes; // 800ko/s
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
- export const maxModelsInCache = 100000;
168
+ /**
169
+ * Maximum model name length
170
+ * @type {number}
171
+ */
63
172
  export const maxModelNameLength = 150;
64
173
 
65
- export const maxDataSize = '20mb';
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
 
@@ -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
  }
@@ -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
- const saveUser = async (user, data) => {
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(logger, id, user, lang);
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(logger, packId, user, lang) {
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
  }