data-primals-engine 1.2.6-rc3 → 1.3.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 (40) hide show
  1. package/README.md +38 -2
  2. package/client/package-lock.json +247 -4354
  3. package/client/package.json +16 -15
  4. package/client/src/App.jsx +13 -20
  5. package/client/src/App.scss +1 -0
  6. package/client/src/AssistantChat.jsx +5 -4
  7. package/client/src/DataEditor.jsx +2 -2
  8. package/client/src/DataTable.jsx +47 -3
  9. package/client/src/ExportDialog.jsx +2 -2
  10. package/client/src/Field.jsx +6 -18
  11. package/client/src/KanbanCard.jsx +4 -2
  12. package/client/src/KanbanConfigModal.jsx +5 -7
  13. package/client/src/ModelCreatorField.jsx +9 -9
  14. package/client/src/PackGallery.jsx +89 -9
  15. package/client/src/PackGallery.scss +58 -4
  16. package/client/src/RTETrans.jsx +11 -0
  17. package/client/src/RelationValue.jsx +3 -4
  18. package/client/src/constants.js +1 -1
  19. package/client/src/core/data.js +2 -1
  20. package/client/src/translations.js +80 -0
  21. package/package.json +8 -1
  22. package/server.js +4 -4
  23. package/src/constants.js +6 -0
  24. package/src/defaultModels.js +23 -10
  25. package/src/filter.js +35 -5
  26. package/src/i18n.js +1 -1
  27. package/src/modules/{assistant.js → assistant/assistant.js} +42 -27
  28. package/src/modules/assistant/constants.js +16 -0
  29. package/src/modules/data/data.core.js +1 -3
  30. package/src/modules/data/data.js +4601 -4525
  31. package/src/modules/data/data.routes.js +29 -3
  32. package/src/modules/mongodb.js +3 -1
  33. package/src/modules/user.js +12 -1
  34. package/src/modules/workflow.js +198 -117
  35. package/src/packs.js +1015 -9
  36. package/src/services/index.js +11 -0
  37. package/src/services/stripe.js +141 -0
  38. package/test/data.integration.test.js +66 -3
  39. package/test/workflow.actions.integration.test.js +474 -0
  40. package/test/workflow.integration.test.js +1 -1
@@ -6,8 +6,8 @@ import crypto from "node:crypto";
6
6
  import ivm from 'isolated-vm';
7
7
 
8
8
  import {Logger} from "../gameObject.js";
9
- import {deleteData, insertData, patchData, scheduleAlerts, searchData} from "./data/index.js";
10
- import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps} from "../constants.js";
9
+ import {deleteData, getModel, insertData, patchData, scheduleAlerts, searchData} from "./data/index.js";
10
+ import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps, port} from "../constants.js";
11
11
  import {ChatOpenAI} from "@langchain/openai";
12
12
  import {ChatGoogleGenerativeAI} from "@langchain/google-genai";
13
13
  import {ChatPromptTemplate} from "@langchain/core/prompts";
@@ -16,7 +16,13 @@ import i18n from "../../src/i18n.js";
16
16
  import {sendEmail} from "../email.js";
17
17
 
18
18
  import * as workflowModule from './workflow.js';
19
- import util from "node:util";
19
+ import {isConditionMet} from "../filter.js";
20
+ import { services } from '../services/index.js';
21
+ import {getEnv} from "./user.js";
22
+ import {getHost} from "../constants.js";
23
+ import {providers} from "./assistant/constants.js";
24
+ import {ChatAnthropic} from "@langchain/anthropic";
25
+ import {getAIProvider} from "./assistant/assistant.js";
20
26
 
21
27
  let logger = null;
