data-primals-engine 1.6.3 → 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.
Files changed (55) hide show
  1. package/README.md +52 -900
  2. package/client/index.js +3 -0
  3. package/client/package-lock.json +7563 -8811
  4. package/client/package.json +11 -1
  5. package/client/src/App.scss +29 -0
  6. package/client/src/AssistantChat.jsx +369 -362
  7. package/client/src/DataEditor.jsx +383 -383
  8. package/client/src/DataLayout.jsx +54 -20
  9. package/client/src/ModelList.jsx +280 -280
  10. package/client/src/ViewSwitcher.scss +0 -31
  11. package/client/src/_variables.scss +3 -0
  12. package/client/src/constants.js +81 -100
  13. package/client/src/contexts/CommandContext.jsx +274 -259
  14. package/client/vite.config.js +30 -30
  15. package/doc/AI-assistance.md +93 -0
  16. package/doc/Advanced-workflows.md +90 -0
  17. package/doc/Event-system.md +79 -0
  18. package/doc/Packs-gallery.md +73 -0
  19. package/doc/automation-workflows.md +102 -0
  20. package/doc/core-concepts.md +33 -0
  21. package/doc/custom-api-endpoints.md +40 -0
  22. package/doc/dashboards-kpis-charts.md +49 -0
  23. package/doc/data-management.md +120 -0
  24. package/doc/data-models.md +75 -0
  25. package/doc/index.md +14 -0
  26. package/doc/roles-permissions.md +43 -0
  27. package/doc/users.md +30 -0
  28. package/package.json +20 -10
  29. package/src/client.js +6 -4
  30. package/src/constants.js +1 -1
  31. package/src/core.js +31 -12
  32. package/src/defaultModels.js +1 -1
  33. package/src/engine.js +342 -335
  34. package/src/filter.js +72 -173
  35. package/src/migrate.js +1 -1
  36. package/src/modules/assistant/assistant.js +30 -20
  37. package/src/modules/data/data.backup.js +4 -4
  38. package/src/modules/data/data.js +8 -7
  39. package/src/modules/data/data.operations.js +186 -133
  40. package/src/modules/data/data.relations.js +3 -2
  41. package/src/modules/data/data.scheduling.js +1 -1
  42. package/src/modules/mongodb.js +17 -8
  43. package/src/modules/swagger.js +25 -5
  44. package/src/modules/user.js +108 -79
  45. package/src/modules/workflow.js +138 -3
  46. package/src/packs.js +5697 -5697
  47. package/src/profiles.js +19 -0
  48. package/swagger-en.yml +3391 -1550
  49. package/swagger-fr.yml +3385 -2896
  50. package/test/assistant.test.js +2 -2
  51. package/test/core.test.js +341 -0
  52. package/test/data.backup.integration.test.js +3 -1
  53. package/test/data.history.integration.test.js +0 -1
  54. package/test/data.integration.test.js +137 -2
  55. package/test/user.test.js +33 -29
package/src/filter.js CHANGED
@@ -1,7 +1,3 @@
1
- import {getCollectionForUser, isObjectId} from "./modules/mongodb.js";
2
- import {getModel} from "./modules/data/index.js";
3
- import {ObjectId} from "mongodb";
4
- import {safeAssignObject} from "./core.js";
5
1
 
6
2
  let safeRegex = null; // Par défaut, aucune fonction de validation n'est définie.
7
3
 
@@ -13,6 +9,47 @@ export function setSafeRegex(validator) {
13
9
  safeRegex = validator;
14
10
  }
15
11
 
