data-primals-engine 1.0.11 → 1.1.1

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 (167) hide show
  1. package/README.md +36 -0
  2. package/client/README.md +116 -0
  3. package/client/index.html +31 -0
  4. package/client/index.js +25 -0
  5. package/client/package-lock.json +11664 -0
  6. package/client/package.json +70 -0
  7. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  8. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  9. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  10. package/client/public/doc/API.postman_collection.json +214 -0
  11. package/client/public/github.svg +1 -0
  12. package/client/public/profilCompany.jpg +0 -0
  13. package/client/public/profilDeveloper.jpg +0 -0
  14. package/client/public/profilEngineer.jpg +0 -0
  15. package/client/public/profilPersonal.jpg +0 -0
  16. package/client/public/profils .psd +0 -0
  17. package/client/public/react.svg +9 -0
  18. package/client/src/APIInfo.jsx +328 -0
  19. package/client/src/AddWidgetTypeModal.jsx +44 -0
  20. package/client/src/AddWidgetTypeModal.scss +87 -0
  21. package/client/src/App.css +42 -0
  22. package/client/src/App.jsx +588 -0
  23. package/client/src/App.scss +1466 -0
  24. package/client/src/AssistantChat.jsx +280 -0
  25. package/client/src/AssistantChat.scss +229 -0
  26. package/client/src/Button.jsx +12 -0
  27. package/client/src/Button.scss +197 -0
  28. package/client/src/CalculationBuilder.jsx +338 -0
  29. package/client/src/CalculationStepRow.jsx +199 -0
  30. package/client/src/CalendarConfigModal.jsx +49 -0
  31. package/client/src/ChartConfigModal.jsx +402 -0
  32. package/client/src/ConditionBuilder.jsx +622 -0
  33. package/client/src/ConditionBuilder.scss +672 -0
  34. package/client/src/ConditionBuilder2.jsx +757 -0
  35. package/client/src/ContentView.jsx +274 -0
  36. package/client/src/CronBuilder.jsx +126 -0
  37. package/client/src/CronBuilder.scss +128 -0
  38. package/client/src/CronPartBuilder.jsx +150 -0
  39. package/client/src/Dashboard.jsx +210 -0
  40. package/client/src/Dashboard.scss +445 -0
  41. package/client/src/DashboardChart.jsx +362 -0
  42. package/client/src/DashboardFlexViewItem.jsx +129 -0
  43. package/client/src/DashboardView.jsx +554 -0
  44. package/client/src/DataChart.jsx +349 -0
  45. package/client/src/DataEditor.jsx +556 -0
  46. package/client/src/DataLayout.jsx +709 -0
  47. package/client/src/DataTable.jsx +996 -0
  48. package/client/src/DataTable.scss +73 -0
  49. package/client/src/Dialog.jsx +90 -0
  50. package/client/src/Dialog.scss +117 -0
  51. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  52. package/client/src/DocumentationPageLayout.jsx +93 -0
  53. package/client/src/DocumentationPageLayout.scss +262 -0
  54. package/client/src/Draggable.jsx +222 -0
  55. package/client/src/Draggable.scss +49 -0
  56. package/client/src/ExportDialog.jsx +172 -0
  57. package/client/src/Field.jsx +1558 -0
  58. package/client/src/FlexBuilder.jsx +269 -0
  59. package/client/src/FlexBuilder.scss +226 -0
  60. package/client/src/FlexBuilderControls.jsx +170 -0
  61. package/client/src/FlexBuilderModal.jsx +75 -0
  62. package/client/src/FlexBuilderModal.scss +109 -0
  63. package/client/src/FlexBuilderPreview.jsx +48 -0
  64. package/client/src/FlexBuilderUtils.js +84 -0
  65. package/client/src/FlexDataRenderer.jsx +170 -0
  66. package/client/src/FlexDataRenderer.scss +80 -0
  67. package/client/src/FlexNode.jsx +290 -0
  68. package/client/src/FlexTreeUtils.js +129 -0
  69. package/client/src/HiddenableCell.jsx +46 -0
  70. package/client/src/HiddenableCell.scss +36 -0
  71. package/client/src/KPIDialog.jsx +39 -0
  72. package/client/src/KPIWidget.jsx +139 -0
  73. package/client/src/KanbanCard.jsx +92 -0
  74. package/client/src/KanbanColumn.jsx +29 -0
  75. package/client/src/KanbanConfigModal.jsx +115 -0
  76. package/client/src/KanbanView.jsx +113 -0
  77. package/client/src/KanbanView.scss +107 -0
  78. package/client/src/MessageRotator.jsx +77 -0
  79. package/client/src/MessageRotator.scss +14 -0
  80. package/client/src/ModelCreator.jsx +640 -0
  81. package/client/src/ModelCreator.scss +154 -0
  82. package/client/src/ModelCreatorField.jsx +810 -0
  83. package/client/src/ModelImporter.jsx +120 -0
  84. package/client/src/ModelList.jsx +205 -0
  85. package/client/src/Notification.jsx +137 -0
  86. package/client/src/Notification.scss +126 -0
  87. package/client/src/NotificationProvider.jsx +73 -0
  88. package/client/src/PackGallery.jsx +211 -0
  89. package/client/src/PackGallery.scss +156 -0
  90. package/client/src/Pagination.jsx +141 -0
  91. package/client/src/RTE.jsx +415 -0
  92. package/client/src/RTETrans.jsx +103 -0
  93. package/client/src/RelationField.jsx +247 -0
  94. package/client/src/RelationValue.jsx +230 -0
  95. package/client/src/RestoreConfirmationModal.jsx +183 -0
  96. package/client/src/RestoreDialog.jsx +134 -0
  97. package/client/src/RestoreDialog.scss +67 -0
  98. package/client/src/RichTextEditorModal.jsx +34 -0
  99. package/client/src/RichTextEditorModal.scss +54 -0
  100. package/client/src/TourSpotlight.jsx +234 -0
  101. package/client/src/TourSpotlight.scss +100 -0
  102. package/client/src/TutorialsMenu.jsx +83 -0
  103. package/client/src/TutorialsMenu.scss +217 -0
  104. package/client/src/TutorialsRewardModal.jsx +65 -0
  105. package/client/src/Utils.jsx +78 -0
  106. package/client/src/ViewSwitcher.jsx +54 -0
  107. package/client/src/ViewSwitcher.scss +74 -0
  108. package/client/src/Webpage.jsx +69 -0
  109. package/client/src/_variables.scss +21 -0
  110. package/client/src/assets/react.svg +1 -0
  111. package/client/src/constants.js +68 -0
  112. package/client/src/contexts/AuthContext.jsx +28 -0
  113. package/client/src/contexts/ModelContext.jsx +328 -0
  114. package/client/src/contexts/UIContext.jsx +52 -0
  115. package/client/src/core/data.js +35 -0
  116. package/client/src/entry-client.jsx +15 -0
  117. package/client/src/entry-server.jsx +13 -0
  118. package/client/src/filter.js +222 -0
  119. package/client/src/hooks/data.js +101 -0
  120. package/client/src/hooks/useDebounce.js +20 -0
  121. package/client/src/hooks/useDragAndDrop.js +59 -0
  122. package/client/src/hooks/useLocalStorage.js +100 -0
  123. package/client/src/hooks/useMount.js +42 -0
  124. package/client/src/hooks/useTutorials.jsx +227 -0
  125. package/client/src/hooks/useWindowSize.js +24 -0
  126. package/client/src/i18n.js +47 -0
  127. package/client/src/index.css +90 -0
  128. package/client/src/index.js +10 -0
  129. package/client/src/main.jsx +11 -0
  130. package/client/src/translations.js +15940 -0
  131. package/client/src/tutorials.js +112 -0
  132. package/client/vite.config.js +30 -0
  133. package/eslint.config.js +50 -0
  134. package/package.json +51 -27
  135. package/server.js +9 -0
  136. package/src/constants.js +12 -23
  137. package/src/core.js +9 -8
  138. package/src/data.js +81 -80
  139. package/src/defaultModels.js +114 -115
  140. package/src/email.js +8 -9
  141. package/src/engine.js +5 -5
  142. package/src/events.js +1 -2
  143. package/src/i18n.js +212 -211
  144. package/src/middlewares/middleware-mongodb.js +94 -93
  145. package/src/migrate.js +24 -24
  146. package/src/modules/assistant.js +55 -55
  147. package/src/modules/bucket.js +10 -9
  148. package/src/modules/data.js +387 -357
  149. package/src/modules/file.js +3 -6
  150. package/src/modules/mongodb.js +6 -7
  151. package/src/modules/user.js +24 -47
  152. package/src/modules/workflow.js +139 -73
  153. package/src/openai.jobs.js +2 -2
  154. package/src/packs.js +30 -31
  155. package/src/providers.js +101 -3
  156. package/src/setenv.js +1 -1
  157. package/src/user.js +0 -17
  158. package/src/workers/crypto-worker.js +21 -20
  159. package/src/workers/import-export-worker.js +18 -18
  160. package/test/data.backup.integration.test.js +7 -12
  161. package/test/data.integration.test.js +27 -25
  162. package/test/globalSetup.js +2 -2
  163. package/test/import_export.integration.test.js +6 -6
  164. package/test/model.integration.test.js +21 -22
  165. package/test/workflow.integration.test.js +2 -5
  166. package/test/workflow.robustness.test.js +6 -13
  167. package/vitest.config.js +2 -2