22
28
  export async function onInit(defaultEngine) {
@@ -25,6 +31,51 @@ export async function onInit(defaultEngine) {
25
31
  await scheduleWorkflowTriggers();
26
32
  }
27
33
 
34
+
35
+
36
+ /**
37
+ * Déclenche un workflow par son nom et lui passe des données de contexte.
38
+ * C'est la fonction clé à exposer aux endpoints pour lancer des processus métier.
39
+ *
40
+ * @param {string} name - Le nom du workflow à exécuter.
41
+ * @param {object} data - Les données à injecter dans context.triggerData.
42
+ * @param {object} user - L'objet utilisateur qui initie l'action.
43
+ * @returns {Promise<{success: boolean, message?: string, runId?: ObjectId}>}
44
+ */
45
+ export async function runWorkflowByName(name, data, user) {
46
+ if (!name) {
47
+ return { success: false, message: "Workflow name is required." };
48
+ }
49
+
50
+ const dbCollection = await getCollectionForUser(user);
51
+
52
+ // 1. Trouver la définition du workflow par son nom
53
+ const workflowDefinition = await dbCollection.findOne({ _model: 'workflow', name });
54
+
55
+ if (!workflowDefinition) {
56
+ const msg = `Workflow with name "${name}" not found.`;
57
+ logger.error(`[runWorkflowByName] ${msg}`);
58
+ return { success: false, message: msg };
59
+ }
60
+
61
+ // 2. Créer le document workflowRun
62
+ const workflowRunData = {
63
+ _model: 'workflowRun',
64
+ _user: user._user || user.username,
65
+ workflow: workflowDefinition._id,
66
+ contextData: { triggerData: data }, // Les données passées deviennent le triggerData
67
+ status: 'pending',
68
+ startedAt: new Date()
69
+ };
70
+
71
+ const insertResult = await dbCollection.insertOne(workflowRunData);
72
+ logger.info(`[runWorkflowByName] Created workflowRun ${insertResult.insertedId} for workflow "${name}".`);
73
+
74
+ // 3. Lancer le traitement de manière asynchrone
75
+ await processWorkflowRun(insertResult.insertedId, user);
76
+
77
+ return { success: true, runId: insertResult.insertedId };
78
+ }
28
79
  /**
29
80
  * Exécute une fonction de manière sécurisée en s'assurant qu'une seule instance
30
81
  * s'exécute à la fois, grâce à un système de verrouillage distribué basé sur la base de données.
@@ -218,13 +269,23 @@ export async function executeSafeJavascript(actionDef, context, user) {
218
269
  return new ivm.ExternalCopy(result.data?.[0] || null).copyInto();
219
270
  };
220
271
 
221
- // 1. Build the sandboxed API
222
- await jail.set('_db_create', new ivm.Reference((modelName, dataObject) => insertData(modelName, JSON.parse(dataObject), {}, user, false)));
272
+ // 1. Build the sandboxed API methods
273
+ await jail.set('_workflow_run', new ivm.Reference(async (name, contextData) => {
274
+ const result = await runWorkflowByName(name, JSON.parse(contextData), user);
275
+ return new ivm.ExternalCopy(result).copyInto();
276
+ }));
277
+ await jail.set('_db_create', new ivm.Reference(async (modelName, dataObject) => {
278
+ const result = await insertData(modelName, JSON.parse(dataObject), {}, user, false);
279
+ if (result.success && result.insertedIds) {
280
+ result.insertedIds = result.insertedIds.map(id => id.toString());
281
+ }
282
+ return new ivm.ExternalCopy(result).copyInto();
283
+ }));
223
284
  await jail.set('_db_find', new ivm.Reference(find));
224
285
  await jail.set('_db_findOne', new ivm.Reference(findOne));
225
286
 
226
- await jail.set('_db_update', new ivm.Reference((modelName, filter, updateObject) => patchData(modelName, JSON.parse(filter), JSON.parse(updateObject), {}, user, false)));
227
- await jail.set('_db_delete', new ivm.Reference((modelName, filter) => deleteData(modelName, JSON.parse(filter), user, false)));
287
+ await jail.set('_db_update', new ivm.Reference(async (modelName, filter, updateObject) => patchData(modelName, JSON.parse(filter), JSON.parse(updateObject), {}, user, false)));
288
+ await jail.set('_db_delete', new ivm.Reference(async (modelName, filter) => deleteData(modelName, JSON.parse(filter), user, false)));
228
289
 
229
290
  const createLoggerMethod = (level) => {
230
291
  return (...args) => {
@@ -247,12 +308,8 @@ export async function executeSafeJavascript(actionDef, context, user) {
247
308
  return new ivm.ExternalCopy(result.data?.[0]?.value || null).copyInto();
248
309
  }));
249
310
  await jail.set('_env_get_all', new ivm.Reference(async () => {
250
- const result = await searchData({ model: 'env' }, user);
251
- const envObject = result.data.reduce((acc, v) => {
252
- acc[v.name] = v.value;
253
- return acc;
254
- }, {});
255
- return new ivm.ExternalCopy(envObject).copyInto();
311
+ const result = await getEnv(user);
312
+ return new ivm.ExternalCopy(result).copyInto();
256
313
  }));
257
314
 
258
315
  // Contexte sécurisé
@@ -274,6 +331,10 @@ export async function executeSafeJavascript(actionDef, context, user) {
274
331
  update: (...args) => _db_update.applySyncPromise(null, normalizeArgs(args)),
275
332
  delete: (...args) => _db_delete.applySyncPromise(null, normalizeArgs(args))
276
333
  };
334
+
335
+ const workflow = {
336
+ run: (...args) => _workflow_run.applySyncPromise(null, normalizeArgs(args))
337
+ };
277
338
 
278
339
  const logger = {
279
340
  info: _log_info,
@@ -324,7 +385,7 @@ export async function executeSafeJavascript(actionDef, context, user) {
324
385
  }
325
386
 
326
387
  /**
327
- * Handles the 'Webhook' workflow action.
388
+ * Handles the 'HttpRequest' workflow action.
328
389
  * Sends an HTTP request to a specified URL with substituted data using native fetch.
329
390
  *
330
391
  * @param {object} actionDef - The definition of the 'Webhook' action.
@@ -333,17 +394,17 @@ export async function executeSafeJavascript(actionDef, context, user) {
333
394
  * @param {object} dbCollection - The MongoDB collection (moins pertinent ici, mais gardé pour la cohérence).
334
395
  * @returns {Promise<{success: boolean, message?: string, responseStatus?: number, responseBody?: any}>} - Result of the action.
335
396
  */
336
- async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
397
+ async function handleHttpRequestAction(actionDef, contextData, user, dbCollection) {
337
398
  const { name: actionName, _id: actionId, url, method = 'POST', headers: headersTemplate, body: bodyTemplate } = actionDef;
338
399
 
339
400
  // 1. Basic Validation
340
401
  if (!url) {
341
- const msg = `[handleWebhookAction] Action ${actionName} (${actionId}): Missing 'url'.`;
402
+ const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Missing 'url'.`;
342
403
  logger.error(msg);
343
404
  return { success: false, message: msg };
344
405
  }
345
406
 
346
- logger.info(`[handleWebhookAction] Action ${actionName} (${actionId}): Executing webhook. Method: ${method}`);
407
+ logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Executing webhook. Method: ${method}`);
347
408
 
348
409
  try {
349
410
  // 2. Substitute Variables
@@ -360,7 +421,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
360
421
  } else if (typeof headersTemplate === 'object') {
361
422
  headersObject = await substituteVariables(headersTemplate, contextData, user);
362
423
  } else {
363
- logger.warn(`[handleWebhookAction] Action ${actionName} (${actionId}): 'headers' has an invalid type (${typeof headersTemplate}). Ignoring.`);
424
+ logger.warn(`[handleHttpRequestAction] Action ${actionName} (${actionId}): 'headers' has an invalid type (${typeof headersTemplate}). Ignoring.`);
364
425
  }
365
426
  }
366
427
 
@@ -371,7 +432,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
371
432
  } else if (typeof bodyTemplate === 'object') {
372
433
  bodyObject = await substituteVariables(bodyTemplate, contextData, user);
373
434
  } else {
374
- logger.warn(`[handleWebhookAction] Action ${actionName} (${actionId}): 'body' has an invalid type (${typeof bodyTemplate}). Ignoring.`);
435
+ logger.warn(`[handleHttpRequestAction] Action ${actionName} (${actionId}): 'body' has an invalid type (${typeof bodyTemplate}). Ignoring.`);
375
436
  }