12
+ /**
13
+ * Évalue une comparaison entre une valeur cible et une valeur de condition.
14
+ * @param {string} operator - L'opérateur de comparaison (ex: '$eq', '$lt').
15
+ * @param {*} targetValue - La valeur actuelle du champ dans les données.
16
+ * @param {*} processedConditionValue - La valeur de la condition à comparer.
17
+ * @param {object} condition - La condition complète pour le contexte de logging.
18
+ * @returns {boolean} - Le résultat de la comparaison.
19
+ */
20
+ function evaluateComparison(operator, targetValue, processedConditionValue, condition) {
21
+ const logClientEvalWarning = (message, details) => {
22
+ console.warn(`[Client Eval] ${message}:`, details);
23
+ };
24
+
25
+ try {
26
+ switch (operator) {
27
+ case '$eq': return targetValue == processedConditionValue; // Utilise '==' pour une comparaison plus souple
28
+ case '$ne': return targetValue != processedConditionValue;
29
+ case '$gt': return targetValue > processedConditionValue;
30
+ case '$lt': return targetValue < processedConditionValue;
31
+ case '$gte': return targetValue >= processedConditionValue;
32
+ case '$lte': return targetValue <= processedConditionValue;
33
+ case '$regex':
34
+ if (typeof targetValue !== 'string' || !processedConditionValue || typeof processedConditionValue !== 'string') return false;
35
+ try {
36
+ if (safeRegex && !safeRegex(processedConditionValue)) return false;
37
+ const regex = new RegExp(processedConditionValue, 'i');
38
+ return regex.test(targetValue);
39
+ } catch (e) {
40
+ logClientEvalWarning(`Invalid regex pattern: ${processedConditionValue}`, condition);
41
+ return false;
42
+ }
43
+ case '$in': return Array.isArray(processedConditionValue) && processedConditionValue.includes(targetValue);
44
+ case '$nin': return !Array.isArray(processedConditionValue) || !processedConditionValue.includes(targetValue);
45
+ default: return true; // Permissif par défaut pour les opérateurs non gérés
46
+ }
47
+ } catch (evalError) {
48
+ logClientEvalWarning(`Error during condition evaluation: ${operator}, targetValue=${targetValue}, processedConditionValue=${processedConditionValue}`, condition);
49
+ return false;
50
+ }
51
+ }
52
+
16
53
  /**
17
54
  * Récupère une valeur imbriquée dans un objet en utilisant une chaîne de chemin.
18
55
  * Gère les tableaux et les objets. Retourne undefined si le chemin n'est pas trouvé.
@@ -67,6 +104,17 @@ const evaluateSingleCondition = (currentModelDef, condition, formData, allModels
67
104
  return true; // Permissive default
68
105
  }
69
106
 
107
+ // If the condition is a standard query operator like { amount: { $lt: 1000 } }
108
+ const fieldNameFromCondition = Object.keys(condition)[0];
109
+ const conditionValueObject = condition[fieldNameFromCondition];
110
+
111
+ if (typeof conditionValueObject === 'object' && conditionValueObject !== null && !Array.isArray(conditionValueObject)) {
112
+ const operator = Object.keys(conditionValueObject)[0];
113
+ if (operator.startsWith('$')) {
114
+ return evaluateComparison(operator, formData[fieldNameFromCondition], conditionValueObject[operator], condition, checkRegex);
115
+ }
116
+ }
117
+
70
118
  // Si la condition est de la forme {field: value}, on la transforme en {$eq: value}
71
119
  if (!Object.keys(condition)[0].startsWith('$') && typeof condition[Object.keys(condition)[0]] !== 'object') {
72
120
  const fieldName = Object.keys(condition)[0];
@@ -81,12 +129,12 @@ const evaluateSingleCondition = (currentModelDef, condition, formData, allModels
81
129
  const [fieldPath, expectedValue] = condition[operator];
82
130
  if (typeof fieldPath === 'string' && fieldPath.startsWith('$')) {
83
131
  const fieldName = fieldPath.substring(1); // Enlève le $ devant
84
- const actualValue = formData[fieldName];
85
- if (operator === '$eq') return actualValue == expectedValue;
86
- if (operator === '$ne') return actualValue != expectedValue;
132
+ const actualValue = getNestedValue(formData, fieldName);
133
+ return evaluateComparison(operator, actualValue, expectedValue, condition, checkRegex);
87
134
  }
88
135
  }
89
136
 
137
+
90
138
  // Si la condition contient des opérateurs logiques, on les gère ici
91
139
  if (condition.$and || condition.$or || condition.$not || condition.$nor) {
92
140
  console.warn("[Client Eval] Condition logique détectée dans evaluateSingleCondition, ce n'est pas attendu. Devrait être géré par isConditionMet.");
@@ -165,7 +213,7 @@ const evaluateSingleCondition = (currentModelDef, condition, formData, allModels
165
213
  return false;
166
214
  }
167
215
 
168
- return evaluateComparison(fieldValue, targetValue, processedConditionValue, condition);
216
+ return evaluateComparison('$eq', targetValue, processedConditionValue, condition);
169
217
 
170
218
  function logClientEvalWarning(message, details) {
171
219
  console.warn(`[Client Eval] ${message}:`, details);
@@ -189,43 +237,6 @@ const evaluateSingleCondition = (currentModelDef, condition, formData, allModels
189
237
  }
190
238
  }
191
239
 
192
- function evaluateComparison(operator, targetValue, processedConditionValue, condition) {
193
- try {
194
- switch (typeof operator === 'object' ? Object.keys(operator)[0] : null) {
195
- case '$eq': return targetValue === processedConditionValue;
196
- case '$ne': return targetValue !== processedConditionValue;
197
- case '$gt': return targetValue > processedConditionValue;
198
- case '$lt': return targetValue < processedConditionValue;
199
- case '$gte': return targetValue >= processedConditionValue;
200
- case '$lte': return targetValue <= processedConditionValue;
201
- case '$regex':
202
- if (typeof targetValue !== 'string') return false;
203
- if (typeof processedConditionValue !== 'string') return false;
204
- try {
205
- // On vérifie si la fonction a été injectée ET si la regex est sûre.
206
- // Côté client, `safeRegex` sera null, donc la vérification est simplement ignorée.
207
- if( checkRegex && safeRegex && safeRegex(processedConditionValue)) {
208
- const regex = new RegExp(processedConditionValue, 'i');
209
- return regex.test(targetValue);
210
- }
211
- return false;
212
- } catch (e) {
213
- logClientEvalWarning(`Invalid regex pattern: ${processedConditionValue}`, condition);
214
- return false;
215
- }
216
- case '$in':
217
- return Array.isArray(processedConditionValue) && processedConditionValue.includes(String(targetValue));
218
- case '$nin':
219
- return !Array.isArray(processedConditionValue) || !processedConditionValue.includes(String(targetValue));
220
- default:
221
- logClientEvalWarning(`Unhandled operator in client evaluation logic: ${operator}`, condition);
222
- return true; // Permissive default
223
- }
224
- } catch (evalError) {
225
- logClientEvalWarning(`Error during client condition evaluation: ${operator}, targetValue=${targetValue}, processedConditionValue=${processedConditionValue}`, condition);
226
- return false;
227
- }
228
- }
229
240
  };
230
241
 
231
242
  export const isConditionMet = (model, cond, formData, allModels, user,checkRegex=true) => {
@@ -237,6 +248,22 @@ export const isConditionMet = (model, cond, formData, allModels, user,checkRegex
237
248
  return true;
238
249
  }
239
250
 
251
+ // --- NOUVELLE LOGIQUE ---
252
+ // Gère les conditions de type expression d'agrégation comme { "$lt": ["$amount", 1000] }
253
+ const operatorKeys = Object.keys(condition).filter(k => k.startsWith('$'));
254
+ if (operatorKeys.length === 1) {
255
+ const operator = operatorKeys[0];
256
+ const operands = condition[operator];
257
+
258
+ if (Array.isArray(operands) && operands.length === 2 && typeof operands[0] === 'string' && operands[0].startsWith('$')) {
259
+ const fieldName = operands[0].substring(1); // Extrait 'amount' de '$amount'
260
+ const expectedValue = operands[1];
261
+ const actualValue = getNestedValue(formData, fieldName);
262
+
263
+ return evaluateComparison(operator, actualValue, expectedValue, condition, checkRegex);
264
+ }
265
+ }
266
+
240
267
  // Cas spécial: Évaluation sans modèle (ex: pour les webhooks où la condition est déjà résolue)
241
268
  // Dans ce mode, la condition doit être un objet avec un seul opérateur.
242
269
  if (!model) {
@@ -314,131 +341,3 @@ export const isConditionMet = (model, cond, formData, allModels, user,checkRegex
314
341
  return evaluateSingleCondition(model, condition, formData, allModels, user, checkRegex);
315
342
  };
316
343
 
317
-
318
-
319
- /**
320
- * Remplace les placeholders dans un template (string, object, array) par des valeurs du contextData.
321
- * Version améliorée avec support des chemins complexes via resolvePathValue.
322
- */
323
- export async function substituteVariables(template, contextData, user) {
324
- // 1. Retourner les types non substituables tels quels
325
- if (template === null || (typeof template !== 'string' && typeof template !== 'object')) {
326
- return template;
327
- }
328
-
329
- // 2. Gérer les tableaux de manière récursive
330
- if (Array.isArray(template)) {
331
- return Promise.all(template.map(item => substituteVariables(item, contextData, user)));
332
- }
333
-
334
- // 3. Gérer les objets de manière récursive
335
- if (typeof template === 'object') {
336
- const newObj = {};
337
- for (const key in template) {
338
- if (Object.prototype.hasOwnProperty.call(template, key)) {
339
- const val = await substituteVariables(template[key], contextData, user);
340
- safeAssignObject(newObj, key, val);
341
- }
342
- }
343
- return newObj;
344
- }
345
-
346
- // --- À partir d'ici, nous savons que `template` est une chaîne de caractères ---
347
-
348
- // 4. Construire le contexte complet pour la substitution
349
- const dbCollection = await getCollectionForUser(user);
350
- const userEnvVars = await dbCollection.find({ _model: 'env', _user: user.username }).toArray();
351
- const userEnv = userEnvVars.reduce((acc, v) => ({ ...acc, [v.name]: v.value }), {});
352
-
353
- // `contextToSearch` contient toutes les données disponibles à sa racine
354
- const contextToSearch = { ...contextData, env: userEnv };
355
-
356
- // 5. Logique de résolution de valeur améliorée avec resolvePathValue
357
- const findValue = async (key) => {
358
- let path = key.trim();
359
- if (path.startsWith('context.')) {
360
- path = path.substring('context.'.length);
361
- }
362
- if (path.endsWith('._id')) {
363
- const basePath = path.slice(0, -4);
364
- const value = await findValue(basePath);
365
- return value?._id?.toString(); // Convertit l'ObjectId en string
366
- }
367
-
368
- // Gérer les valeurs dynamiques spéciales
369
- if (path === 'now') {
370
- return new Date().toISOString();
371
- } else if (path === 'randomUUID') {
372
- return crypto.randomUUID();
373
- } else if( path === "baseUrl" ){
374
- return process.env.NODE_ENV === 'production' ? 'https://'+getHost()+'/' : 'http://localhost:/'+port;
375
- }
376
-
377
- // Détecter si le chemin est complexe (contient plus d'un point)
378
- if (path.split('.').length > 1) {
379
- try {
380
- // Essayer de résoudre le chemin avec resolvePathValue
381
- const [root, ...rest] = path.split('.');
382
- // On vérifie si la racine du chemin (ex: 'triggerData') existe dans notre contexte
383
- if (contextToSearch[root]) {
384
- const resolvedValue = await resolvePathValue(
385
- rest.join('.'),
386
- contextToSearch[root], // On passe le bon objet de départ (ex: l'objet triggerData)
387
- user
388
- );
389
- if (resolvedValue !== undefined) {
390
- return resolvedValue;
391
- }
392
- }
393
- } catch (error) {
394
- console.warn(`Erreur lors de la résolution du chemin "${path}":`, error.message);
395
- // On continue avec la méthode normale si la résolution échoue
396
- }
397
- }
398
-
399
- // Fallback: chercher le chemin dans l'objet de contexte normal
400
- return getNestedValue(contextToSearch, path);
401
- };
402
-
403
- // CAS A : La chaîne est un unique placeholder (ex: "{context.triggerData.product.price}")
404
- const singlePlaceholderMatch = template.match(/^\{([^}]+)\}$/);
405
- if (singlePlaceholderMatch) {
406
- const key = singlePlaceholderMatch[1];
407
- const value = await findValue(key);
408
-
409
- if (value === undefined) {
410
- return template; // Placeholder not found, return as is.
411
- }
412
-
413
- // If the resolved value is a string, it might contain more placeholders.
414
- // We recursively call substituteVariables on it, but only if it's different
415
- // from the original template to prevent infinite loops.
416
- if (typeof value === 'string' && value !== template) {
417
- return substituteVariables(value, contextData, user);
418
- }
419
-
420
- // For non-string values or if value is same as template, return the value.
421
- return value;
422
- }
423
-
424
- // CAS B : La chaîne contient plusieurs placeholders ou mix texte/variables
425
- const placeholderRegex = /\{([^}]+)\}/g;
426
- const placeholders = [...template.matchAll(placeholderRegex)];
427
-
428
- // Si aucun placeholder trouvé, retourner la chaîne telle quelle
429
- if (placeholders.length === 0) {
430
- return template;
431
- }
432
-
433
- // Remplacer chaque placeholder de manière asynchrone
434
- let result = template;
435
- for (const [match, key] of placeholders) {
436
- const value = await findValue(key);
437
- const replacement = value !== undefined
438
- ? (value === null ? 'null' : typeof value === 'object' ? JSON.stringify(value) : String(value))
439
- : match;
440
- result = result.replace(match, replacement);
441
- }
442
-
443
- return result;
444
- }
package/src/migrate.js CHANGED
@@ -24,7 +24,7 @@ engine.start(port, async () => {
24
24
  const target = process.argv[3]; // Le nom du fichier de migration cible
25
25
 
26
26
  try {
27
- const db = MongoDatabase;
27
+ const db = MongoDatabase();
28
28
  const { executedNames, allMigrationFiles } = await getMigrationStatus(db);
29
29
 
30
30
  switch (command) {
@@ -8,7 +8,7 @@ import {Config} from "../../config.js";
8
8
  import { Event } from "../../events.js";
9
9
  import rateLimit from "express-rate-limit";
10
10
  import {generateLimiter} from "../user.js";
11
- import {assistantConfig, maxAIReflectiveSteps} from "../../constants.js";
11
+ import {maxAIReflectiveSteps} from "../../constants.js";
12
12
  import i18n from "../../i18n.js";
13
13
  import {parseSafeJSON} from "../../core.js";
14
14
 
@@ -80,16 +80,15 @@ REGLE FONDATRICE : suis les règles et ne dévie pas du chemin.
80
80
  STYLE UTILISE : apporte l'information au plus rapide, sans détours, ni sollicitation à l'utilisateur, ou à des tiers.
81
81
 
82
82
  FORMAT DE RÉPONSE OBLIGATOIRE :
83
- Ta réponse DOIT être un objet JSON unique OU un tableau d'objets JSON. Chaque objet doit contenir exactement 2 champs :
83
+ Ta réponse DOIT être un tableau d'objets JSON (Même pour une seule action). Chaque objet doit contenir exactement 2 champs :
84
84
  1. "action" (string)
85
85
  2. "params" (object)
86
86
 
87
- - Pour une action simple, retourne un objet : { "action": "...", "params": {...} }
88
- - Pour enchaîner plusieurs actions, retourne un tableau :
89
- [
90
- { "action": "search_models", "params": {"query": "..."} },
91
- { "action": "displayMessage", "params": { "message": "Je cherche les modèles..." } }
92
- ]
87
+ Exemple :
88
+ [
89
+ { "action": "search_models", "params": {"query": "..."} },
90
+ { "action": "displayMessage", "params": { "message": "Je cherche les modèles..." } }
91
+ ]
93
92
 
94
93
  Tu as accès aux outils et actions suivants.
95
94
 
@@ -529,7 +528,7 @@ export async function handleChatRequest(params, user, sendEvent = null) {
529
528
  }
530
529
 
531
530
  const toolResults = [];
532
- let finalActionResult = null;
531
+ const finalActionResults = [];
533
532
 
534
533
  for (const command of commands) {
535
534
  logger.debug(`[Assistant] Action décidée par l'IA: ${command.action}`, command);
@@ -554,27 +553,31 @@ export async function handleChatRequest(params, user, sendEvent = null) {
554
553
  const res = await Event.Trigger('OnChatAction', 'event', 'user', action, parsedParams, command, llmOptions, user, params);
555
554
 
556
555
  if (res) {
557
- // On a trouvé une action finale. On la stocke et on arrête de chercher.
558
- finalActionResult = res;
559
- break; // Sort de la boucle des commandes
556
+ if (res.isToolResult) {
557
+ toolResults.push({ action, result: res.toolResult || res.displayMessage });
558
+ } else {
559
+ // On a trouvé une action finale. On la stocke.
560
+ finalActionResults.push(res);
561
+ }
560
562
  } else {
561
563
  // Si l'action n'est reconnue par aucune des logiques ci-dessus
562
564
  logger.warn(`[Assistant] Action non reconnue reçue de l'IA: ${action}`);
563
- finalActionResult = {
565
+ finalActionResults.push({
564
566
  success: true,
565
567
  displayMessage: i18n.t('assistant.unknownAction', "Désolé, je ne comprends pas la commande '{{action}}'.", { action })
566
- };
567
- break; // Sortir de la boucle des commandes
568
+ });
568
569
  }
569
570
  }
570
571
 
571
- // Si une action finale a été exécutée, on retourne son résultat.
572
- if (finalActionResult) {
572
+ // Si une ou plusieurs actions finales ont été exécutées, on retourne le résultat.
573
+ if (finalActionResults.length > 0) {
574
+ // Rétrocompatibilité : si un seul résultat, on renvoie l'objet directement. Sinon, on renvoie un tableau 'results'.
575
+ const response = finalActionResults.length === 1 ? finalActionResults[0] : { success: true, results: finalActionResults };
573
576
  if (sendEvent) {
574
- sendEvent('final_result', finalActionResult);
577
+ sendEvent('final_result', response);
575
578
  return;
576
579
  }
577
- return finalActionResult;
580
+ return response;
578
581
  }
579
582
 
580
583
  // Si on a uniquement des résultats d'outils, on les ajoute à l'historique et on continue la boucle.
@@ -673,7 +676,14 @@ async function handleFinalChatAction(action, params, parsedResponse, llmOptions,
673
676
 
674
677
  // Actions nécessitant une confirmation de l'utilisateur
675
678
  if (['post', 'update', 'delete'].includes(action)) {
676
- return { success: true, displayMessage: i18n.t('assistant.confirmActionPrompt', "Veuillez confirmer l'action suivante :"), confirmationRequest: parsedResponse };
679
+ return {
680
+ success: true,
681
+ displayMessage: i18n.t('assistant.confirmActionPrompt', "Veuillez confirmer l'action suivante :"),
682
+ confirmationRequest: parsedResponse,
683
+ model: params.model,
684
+ filter: params.filter,
685
+ data: params.data
686
+ };
677
687
  }
678
688
 
679
689
  // Action finale pour afficher un message
@@ -8,7 +8,7 @@ import {downloadFromS3, getUserS3Config, listS3Backups, uploadToS3} from "../buc
8
8
  import fs from "node:fs";
9
9
  import {modelsCache, runCryptoWorkerTask} from "./data.core.js";
10
10
  import * as tar from "tar";
11
- import {getCollection, getUserCollectionName, modelsCollection} from "../mongodb.js";
11
+ import {getCollection, getDatabase, getUserCollectionName, modelsCollection} from "../mongodb.js";
12
12
  import {removeFile} from "../file.js";
13
13
  import {cancelAlerts, scheduleAlerts} from "./data.scheduling.js";
14
14
  import {dbName} from "../../constants.js";
@@ -136,7 +136,7 @@ export const loadFromDump = async (user, options = {}) => {
136
136
  await tar.extract({file: backupFilePath, gzip: true, C: tmpRestoreDir, sync: true});
137
137
 
138
138
  // ... (Cleaning logic: deleteMany, removeFile, cancelAlerts) ...
139
- const datasCollection = getCollection("datas");
139
+ const datasCollection = getCollection(Config.Get('dataCollection', 'datas'));
140
140
  if (modelsOnly) {
141
141
  await modelsCollection.deleteMany({_user: user.username});
142
142
  } else {
@@ -169,7 +169,7 @@ export const loadFromDump = async (user, options = {}) => {
169
169
  args.push('--nsInclude', `${d}.models`);
170
170
  } else {
171
171
  // mongorestore accepte plusieurs fois l'option --nsInclude
172
- args.push('--nsInclude', `${d}.datas`);
172
+ args.push('--nsInclude', `${d}.${Config.Get('dataCollection', 'datas')}`);
173
173
  args.push('--nsInclude', `${d}.models`);
174
174
  }
175
175
  // Le répertoire source est le dernier argument
@@ -245,7 +245,7 @@ export const dumpUserData = async (user) => {
245
245
 
246
246
 
247
247
  const d = Config.Get('dbName', dbName);
248
- const collections = await MongoDatabase.listCollections().toArray();
248
+ const collections = await getDatabase().listCollections().toArray();
249
249
  for (const collection of collections) {
250
250
  const collsToBackup = [await getUserCollectionName(user), 'models'];
251
251
  if (collsToBackup.includes(collection.name)) {
@@ -2,7 +2,7 @@ import {Logger} from "../../gameObject.js";
2
2
  import {mkdir} from 'node:fs/promises';
3
3
  import {onInit as historyInit} from "./data.history.js";
4
4
  import {isDemoUser, isLocalUser} from "../../data.js";
5
- import {install, maxAlertsPerUser, maxRequestData, storageSafetyMargin} from "../../constants.js";
5
+ import {install, storageSafetyMargin} from "../../constants.js";
6
6
  import {createCollection, getCollection} from "../mongodb.js";
7
7
  import path from "node:path";
8
8
  import {isGUID, sequential} from "../../core.js";
@@ -10,19 +10,19 @@ import {Event} from "../../events.js";
10
10
  import fs from "node:fs";
11
11
  import schedule from "node-schedule";
12
12
  import {middleware} from "../../middlewares/middleware-mongodb.js";
13
- import i18n from "../../i18n.js";
14
13
  import checkDiskSpace from "check-disk-space";
15
14
  import {removeFile} from "../file.js";
16
15
  import {hasPermission} from "../user.js";
17
- import {profiles} from "../../../client/src/constants.js";
16
+ import { onInit as userInit } from '../user.js';
18
17
  import {registerRoutes} from "./data.routes.js";
19
18
  import {mongoDBWhitelist} from "./data.core.js";
20
19
  import {onInit as relationsInit} from "./data.relations.js";
21
- import {validateField, onInit as validationInit} from "./data.validation.js";
20
+ import { onInit as validationInit} from "./data.validation.js";
22
21
  import {cancelAlerts, scheduleAlerts, onInit as scheduleInit} from "./data.scheduling.js";
23
22
  import {deleteData, installPack, onInit as operationsInit} from "./data.operations.js";
24
23
  import {jobDumpUserData, onInit as backupInit} from "./data.backup.js";
25
24
  import {Config} from "../../config.js";
25
+ import {profiles} from "../../profiles.js";
26
26
 
27
27
  let engine;
28
28
  let logger;
@@ -57,7 +57,7 @@ export async function onInit(defaultEngine) {
57
57
 
58
58
  const i = Config.Get('install', install);
59
59
  if( i ) {
60
- datasCollection = await createCollection("datas");
60
+ datasCollection = await createCollection(Config.Get('dataCollection', 'datas'));
61
61
  historyCollection = await createCollection("history");
62
62
  filesCollection = await createCollection("files");
63
63
  packsCollection = await createCollection("packs");
@@ -127,7 +127,7 @@ export async function onInit(defaultEngine) {
127
127
 
128
128
  }else {
129
129
  modelsCollection = getCollection("models");
130
- datasCollection = getCollection("datas");
130
+ datasCollection = getCollection(Config.Get('dataCollection','datas'));
131
131
  filesCollection = getCollection("files");
132
132
  packsCollection = getCollection("packs");
133
133
  historyCollection = getCollection("history");
@@ -139,6 +139,7 @@ export async function onInit(defaultEngine) {
139
139
  validationInit(defaultEngine);
140
140
  relationsInit(defaultEngine);
141
141
  scheduleInit(defaultEngine);
142
+ userInit(defaultEngine);
142
143
  operationsInit(defaultEngine);
143
144
 
144
145
 
@@ -255,7 +256,7 @@ export async function handleDemoInitialization(req, res) {
255
256
 
256
257
  try {
257
258
  // 1. Nettoyage de l'environnement (inchangé)
258
- const datasCollection = getCollection("datas");
259
+ const datasCollection = getCollection(Config.Get('dataCollection',"datas"));
259
260
  const modelsCollection = getCollection("models");
260
261
  const filesCollection = getCollection("files");
261
262