@@ -3,7 +3,6 @@
3
3
  import {maxPrivateFileSize, megabytes} from "../constants.js";
4
4
  import {isLocalUser} from "../data.js";
5
5
  import i18n from "data-primals-engine/i18n";
6
- import {getUserStorageLimit} from "../user.js";
7
6
  import {getCollection} from "./mongodb.js";
8
7
  import {getFileExtension, isGUID, uuidv4} from "../core.js";
9
8
  import path from "node:path";
@@ -14,9 +13,6 @@ import crypto from "node:crypto";
14
13
  import * as tar from "tar";
15
14
  import {promisify} from "node:util";
16
15
  import {calculateTotalUserStorageUsage, hasPermission} from "./user.js";
17
- import {Logger} from "../gameObject.js";
18
- import YAML from "yaml";
19
- import swaggerUi from "swagger-ui-express";
20
16
 
21
17
  const pbkdf2Async = promisify(crypto.pbkdf2);
22
18
 
@@ -56,7 +52,7 @@ export const addFile = async (file, user) => {
56
52
 
57
53
  const incomingDataSize = file.size;
58
54
 
59
- const userStorageLimit = getUserStorageLimit(user);
55
+ const userStorageLimit = await engine.userProvider.getUserStorageLimit(user);
60
56
  const currentStorageUsage = await calculateTotalUserStorageUsage(user);
61
57
 
62
58
  if (currentStorageUsage + incomingDataSize > userStorageLimit) {
@@ -194,6 +190,7 @@ export async function decryptFile(filePath, password) {
194
190
  }
195
191
  }
196
192
 
193
+ let engine;
197
194
  export async function onInit(defaultEngine) {
198
-
195
+ engine = defaultEngine;
199
196
  }
@@ -1,9 +1,7 @@
1
1
 
2
2
  import process from "process";
3
- import {MongoClient as InternalMongoClient} from "mongodb";
4
3
  import {Logger} from "../gameObject.js";
5
- import {MongoClient, MongoDatabase} from "../engine.js";
6
- import * as tls from "node:tls";
4
+ import {MongoDatabase} from "../engine.js";
7
5
  import fs from "node:fs";
8
6
 
9
7
  export let modelsCollection, datasCollection, filesCollection, packsCollection;
@@ -46,14 +44,15 @@ export const getCollection = (str) => {
46
44
 
47
45
 
48
46
  // New function to determine the collection name for a user
49
- export const getUserCollectionName = (user) => {
50
- return user?.userPlan === 'premium' ? `datas_${user.username}` : 'datas';
47
+ export const getUserCollectionName = async (user) => {
48
+ const feat = await engine.userProvider.hasFeature(user, 'indexes');
49
+ return feat ? `datas_${user.username}` : 'datas';
51
50
  };
52
51
 
53
52
 
54
53
  // Modify existing functions to use the correct collection
55
- export const getCollectionForUser = (user) => {
56
- const collectionName = getUserCollectionName(user);
54
+ export const getCollectionForUser = async (user) => {
55
+ const collectionName = await getUserCollectionName(user);
57
56
  return getCollection(collectionName);
58
57
  };
59
58
 
@@ -1,7 +1,6 @@
1
1
  import i18n from "data-primals-engine/i18n";
2
- import {MongoClient, MongoDatabase} from "../engine.js";
2
+ import {MongoDatabase} from "../engine.js";
3
3
  import {getCollection, getCollectionForUser, getUserCollectionName} from "./mongodb.js";
4
- import {dbName, plans} from "../constants.js";
5
4
  import {isLocalUser} from "../data.js";
6
5
  import {ObjectId} from "mongodb";
7
6
  import {getAPILang} from "./data.js";
@@ -20,10 +19,10 @@ export const userInitiator = async (req, res, next) => {
20
19
  // set current lang for user
21
20
  i18n.changeLanguage(lang);
22
21
 
23
- if (req.me.userPlan === 'premium') {
22
+ if (await engine.userProvider.hasFeature(req.me, 'indexes')) {
24
23
  const collections = await MongoDatabase.listCollections().toArray();
25
24
  const collectionNames = collections.map(c => c.name);
26
- const coll = getUserCollectionName(req.me);
25
+ const coll = await getUserCollectionName(req.me);
27
26
  if (collectionNames.includes(coll)) {
28
27
  const collection = await MongoDatabase.createCollection(coll);
29
28
  const indexes = await collection.indexes();
@@ -83,33 +82,11 @@ export const generateLimiter = rateLimit({
83
82
  }
84
83
  });
85
84
 
86
- const freeSearchLimiter = rateLimit({
87
- windowMs: 1000 * 3600,
88
- limit: plans.free.requestLimitPerHour,
89
- standardHeaders: true,
90
- legacyHeaders: false,
91
- });
92
- const premiumSearchLimiter = rateLimit({
93
- windowMs: 1000 * 3600,
94
- limit: plans.premium.requestLimitPerHour,
95
- standardHeaders: true,
96
- legacyHeaders: false,
97
- });
98
-
99
- export const myFreePremiumAnonymousLimiter = async function (req, res, next) {
100
- const user = req.me;
101
- if (user?.userPlan === "premium") {
102
- return premiumSearchLimiter(req, res, next);
103
- }
104
- return freeSearchLimiter(req, res, next);
105
- };
106
85
 
107
86
  let logger,engine;
108
87
  export async function onInit(defaultEngine) {
109
88
  engine = defaultEngine;
110
89
  logger = engine.getComponent(Logger);
111
-
112
-
113
90
  }
114
91
 
115
92
  export async function hasPermission(permissionNames, user) {
@@ -119,7 +96,7 @@ export async function hasPermission(permissionNames, user) {
119
96
  try {
120
97
  // Si on a une string on le transforme en tableau.
121
98
  const permissionNamesArray = Array.isArray(permissionNames) ? permissionNames : [permissionNames];
122
- const collection = getCollectionForUser(user);
99
+ const collection = await getCollectionForUser(user);
123
100
 
124
101
  const job = [
125
102
  {
@@ -131,39 +108,39 @@ export async function hasPermission(permissionNames, user) {
131
108
  $match: {
132
109
  $expr: {
133
110
  $and: [
134
- { $in: ['$_id', '$$rolesIds'] },
135
- ],
136
- },
137
- },
111
+ { $in: ['$_id', '$$rolesIds'] }
112
+ ]
113
+ }
114
+ }
138
115
  },
139
116
  {
140
117
  $lookup: {
141
118
  from: 'datas',
142
119
  let: { rolePermissions: {
143
- "$map": {
144
- "input": "$permissions",
145
- "in": { "$toObjectId": "$$this" }
146
- }
147
- } },
120
+ "$map": {
121
+ "input": "$permissions",
122
+ "in": { "$toObjectId": "$$this" }
123
+ }
124
+ } },
148
125
  pipeline: [
149
126
  {
150
127
  $match: {
151
128
  $expr: {
152
129
  $and: [
153
- { $in: ['$_id', '$$rolePermissions'] },
154
- ],
155
- },
156
- },
130
+ { $in: ['$_id', '$$rolePermissions'] }
131
+ ]
132
+ }
133
+ }
157
134
  },
158
- { $limit: 1 },
135
+ { $limit: 1 }
159
136
  ],
160
- as: 'permissions',
161
- },
137
+ as: 'permissions'
138
+ }
162
139
  },
163
- { $unwind: { path: '$permissions', preserveNullAndEmptyArrays: true } },
140
+ { $unwind: { path: '$permissions', preserveNullAndEmptyArrays: true } }
164
141
  ],
165
- as: 'roles',
166
- },
142
+ as: 'roles'
143
+ }
167
144
  },