376
437
  }
377
438
 
@@ -383,7 +444,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
383
444
  throw new Error("Parsed headers is not a valid object.");
384
445
  }
385
446
  } catch (parseError) {
386
- logger.error(`[handleWebhookAction] Action ${actionName} (${actionId}): Failed to parse substituted 'headers' JSON. Error: ${parseError.message}. Using default headers. Substituted string: ${substitutedHeadersString}`);
447
+ logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Failed to parse substituted 'headers' JSON. Error: ${parseError.message}. Using default headers. Substituted string: ${substitutedHeadersString}`);
387
448
  headersObject = { 'Content-Type': 'application/json' }; // Fallback
388
449
  }
389
450
  }
@@ -425,7 +486,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
425
486
  }
426
487
 
427
488
  // 5. Execute Fetch Request using native fetch
428
- logger.info(`[handleWebhookAction] Action ${actionName} (${actionId}): Calling URL: ${substitutedUrl}`);
489
+ logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Calling URL: ${substitutedUrl}`);
429
490
  const response = await fetch(substitutedUrl, fetchOptions); // Utilisation de fetch natif
430
491
 
431
492
  // 6. Process Response
@@ -438,7 +499,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
438
499
  responseBody = await response.text();
439
500
  }
440
501
  } catch (responseParseError) {
441
- logger.error(`[handleWebhookAction] Action ${actionName} (${actionId}): Failed to parse response body. Error: ${responseParseError.message}`);
502
+ logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Failed to parse response body. Error: ${responseParseError.message}`);
442
503
  // Try reading as text again in case of error during json parsing
443
504
  try {
444
505
  responseBody = await response.text();
@@ -447,7 +508,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
447
508
  }
448
509
  }
449
510
 
450
- logger.info(`[handleWebhookAction] Action ${actionName} (${actionId}): Received response. Status: ${response.status}`);
511
+ logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Received response. Status: ${response.status}`);
451
512
 
452
513
  // 7. Return Result
453
514
  if (response.ok) { // Status code 200-299
@@ -455,13 +516,13 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
455
516
  success: true,
456
517
  message: `Webhook executed successfully. Status: ${response.status}`,
457
518
  responseStatus: response.status,
458
- responseBody: responseBody
459
- // updatedContext: { webhookResponse: responseBody } // Optionnel: Ajouter la réponse au contexte
519
+ responseBody: responseBody,
520
+ updatedContext: { httpResponse: responseBody }
460
521
  };
461
522
  } else {
462
523
  // Handle non-successful responses (4xx, 5xx)
463
524
  const errorMsg = `Webhook execution failed. Status: ${response.status}. Response: ${typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody)}`;
464
- logger.error(`[handleWebhookAction] Action ${actionName} (${actionId}): ${errorMsg}`);
525
+ logger.error(`[handleHttpRequestAction] Action ${actionName} (${actionId}): ${errorMsg}`);
465
526
  return {
466
527
  success: false,
467
528
  message: errorMsg,
@@ -472,7 +533,7 @@ async function handleWebhookAction(actionDef, contextData, user, dbCollection) {
472
533
 
473
534
  } catch (error) {
474
535
  // Catch network errors or other unexpected errors during the process
475
- const msg = `[handleWebhookAction] Action ${actionName} (${actionId}): Unexpected error during webhook execution. Error: ${error.message}`;
536
+ const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Unexpected error during webhook execution. Error: ${error.message}`;
476
537
  logger.error(msg, error.stack);
477
538
  return { success: false, message: msg };
478
539
  }
@@ -778,6 +839,52 @@ async function handleDeleteDataAction(actionDef, contextData, user, dbCollection
778
839
  }
779
840
  }
780
841
 
842
+ /**
843
+ * Handles the 'ExecuteServiceFunction' workflow action.
844
+ * Acts as a secure bridge between the workflow engine and native service modules.
845
+ *
846
+ * @param {object} actionDef - The action definition.
847
+ * @param {object} contextData - The current workflow context.
848
+ * @param {object} user - The user object.
849
+ * @returns {Promise<{success: boolean, message?: string, updatedContext?: object}>}
850
+ */
851
+ async function handleExecuteServiceFunction(actionDef, contextData, user) {
852
+ const { serviceName, functionName, args: argsTemplate } = actionDef;
853
+
854
+ if (!serviceName || !functionName) {
855
+ return { success: false, message: "Action requires 'serviceName' and 'functionName'." };
856
+ }
857
+
858
+ const service = services[serviceName];
859
+ if (!service) {
860
+ return { success: false, message: `Service '${serviceName}' not found in the registry.` };
861
+ }
862
+
863
+ const func = service[functionName];
864
+ if (typeof func !== 'function') {
865
+ return { success: false, message: `Function '${functionName}' not found in service '${serviceName}'.` };
866
+ }
867
+
868
+ try {
869
+ // Substitute variables in the arguments array
870
+ const substitutedArgs = Array.isArray(argsTemplate)
871
+ ? await substituteVariables(argsTemplate, contextData, user)
872
+ : [];
873
+
874
+ logger.info(`[Service Call] Calling ${serviceName}.${functionName} with ${substitutedArgs.length} argument(s).`);
875
+ const result = await func(...substitutedArgs, user);
876
+
877
+ return {
878
+ success: true,
879
+ updatedContext: { serviceResult: result } // Store result in context
880
+ };
881
+ } catch (error) {
882
+ const msg = `Error executing ${serviceName}.${functionName}: ${error.message}`;
883
+ logger.error(`[Service Call] ${msg}`, error.stack);
884
+ return { success: false, message: msg };
885
+ }
886
+ }
887
+
781
888
  // Dans workflow.js
782
889
  export async function executeStepAction(actionDef, contextData, user, dbCollection) {
783
890
  logger.info(`[executeStepAction] Executing action type ${actionDef.type} for action ${actionDef._id} (${actionDef.name})`);
@@ -789,8 +896,8 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
789
896
  logger.info(`[Workflow Log Action] Action: ${actionDef.name}. Contexte:`, contextData);
790
897
  result = { success: true, message: 'Log action executed successfully.' }; // <--- CORRECTION
791
898
  break;
792
- case 'Webhook':
793
- result = await handleWebhookAction(actionDef, contextData, user, dbCollection);
899
+ case 'HttpRequest':
900
+ result = await handleHttpRequestAction(actionDef, contextData, user, dbCollection);
794
901
  break;
795
902
  case 'CreateData':
796
903
  result = await handleCreateDataAction(actionDef, contextData, user, dbCollection);
@@ -813,6 +920,9 @@ export async function executeStepAction(actionDef, contextData, user, dbCollecti
813
920
  case 'ExecuteScript':
814
921
  result = await executeSafeJavascript(actionDef, contextData, user);
815
922
  break;
923
+ case 'ExecuteServiceFunction':
924
+ result = await handleExecuteServiceFunction(actionDef, contextData, user);
925
+ break;
816
926
  default:
817
927
  logger.error(`[executeStepAction] Unknown action type: ${actionDef.type}`);
818
928
  return { success: false, message: `Unknown action type: ${actionDef.type}` };
@@ -1027,6 +1137,8 @@ export async function substituteVariables(template, contextData, user) {
1027
1137
  return new Date().toISOString();
1028
1138
  } else if (path === 'randomUUID') {
1029
1139
  return crypto.randomUUID();
1140
+ } else if( path === "baseUrl" ){
1141
+ return process.env.NODE_ENV === 'production' ? 'https://'+getHost()+'/' : 'http://localhost:/'+port;
1030
1142
  }
1031
1143
 
1032
1144
  // Détecter si le chemin est complexe (contient plus d'un point)
@@ -1099,102 +1211,96 @@ export async function substituteVariables(template, contextData, user) {
1099
1211
  }
1100
1212
 
1101
1213
  /**
1102
- * Déclenche l'instanciation d'un workflowRun si les conditions sont remplies.
1103
- * Vérifie le type d'événement et le filtre de données du déclencheur.
1104
- * Crée un document 'workflowRun' pour une exécution asynchrone ultérieure.
1214
+ * Triggers the instantiation of a workflowRun if conditions are met.
1215
+ * Checks the event type and trigger's data filter.
1216
+ * Creates a 'workflowRun' document for later asynchronous execution.
1105
1217
  *
1106
- * @param {object} triggerData - La donnée qui a déclenché le(s) workflow(s) (peut être un document de données ou un document de modèle).
1107
- * @param {object} user - L'utilisateur associé.
1108
- * @param {'DataAdded' | 'DataEdited' | 'DataDeleted' | 'ModelAdded' | 'ModelEdited' | 'ModelDeleted'} eventType - Le type d'événement.
1218
+ * @param {object} triggerData - The data that triggered the workflow(s) (can be a data document or model document).
1219
+ * @param {object} user - The associated user.
1220
+ * @param {'DataAdded' | 'DataEdited' | 'DataDeleted' | 'ModelAdded' | 'ModelEdited' | 'ModelDeleted'} eventType - The event type.
1109
1221
  */
1110
- export async function triggerWorkflows(triggerData, user, eventType) {
1111
- const dataId = eventType.startsWith('Model') ? null : triggerData._id;
1112
-
1222
+ export async function triggerWorkflows(triggerData, user, eventType) {
1113
1223
  const trigger = async (triggerData, user, eventType) => {
1114
-
1115
1224
  // Basic validation
1116
1225
  if (!triggerData || !user || !eventType) {
1117
- console.warn("triggerWorkflows: Appel invalide - triggerData, user, ou eventType manquant.", { hasTriggerData: !!triggerData, hasUser: !!user, eventType });
1226
+ console.warn("triggerWorkflows: Invalid call - missing triggerData, user, or eventType.", {
1227
+ hasTriggerData: !!triggerData,
1228
+ hasUser: !!user,
1229
+ eventType
1230
+ });
1118
1231
  return;
1119
1232
  }
1120
- // Determine the model name and data ID based on the event type
1233
+
1234
+ // Determine model name and data ID based on event type
1121
1235
  const targetModelName = eventType.startsWith('Model') ? triggerData.name : triggerData._model;
1122
- const dataId = eventType.startsWith('Model') ? null : triggerData._id; // ID only relevant for data events
1236
+ const dataId = eventType.startsWith('Model') ? null : triggerData._id;
1123
1237
 
1124
1238
  if (!targetModelName) {
1125
- console.warn(`triggerWorkflows: Impossible de déterminer le nom du modèle pour l'événement ${eventType}.`, triggerData);
1239
+ console.warn(`triggerWorkflows: Cannot determine model name for event ${eventType}.`, triggerData);
1126
1240
  return;
1127
1241
  }
1128
1242
 
1129
1243
  console.log(`[Workflow Trigger] Event: ${eventType}, Model: ${targetModelName}${dataId ? `, Data ID: ${dataId}` : ''}, User: ${user.username}`);
1130
1244
 
1131
1245
  try {
1132
- const dbCollection = await getCollectionForUser(user); // Collection des données utilisateur
1246
+ const dbCollection = await getCollectionForUser(user);
1133
1247
 
1134
- // 1. Trouver les WorkflowTriggers pertinents
1248
+ // 1. Find relevant WorkflowTriggers
1135
1249
  const workflowTriggers = await dbCollection.find({
1136
1250
  _model: 'workflowTrigger',
1137
1251
  targetModel: targetModelName,
1138
1252
  isActive: true,
1139
- onEvent: eventType, // Assurez-vous que le champ s'appelle bien 'onEvent' dans votre modèle
1253
+ onEvent: eventType,
1140
1254
  $or: [{_user: user._user}, {_user: user.username}]
1141
1255
  }).toArray();
1142
1256
 
1143
1257
  if (workflowTriggers.length === 0) {
1144
- console.debug(`[Workflow Trigger] Aucun déclencheur actif trouvé pour ${targetModelName} / ${eventType}.`);
1258
+ console.debug(`[Workflow Trigger] No active triggers found for ${targetModelName}/${eventType}.`);
1145
1259
  return;
1146
1260
  }
1147
- console.debug(`[Workflow Trigger] Trouvé ${workflowTriggers.length} déclencheur(s) potentiel(s) pour ${targetModelName} / ${eventType}.`);
1261
+ console.debug(`[Workflow Trigger] Found ${workflowTriggers.length} potential trigger(s) for ${targetModelName}/${eventType}.`);
1148
1262
 
1149
- // 2. Pour chaque déclencheur trouvé, vérifier le filtre de données et créer un workflowRun
1263
+ // 2. For each trigger, verify data filter and create workflowRun
1150
1264
  for (const trigger of workflowTriggers) {
1151
- console.debug(`[Workflow Trigger] Vérification du déclencheur ${trigger._id} (${trigger.name || 'Sans nom'})...`);
1265
+ console.debug(`[Workflow Trigger] Evaluating trigger ${trigger._id} (${trigger.name || 'Unnamed'})...`);
1152
1266
 
1153
- // 3. Vérifier le filtre de données (dataFilter) si applicable
1154
- if (eventType.startsWith('Data') && trigger.dataFilter && dataId) {
1267
+ // 3. Check data filter if applicable
1268
+ if (eventType.startsWith('Data') && trigger.dataFilter) {
1155
1269
  let dataFilterCondition = null;
1156
1270
  try {
1157
- // dataFilter est supposé être stocké comme un objet (ou une string JSON valide)
1271
+ // dataFilter is expected to be stored as an object or valid JSON string
1158
1272
  if (typeof trigger.dataFilter === 'string') {
1159
1273
  dataFilterCondition = JSON.parse(trigger.dataFilter);
1160
1274
  } else if (typeof trigger.dataFilter === 'object' && trigger.dataFilter !== null) {
1161
1275
  dataFilterCondition = trigger.dataFilter;
1162
1276
  }
1163
1277
  } catch (parseError) {
1164
- console.error(`[Workflow Trigger] Erreur de parsing JSON pour dataFilter du trigger ${trigger._id}:`, parseError);
1165
- continue; // Passer au trigger suivant si le filtre est invalide
1278
+ console.error(`[Workflow Trigger] JSON parsing error for dataFilter in trigger ${trigger._id}:`, parseError);
1279
+ continue; // Skip to next trigger if filter is invalid
1166
1280
  }
1167
1281
 
1168
1282
  try {
1169
- const finalFilter = {
1170
- '$and': [
1171
- {"_id": { "$toObjectId": triggerData._id.toString() }},
1172
- dataFilterCondition // Applique la condition du trigger
1173
- ]
1174
- };
1175
-
1176
-
1177
- console.debug(`[Workflow Trigger] Vérification dataFilter pour trigger ${trigger._id} avec filtre combiné:`, JSON.stringify(finalFilter));
1178
- const match = await searchData({ model: triggerData._model, filter: finalFilter, limit: 1 }, user);
1179
- if (!match.count) {
1180
- console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter non satisfait par la donnée ${dataId}. WorkflowRun non créé.`);
1181
- continue; // Passer au trigger suivant
1182
- } else {
1183
- console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter satisfait par la donnée ${dataId}.`);
1283
+ const mod = await getModel(targetModelName, user);
1284
+ const filterMatches = isConditionMet(mod, dataFilterCondition, triggerData, [], user);
1285
+
1286
+ if (!filterMatches) {
1287
+ console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter not satisfied by data. Skipping workflowRun creation.`);
1288
+ continue;
1184
1289
  }
1290
+ console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter satisfied.`);
1185
1291
  } catch (filterError) {
1186
- console.error(`[Workflow Trigger] Erreur lors de la conversion ou de l'exécution du dataFilter pour le trigger ${trigger._id}:`, filterError);
1187
- continue; // Ne pas créer en cas d'erreur de filtre
1292
+ console.error(`[Workflow Trigger] Error evaluating dataFilter for trigger ${trigger._id}:`, filterError);
1293
+ continue;
1188
1294
  }
1189
- } // Fin de la vérification dataFilter
1295
+ }
1190
1296
 
1191
- // 4. Si les filtres (eventType, dataFilter) sont passés, créer l'instance workflowRun
1297
+ // 4. If filters passed, create workflowRun instance
1192
1298
  if (!trigger.workflow || !isObjectId(trigger.workflow)) {
1193
- console.warn(`[Workflow Trigger] Trigger ${trigger._id} n'a pas de workflow valide associé.`);
1299
+ console.warn(`[Workflow Trigger] Trigger ${trigger._id} has no valid associated workflow.`);
1194
1300
  continue;
1195
1301
  }
1196
1302
 
1197
- // a. Récupérer la définition du Workflow (juste pour vérifier qu'il existe)
1303
+ // a. Verify workflow exists
1198
1304
  const workflowDefinition = await dbCollection.findOne({
1199
1305
  _id: new ObjectId(trigger.workflow),
1200
1306
  _model: 'workflow',
@@ -1202,21 +1308,20 @@ export async function triggerWorkflows(triggerData, user, eventType) {
1202
1308
  });
1203
1309
 
1204
1310
  if (!workflowDefinition) {
1205
- console.warn(`[Workflow Trigger] Workflow ${trigger.workflow} associé au trigger ${trigger._id} non trouvé.`);
1311
+ console.warn(`[Workflow Trigger] Workflow ${trigger.workflow} associated with trigger ${trigger._id} not found.`);
1206
1312
  continue;
1207
1313
  }
1208
1314
 
1209
- // b. Créer le document workflowRun
1315
+ // b. Create workflowRun document
1210
1316
  const workflowRunData = {
1211
1317
  _model: 'workflowRun',
1212
1318
  _user: user._user || user.username,
1213
- workflow: workflowDefinition._id, // Référence au workflow parent
1214
- contextData: { // Contexte initial
1215
- triggerDataModel: targetModelName,// Modèle de la donnée déclencheuse
1216
- triggerData: triggerData // Inclure la donnée déclencheuse
1217
- // Vous pourriez ajouter d'autres infos ici si nécessaire
1319
+ workflow: workflowDefinition._id,
1320
+ contextData: {
1321
+ triggerDataModel: targetModelName,
1322
+ triggerData: triggerData
1218
1323
  },
1219
- status: 'pending', // Statut initial
1324
+ status: 'pending',
1220
1325
  owner: null,
1221
1326
  startedAt: new Date()
1222
1327
  };
@@ -1224,30 +1329,25 @@ export async function triggerWorkflows(triggerData, user, eventType) {
1224
1329
  try {
1225
1330
  const insertResult = await dbCollection.insertOne(workflowRunData);
1226
1331
  if (insertResult.insertedId) {
1227
- console.info(`[Workflow Trigger] WorkflowRun ${insertResult.insertedId} créé pour le workflow ${workflowDefinition.name} (ID: ${workflowDefinition._id}) déclenché par ${trigger._id}.`);
1228
-
1332
+ console.info(`[Workflow Trigger] Created workflowRun ${insertResult.insertedId} for workflow ${workflowDefinition.name} (ID: ${workflowDefinition._id}) triggered by ${trigger._id}.`);
1229
1333
  await workflowModule.processWorkflowRun(insertResult.insertedId, user);
1230
1334
  } else {
1231
- console.error(`[Workflow Trigger] Échec de la création du WorkflowRun pour le workflow ${workflowDefinition._id} (Trigger: ${trigger._id}).`);
1335
+ console.error(`[Workflow Trigger] Failed to create workflowRun for workflow ${workflowDefinition._id} (Trigger: ${trigger._id}).`);
1232
1336
  }
1233
1337
  } catch (insertError) {
1234
- console.error(`[Workflow Trigger] Erreur lors de l'insertion du WorkflowRun pour le workflow ${workflowDefinition._id} (Trigger: ${trigger._id}):`, insertError);
1338
+ console.error(`[Workflow Trigger] Error creating workflowRun for workflow ${workflowDefinition._id} (Trigger: ${trigger._id}):`, insertError);
1235
1339
  }
1236
-
1237
- } // Fin de la boucle des triggers
1238
-
1340
+ }
1239
1341
  } catch (error) {
1240
- console.error(`[Workflow Trigger] Erreur générale dans triggerWorkflows pour ${targetModelName}${dataId ? ` ID: ${dataId}` : ''} (Event: ${eventType}):`, error);
1342
+ console.error(`[Workflow Trigger] General error in triggerWorkflows for ${targetModelName}${dataId ? ` ID: ${dataId}` : ''} (Event: ${eventType}):`, error);
1241
1343
  }
1242
1344
  }
1243
1345
 
1244
1346
  return new Promise((resolve) => setTimeout(async () => {
1245
1347
  await trigger(triggerData, user, eventType);
1246
1348
  resolve();
1247
- }, 0)
1248
- );
1349
+ }, 0));
1249
1350
  }