168
145
  { $unwind: { path: '$roles', preserveNullAndEmptyArrays: true } },
169
146
  { $match: { 'roles.permissions.name': { $in: permissionNamesArray } } }, // Match if permissions.name in array
@@ -186,7 +163,7 @@ export async function hasPermission(permissionNames, user) {
186
163
  */
187
164
  export async function calculateTotalUserStorageUsage(user) {
188
165
  const userId = user._user || user.username;
189
- const datasCollection = getCollectionForUser(user);
166
+ const datasCollection = await getCollectionForUser(user);
190
167
  const filesCollection = getCollection("files");
191
168
 
192
169
  // Pipeline pour calculer la taille des documents de données
@@ -4,17 +4,16 @@ import schedule from "node-schedule";
4
4
  import {ObjectId} from "mongodb";
5
5
  import crypto from "node:crypto";
6
6
 
7
+ import {VM, VMScript} from 'vm2';
7
8
  import {Logger} from "../gameObject.js";
8
- import {deleteData, editData, insertData, patchData, searchData} from "./data.js";
9
+ import {deleteData, insertData, patchData, searchData} from "./data.js";
9
10
  import {maxExecutionsByStep, maxWorkflowSteps} from "../constants.js";
10
- import { ChatOpenAI } from "@langchain/openai";
11
- import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
12
- import { ChatPromptTemplate } from "@langchain/core/prompts";
11
+ import {ChatOpenAI} from "@langchain/openai";
12
+ import {ChatGoogleGenerativeAI} from "@langchain/google-genai";
13
+ import {ChatPromptTemplate} from "@langchain/core/prompts";
13
14
  import i18n from "data-primals-engine/i18n";
14
15
  import {sendEmail} from "../email.js";
15
16
 
16
- // 1. ADD THIS IMPORT AT THE TOP OF THE FILE
17
- // This allows the module to call its own exported functions.
18
17
  import * as workflowModule from './workflow.js';
19
18
 
20
19
  let logger = null;
@@ -131,7 +130,7 @@ export async function scheduleWorkflowTriggers() {
131
130
  // Trouver tous les workflows actifs avec une cronExpression définie
132
131
  const workflowsToSchedule = await datasCollection.find({
133
132
  _model: 'workflowTrigger',
134
- cronExpression: { $exists: true, $ne: "" },
133
+ cronExpression: { $exists: true, $ne: "" }
135
134
  // Ajoutez d'autres conditions si nécessaire (ex: active: true)
136
135
  }).toArray();
137
136
 
@@ -181,6 +180,69 @@ export async function scheduleWorkflowTriggers() {
181
180
  }
182
181
 
183
182
 
183
+ /* Remplacement de la fonction executeSafeJavascript
184
+ async function executeWithIsolatedVm(actionDef, context) {
185
+ const code = actionDef.script;
186
+ const isolate = new ivm.Isolate({ memoryLimit: 128 }); // Limite de 128MB de mémoire
187
+
188
+ try {
189
+ const vmContext = await isolate.createContext();
190
+ const jail = vmContext.global;
191
+
192
+ // On expose une fonction 'log' sécurisée à l'intérieur de la VM.
193
+ // C'est une référence, pas la fonction elle-même.
194
+ await jail.set('log', new ivm.Reference(function(...args) {
195
+ // On peut préfixer les logs pour savoir qu'ils viennent de la VM
196
+ logger.info('[VM Script Log]', ...args);
197
+ }));
198
+
199
+ // On injecte les données du contexte. Elles sont COPIÉES, pas référencées.
200
+ // C'est une caractéristique de sécurité clé.
201
+ await jail.set('contextData', new ivm.ExternalCopy(context).copyInto());
202
+ // On peut aussi exposer des fonctions spécifiques de votre application
203
+ // await jail.set('myApiFunction', new ivm.Reference(async (params) => { ... }));
204
+
205
+ // On compile le script
206
+ const script = await isolate.compileScript(code);
207
+
208
+ // On exécute le script avec un timeout
209
+ const result = await script.run(vmContext, { timeout: 1000 });
210
+
211
+ // Le résultat est une référence, on le copie pour l'utiliser dans notre contexte principal.
212
+ return result;
213
+
214
+ } catch (error) {
215
+ logger.error("Error executing script with isolated-vm:", error.stack);
216
+ // On propage une erreur propre
217
+ throw new Error(`Script execution failed: ${error.message}`);
218
+ } finally {
219
+ // TRÈS IMPORTANT : Toujours libérer l'isolate pour éviter les fuites de mémoire.
220
+ if (isolate && !isolate.isDisposed) {
221
+ isolate.dispose();
222
+ }
223
+ }
224
+ }
225
+ REQUIRES NODE >=20
226
+ */
227
+ async function executeSafeJavascript(actionDef, context) {
228
+ const code = actionDef.script;
229
+ const vm = new VM({
230
+ timeout: 1000, // Time out after 1 second
231
+ sandbox: context, // Pass the context object
232
+ console: 'redirect', // Redirect console output
233
+ require: false, // Disable require
234
+ wasm: false // disable WebAssembly
235
+ });
236
+
237
+ try {
238
+ const script = new VMScript(code);
239
+ return vm.run(script);
240
+ } catch (error) {
241
+ console.error("Error executing script:", error);
242
+ throw error; // or return an error object
243
+ }
244
+ }
245
+
184
246
  /**
185
247
  * Handles the 'Webhook' workflow action.
186
248
  * Sends an HTTP request to a specified URL with substituted data using native fetch.
@@ -270,7 +332,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
270
332
  // 4. Prepare Fetch Options
271
333
  const fetchOptions = {
272
334
  method: method.toUpperCase(),
273
- headers: headersObject, // Native fetch accepts an object directly
335
+ headers: headersObject // Native fetch accepts an object directly
274
336
  };
275
337
 
276
338
  if (bodyObject !== null && ['POST', 'PUT', 'PATCH'].includes(fetchOptions.method)) {
@@ -649,32 +711,36 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
649
711
  try {
650
712
  let result;
651
713
  switch (actionDef.type) {
652
- case 'Log':
653
- logger.info(`[Workflow Log Action] Action: ${actionDef.name}. Contexte:`, contextData);
654
- result = { success: true, message: 'Log action executed successfully.' }; // <--- CORRECTION
655
- break;
656
- case 'Webhook':
657
- result = await handleWebhookAction(actionDef, contextData, user, dbCollection);
658
- break;
659
- case 'CreateData':
660
- result = await handleCreateDataAction(actionDef, contextData, user, dbCollection);
661
- break;
662
- case 'UpdateData':
663
- result = await handleUpdateDataAction(actionDef, contextData, user);
664
- break;
665
- case 'DeleteData':
666
- result = await handleDeleteDataAction(actionDef, contextData, user, dbCollection);
667
- break;
668
- case 'GenerateAIContent':
669
- result = await executeGenerateAIContentAction(actionDef, contextData, user);
670
- break;
671
- case 'SendEmail':
672
- result = await handleSendEmailAction(actionDef, contextData, user);
673
- break;
714
+ case 'Log':
715
+ logger.info(`[Workflow Log Action] Action: ${actionDef.name}. Contexte:`, contextData);
716
+ result = { success: true, message: 'Log action executed successfully.' }; // <--- CORRECTION
717
+ break;
718
+ case 'Webhook':
719
+ result = await handleWebhookAction(actionDef, contextData, user, dbCollection);
720
+ break;
721
+ case 'CreateData':
722
+ result = await handleCreateDataAction(actionDef, contextData, user, dbCollection);
723
+ break;
724
+ case 'UpdateData':
725
+ result = await handleUpdateDataAction(actionDef, contextData, user);
726
+ break;
727
+ case 'DeleteData':
728
+ result = await handleDeleteDataAction(actionDef, contextData, user, dbCollection);
729
+ break;
730
+ case 'GenerateAIContent':
731
+ result = await executeGenerateAIContentAction(actionDef, contextData, user);
732
+ break;
733
+ case 'SendEmail':
734
+ result = await handleSendEmailAction(actionDef, contextData, user);
735
+ break;
736
+ case 'ExecuteScript':
737
+ result = await executeSafeJavascript(actionDef, contextData);
738
+ break;
739
+
674
740
  // ... autres cases à venir ...
675
- default:
676
- logger.error(`[executeStepAction] Unknown action type: ${actionDef.type}`);
677
- return { success: false, message: `Unknown action type: ${actionDef.type}` };
741
+ default:
742
+ logger.error(`[executeStepAction] Unknown action type: ${actionDef.type}`);
743
+ return { success: false, message: `Unknown action type: ${actionDef.type}` };
678
744
  }
679
745
  return result;
680
746
  } catch (error) {
@@ -749,7 +815,7 @@ async function resolvePathValue(pathString, initialContext, user) {
749
815
 
750
816
  let currentModelName = initialContext._model;
751
817
  let currentDocId = new ObjectId(initialContext._id);
752
- const collection = getCollectionForUser(user);
818
+ const collection = await getCollectionForUser(user);
753
819
 
754
820
  // Construire le pipeline d'agrégation
755
821
  const pipeline = [
@@ -862,7 +928,7 @@ export async function substituteVariables(template, contextData, user) {
862
928
  // --- À partir d'ici, nous savons que `template` est une chaîne de caractères ---
863
929
 
864
930
  // 4. Construire le contexte complet pour la substitution
865
- const dbCollection = getCollectionForUser(user);
931
+ const dbCollection = await getCollectionForUser(user);
866
932
  const userEnvVars = await dbCollection.find({ _model: 'env', _user: user.username }).toArray();
867
933
  const userEnv = userEnvVars.reduce((acc, v) => ({ ...acc, [v.name]: v.value }), {});
868
934
 
@@ -972,7 +1038,7 @@ export async function triggerWorkflows(triggerData, user, eventType) {
972
1038
  console.log(`[Workflow Trigger] Event: ${eventType}, Model: ${targetModelName}${dataId ? `, Data ID: ${dataId}` : ''}, User: ${user.username}`);
973
1039
 
974
1040
  try {
975
- const dbCollection = getCollectionForUser(user); // Collection des données utilisateur
1041
+ const dbCollection = await getCollectionForUser(user); // Collection des données utilisateur
976
1042
 
977
1043
  // 1. Trouver les WorkflowTriggers pertinents
978
1044
  const workflowTriggers = await dbCollection.find({
@@ -1008,29 +1074,29 @@ export async function triggerWorkflows(triggerData, user, eventType) {
1008
1074
  continue; // Passer au trigger suivant si le filtre est invalide
1009
1075
  }
1010
1076
 
1011
- try {
1012
- const finalFilter = {
1013
- '$and': [
1014
- dataFilterCondition // Applique la condition du trigger
1015
- ]
1016
- };
1017
-
1018
- console.debug(`[Workflow Trigger] Vérification dataFilter pour trigger ${trigger._id} avec filtre combiné:`, JSON.stringify(finalFilter));
1019
-
1020
- // Exécuter la vérification dans la base de données
1021
- // Utilisation de countDocuments pour une vérification rapide
1022
- const matchCount = await searchData({ user, query: { model: targetModelName, filter: finalFilter, limit: 1 } });
1023
-
1024
- if (!matchCount.count) {
1025
- console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter non satisfait par la donnée ${dataId}. WorkflowRun non créé.`);
1026
- continue; // Passer au trigger suivant
1027
- } else {
1028
- console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter satisfait par la donnée ${dataId}.`);
1029
- }
1030
- } catch (filterError) {
1031
- console.error(`[Workflow Trigger] Erreur lors de la conversion ou de l'exécution du dataFilter pour le trigger ${trigger._id}:`, filterError);
1032
- continue; // Ne pas créer en cas d'erreur de filtre
1077
+ try {
1078
+ const finalFilter = {
1079
+ '$and': [
1080
+ dataFilterCondition // Applique la condition du trigger
1081
+ ]
1082
+ };
1083
+
1084
+ console.debug(`[Workflow Trigger] Vérification dataFilter pour trigger ${trigger._id} avec filtre combiné:`, JSON.stringify(finalFilter));
1085
+
1086
+ // Exécuter la vérification dans la base de données
1087
+ // Utilisation de countDocuments pour une vérification rapide
1088
+ const matchCount = await searchData({ user, query: { model: targetModelName, filter: finalFilter, limit: 1 } });
1089
+
1090
+ if (!matchCount.count) {
1091
+ console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter non satisfait par la donnée ${dataId}. WorkflowRun non créé.`);
1092
+ continue; // Passer au trigger suivant
1093
+ } else {
1094
+ console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter satisfait par la donnée ${dataId}.`);
1033
1095
  }
1096
+ } catch (filterError) {
1097
+ console.error(`[Workflow Trigger] Erreur lors de la conversion ou de l'exécution du dataFilter pour le trigger ${trigger._id}:`, filterError);
1098
+ continue; // Ne pas créer en cas d'erreur de filtre
1099
+ }
1034
1100
  } // Fin de la vérification dataFilter
1035
1101
 
1036
1102
  // 4. Si les filtres (eventType, dataFilter) sont passés, créer l'instance workflowRun
@@ -1087,9 +1153,9 @@ export async function triggerWorkflows(triggerData, user, eventType) {
1087
1153
  }
1088
1154
 
1089
1155
  return new Promise((resolve) => setTimeout(async () => {
1090
- await trigger(triggerData, user, eventType);
1091
- resolve();
1092
- }, 0)
1156
+ await trigger(triggerData, user, eventType);
1157
+ resolve();
1158
+ }, 0)
1093
1159
  );
1094
1160
  }
1095
1161
 
@@ -1105,7 +1171,7 @@ export async function triggerWorkflows(triggerData, user, eventType) {
1105
1171
  // C:/Dev/hackersonline-engine/server/src/modules/workflow.js
1106
1172
 
1107
1173
  export async function processWorkflowRun(workflowRunId, user) {
1108
- const dbCollection = getCollectionForUser(user);
1174
+ const dbCollection = await getCollectionForUser(user);
1109
1175
  const runId = typeof workflowRunId === 'string' ? new ObjectId(workflowRunId) : workflowRunId;
1110
1176
 
1111
1177
  logger.info(`[processWorkflowRun] Starting processing for workflowRun ID: ${runId}`);
@@ -1290,7 +1356,7 @@ async function executeGenerateAIContentAction(action, context, user) {
1290
1356
 
1291
1357
  const providers = {
1292
1358
  "OpenAI" : "OPENAI_API_KEY",
1293
- "Google": "GOOGLE_API_KEY",
1359
+ "Google": "GOOGLE_API_KEY"
1294
1360
  }
1295
1361
  const envKeyName = providers[aiProvider];
1296
1362
  if( !envKeyName ) {
@@ -1298,7 +1364,7 @@ async function executeGenerateAIContentAction(action, context, user) {
1298
1364
  }
1299
1365
 
1300
1366
  // Cherche d'abord dans les variables d'environnement de l'utilisateur
1301
- const envCollection = getCollectionForUser(user);
1367
+ const envCollection = await getCollectionForUser(user);
1302
1368
  const userEnvVar = await envCollection.findOne({ _model: 'env', name: envKeyName, _user: user.username });
1303
1369
 
1304
1370
  if (userEnvVar && userEnvVar.value) {
@@ -1319,14 +1385,14 @@ async function executeGenerateAIContentAction(action, context, user) {
1319
1385
  let llm;
1320
1386
  try {
1321
1387
  switch (aiProvider) {
1322
- case 'OpenAI':
1323
- llm = new ChatOpenAI({ apiKey, modelName: aiModel, temperature: 0.7 });
1324
- break;
1325
- case 'GoogleGemini':
1326
- llm = new ChatGoogleGenerativeAI({ apiKey, modelName: aiModel, temperature: 0.7 });
1327
- break;
1328
- default:
1329
- throw new Error(`Fournisseur IA non supporté : ${aiProvider}`);
1388
+ case 'OpenAI':
1389
+ llm = new ChatOpenAI({ apiKey, modelName: aiModel, temperature: 0.7 });
1390
+ break;
1391
+ case 'GoogleGemini':
1392
+ llm = new ChatGoogleGenerativeAI({ apiKey, modelName: aiModel, temperature: 0.7 });
1393
+ break;
1394
+ default:
1395
+ throw new Error(`Fournisseur IA non supporté : ${aiProvider}`);
1330
1396
  }
1331
1397
  } catch (initError) {
1332
1398
  const message = `Échec de l'initialisation du client IA pour ${aiProvider}: ${initError.message}`;
@@ -1413,7 +1479,7 @@ async function handleSendEmailAction(action, triggerData, user) {
1413
1479
  // 3. Préparer les données pour sendEmail
1414
1480
  const emailData = {
1415
1481
  title: rsubject,
1416
- content: rbody,
1482
+ content: rbody
1417
1483
  };
1418
1484
 
1419
1485
  await sendEmail(rto, emailData, smtpConfig, user.lang);
@@ -121,10 +121,10 @@ export const openaiJobModel = async (lang, txt, history, existingModels = []) =>
121
121
  },
122
122
  {
123
123
  role: "user",
124
- content: typeof(txt) === 'string' ? txt.substring(0, 4096) : '',
124
+ content: typeof(txt) === 'string' ? txt.substring(0, 4096) : ''
125
125
  },
126
126
  ...history
127
- ],
127
+ ]
128
128
  });
129
129
 
130
130
  const aiResponse = completion.choices[0].message.content;