1250
-
1251
1351
  /**
1252
1352
  * Processes a workflowRun instance step-by-step.
1253
1353
  * Fetches the run, evaluates conditions, executes actions, and transitions
@@ -1483,12 +1583,7 @@ async function executeGenerateAIContentAction(action, context, user) {
1483
1583
  // 1. Retrieve the API key (User Environment > Machine Environment)
1484
1584
  let apiKey;
1485
1585
 
1486
- const providers = {
1487
- "OpenAI" : "OPENAI_API_KEY",
1488
- "Google": "GOOGLE_API_KEY",
1489
- "DeepSeek": "DEEPSEEK_API_KEY"
1490
- }
1491
- const envKeyName = providers[aiProvider];
1586
+ const envKeyName = providers[aiProvider].key;
1492
1587
  if( !envKeyName ) {
1493
1588
  return {success: false, message: i18n.t('aiContent.env', `API key for provider ${aiProvider} (${envKeyName}) not found in user environment.`)};
1494
1589
  }
@@ -1512,22 +1607,8 @@ async function executeGenerateAIContentAction(action, context, user) {
1512
1607
  }
1513
1608
 
1514
1609
  // 2. Initialize the LLM client with LangChain
1515
- let llm;
1516
- try {
1517
- switch (aiProvider) {
1518
- case 'OpenAI':
1519
- llm = new ChatOpenAI({ apiKey, model: aiModel, temperature: 0.7 });
1520
- break;
1521
- case 'Google':
1522
- llm = new ChatGoogleGenerativeAI({ apiKey, model: aiModel, temperature: 0.7 });
1523
- break;
1524
- case 'DeepSeek':
1525
- llm = new ChatDeepSeek({ apiKey, model: aiModel, temperature: 0.7 });
1526
- break;
1527
- default:
1528
- throw new Error(`Unsupported AI provider: ${aiProvider}`);
1529
- }
1530
- } catch (initError) {
1610
+ let llm = getAIProvider(aiProvider, aiModel, apiKey);
1611
+ if( !llm ) {
1531
1612
  const message = `Failed to initialize AI client for ${aiProvider}: ${initError.message}`;
1532
1613
  logger.error(`[AI Action] ${message}`);
1533
1614
  return { success: false, message };