data-primals-engine 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,7 @@ import Draggable from "./Draggable.jsx";
25
25
  import CronBuilder from "./CronBuilder.jsx";
26
26
  import RTETrans from "./RTETrans.jsx";
27
27
  import uniqid from "uniqid";
28
+ import {isConditionMet} from "data-primals-engine/filter";
28
29
 
29
30
  // ... (fonction getInputType) ...
30
31
  // Fonction pour obtenir le type d'input HTML basé sur le type de champ du modèle
@@ -52,208 +53,6 @@ const getInputType = (fieldType) => {
52
53
  }
53
54
  };
54
55
 
55
- /**
56
- * Evaluates a single condition against form data.
57
- * @param {object} currentModelDef - The definition of the current model.
58
- * @param {object} condition - The condition to evaluate.
59
- * @param {object} formData - The form data.
60
- * @param {object[]} allModels - An array of all model definitions.
61
- * @param {object} user - The current user.
62
- * @returns {boolean} - True if the condition is met, false otherwise.
63
- */
64
- const evaluateSingleCondition = (currentModelDef, condition, formData, allModels, user) => {
65
- // Condition est directement un filtre MongoDB, donc on l'applique
66
- // en utilisant les opérateurs et les valeurs qu'il contient.
67
-
68
- if (!condition || typeof condition !== 'object') {
69
- console.warn("[Client Eval] Condition is not an object:", condition);
70
- return true; // Permissive default
71
- }
72
-
73
- // Si la condition contient des opérateurs logiques, on les gère ici
74
- if (condition.$and || condition.$or || condition.$not || condition.$nor) {
75
- console.warn("[Client Eval] Condition logique détectée dans evaluateSingleCondition, ce n'est pas attendu. Devrait être géré par isConditionMet.");
76
- return true; // Permissive default
77
- }
78
-
79
- if (condition.$find) {
80
- const fieldName = Object.keys(condition)[0];
81
- const fieldValue = formData[fieldName];
82
- const findCondition = condition.$find;
83
-
84
- if (!Array.isArray(fieldValue)) return false;
85
-
86
- return fieldValue.some(item => {
87
- // Gestion spéciale pour la syntaxe $eq: ["$$this.field", value]
88
- if (findCondition.$eq && Array.isArray(findCondition.$eq)) {
89
- const [fieldPath, value] = findCondition.$eq;
90
- if (fieldPath.startsWith("$$this.")) {
91
- const fieldToCheck = fieldPath.replace("$$this.", "");
92
- return item[fieldToCheck] == value;
93
- }
94
- }
95
-
96
- // Sinon, évaluation normale
97
- const tempData = { ...item };
98
- return evaluateSingleCondition(currentModelDef, findCondition, tempData, allModels, user);
99
- });
100
- }
101
-
102
- // Si la condition contient un opérateur $exists, on le gère ici
103
- if (condition.$exists !== undefined) {
104
- const fieldName = Object.keys(condition)[0]; // Récupérer le nom du champ
105
- const shouldExist = condition.$exists; // Récupérer la valeur de $exists (true ou false)
106
- const exists = Object.prototype.hasOwnProperty.call(formData, fieldName) && formData[fieldName] !== undefined && formData[fieldName] !== null;
107
- return exists === shouldExist;
108
- }
109
-
110
- // Si la condition contient un opérateur $find, on le gère ici
111
- if (condition.$find) {
112
- // Récupérer le nom du champ
113
- const fieldName = Object.keys(condition)[0];
114
- const fieldValue = formData[fieldName];
115
- try {
116
- // Assuming evaluateSingleCondition handles $find
117
- return evaluateSingleCondition(currentModelDef, condition.$find, formData, allModels, user);
118
- } catch (error) {
119
- console.error("Error evaluating $find condition:", condition, error);
120
- return false;
121
- }
122
- }
123
-
124
- // Récupérer le nom du champ et la condition
125
- const fieldName = Object.keys(condition)[0];
126
- const fieldValue = condition[fieldName];
127
-
128
- // Récupérer la définition du champ
129
- const fieldDef = currentModelDef?.fields.find(f => f.name === fieldName);
130
-
131
- // Si la définition du champ n'est pas trouvée, on retourne true
132
- if (!fieldDef) {
133
- console.warn(`[Client Eval] Field definition not found for field: ${fieldName}`);
134
- return true; // Permissive default
135
- }
136
-
137
- let targetValue = formData[fieldName];
138
- let processedConditionValue = fieldValue;
139
-
140
- // 1. Handle $exists (on the first field)
141
- // 2. Convert condition value based on operator's expected input type
142
- const fieldType = fieldDef?.type; // Type of the first field
143
-
144
- try {
145
- processedConditionValue = convertValueType(fieldValue, fieldType);
146
- } catch (e) {
147
- logClientEvalWarning(`Error converting value type: ${e.message}`, condition);
148
- return false;
149
- }
150
-
151
- return evaluateComparison(fieldValue, targetValue, processedConditionValue, condition);
152
-
153
- function logClientEvalWarning(message, details) {
154
- console.warn(`[Client Eval] ${message}:`, details);
155
- }
156
-
157
- function convertValueType(value, inputType) {
158
- switch (inputType) {
159
- case 'number':
160
- const numValue = parseFloat(value);
161
- if (isNaN(numValue)) {
162
- throw new Error(`Invalid number value: ${value}`);
163
- }
164
- return numValue;
165
- case 'boolean':
166
- return String(value).toLowerCase() === 'true';
167
- case 'csv':
168
- return String(value).split(',').map(item => item.trim()).filter(Boolean);
169
- case 'text':
170
- default:
171
- return String(value);
172
- }
173
- }
174
-
175
- function evaluateComparison(operator, targetValue, processedConditionValue, condition) {
176
- try {
177
- switch (typeof operator === 'object' ? Object.keys(operator)[0] : null) {
178
- case '$eq': return targetValue == processedConditionValue;
179
- case '$ne': return targetValue != processedConditionValue;
180
- case '$gt': return targetValue > processedConditionValue;
181
- case '$lt': return targetValue < processedConditionValue;
182
- case '$gte': return targetValue >= processedConditionValue;
183
- case '$lte': return targetValue <= processedConditionValue;
184
- case '$regex':
185
- if (typeof targetValue !== 'string') return false;
186
- if (typeof processedConditionValue !== 'string') return false;
187
- try {
188
- const regex = new RegExp(processedConditionValue, 'i');
189
- return regex.test(targetValue);
190
- } catch (e) {
191
- logClientEvalWarning(`Invalid regex pattern: ${processedConditionValue}`, condition);
192
- return false;
193
- }
194
- case '$in':
195
- return Array.isArray(processedConditionValue) && processedConditionValue.includes(String(targetValue));
196
- case '$nin':
197
- return !Array.isArray(processedConditionValue) || !processedConditionValue.includes(String(targetValue));
198
- default:
199
- logClientEvalWarning(`Unhandled operator in client evaluation logic: ${operator}`, condition);
200
- return true; // Permissive default
201
- }
202
- } catch (evalError) {
203
- logClientEvalWarning(`Error during client condition evaluation: ${operator}, targetValue=${targetValue}, processedConditionValue=${processedConditionValue}`, condition);
204
- return false;
205
- }
206
- }
207
- };
208
-
209
- export const isConditionMet = (model, cond, formData, allModels, user) => {
210
- const isNode = (v) => typeof v === 'object' && v !== null;
211
-
212
- const condition = cond;
213
-
214
- if (!condition) return true;
215
-
216
- if (condition.$and && Array.isArray(condition.$and)) {
217
- if (condition.$and.length === 0) return true;
218
- return condition.$and.every(sub => isConditionMet(model, sub, formData, allModels, user));
219
- }
220
-
221
- if (condition.$or && Array.isArray(condition.$or)) {
222
- if (condition.$or.length === 0) return false;
223
- return condition.$or.some(sub => isConditionMet(model, sub, formData, allModels, user));
224
- }
225
-
226
- if (condition.$not) {
227
- return !isConditionMet(model, condition.$not, formData, allModels, user);
228
- }
229
-
230
- if (condition.$nor && Array.isArray(condition.$nor)) {
231
- if (condition.$nor.length === 0) return true;
232
- return !condition.$nor.some(sub => isConditionMet(model, sub, formData, allModels, user));
233
- }
234
-
235
- if (condition.$find) {
236
- try {
237
- // Assuming evaluateSingleCondition handles $find
238
- return evaluateSingleCondition(model, condition, formData, allModels, user);
239
- } catch (error) {
240
- console.error("Error evaluating $find condition:", condition, error);
241
- return false;
242
- }
243
- }
244
-
245
- if (condition.path && condition.op) {
246
- try {
247
- return evaluateSingleCondition(model, condition, formData, allModels, user);
248
- } catch (error) {
249
- console.error("Error evaluating condition:", condition, error);
250
- return false;
251
- }
252
- }
253
-
254
- console.warn("Unknown condition format:", condition);
255
- return true;
256
- };
257
56
 
258
57
  export const DataEditor = forwardRef(function MyDataEditor({
259
58
  isLoading,
@@ -43,7 +43,6 @@ import RelationValue from "./RelationValue.jsx";
43
43
  import {FaGear, FaPencil, FaTriangleExclamation} from "react-icons/fa6";
44
44
  import RestoreConfirmationModal from "./RestoreConfirmationModal.jsx";
45
45
  import {event_trigger, isLightColor} from "../../src/core.js";
46
- import {isConditionMet} from "./DataEditor.jsx";
47
46
  import {Tooltip} from "react-tooltip";
48
47
  import ExportDialog from "./ExportDialog.jsx";
49
48
  import Captions from "yet-another-react-lightbox/plugins/captions";
@@ -58,6 +57,7 @@ import PackGallery from "./PackGallery.jsx";
58
57
  import {HiddenableCell} from "./HiddenableCell.jsx";
59
58
  import ConditionBuilder from "./ConditionBuilder.jsx";
60
59
  import {pagedFilterToMongoConds} from "./filter.js";
60
+ import {isConditionMet} from "data-primals-engine/filter";
61
61
 
62
62
  // Ajoutez cette constante pour la clé de sessionStorage
63
63
  const SESSION_STORAGE_IMPORT_JOBS_KEY = 'activeImportJobs';
@@ -53,7 +53,7 @@ export const MONGO_OPERATORS = {
53
53
  export const profiles = {
54
54
  'personal': ['contact', 'location', 'imageGallery', 'budget', 'currency', 'taxonomy'], // budget,
55
55
  'developer': ['alert','endpoint','request','webpage', 'content', 'taxonomy', 'resource', 'translation', 'contact', 'location', 'channel', 'lang', 'token', 'message', 'ticket', 'user', 'permission', 'role'],
56
- 'company': ['alert','request','location', 'campaign', 'order', 'currency', 'product', 'cart', 'cartItem', 'invoice', 'message', 'user', 'role', 'permission', 'token','translation', 'lang', 'webpage', 'content', 'taxonomy', 'contact', 'resource', 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'kpi', 'dashboard'],
56
+ 'company': ['alert','request','location', 'order', 'currency', 'product', 'cart', 'cartItem', 'invoice', 'message', 'user', 'role', 'permission', 'token','translation', 'lang', 'webpage', 'content', 'taxonomy', 'contact', 'resource', 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'kpi', 'dashboard'],
57
57
  'engineer': ['alert','endpoint','request','dashboard', 'kpi', 'user', 'role', 'token', 'permission', 'workflow', 'workflowRun', 'workflowStep', "channel", "message", 'workflowAction', 'workflowTrigger']
58
58
  }
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "data-primals-engine",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
5
5
  "main": "src/engine.js",
6
6
  "type": "module",
package/src/constants.js CHANGED
@@ -66,7 +66,7 @@ export const awsDefaultConfig = {
66
66
  export const emailDefaultConfig = {
67
67
  from: "Support - data@primals.net <data@primals.net>",
68
68
  host: 'smtp.mydomain.tld',
69
- port: 2500,
69
+ port: 587,
70
70
  secure: false,
71
71
  user: 'user',
72
72
  pass: 'password'
@@ -288,7 +288,7 @@ metaModels['messaging'] = { load: ['alert','ticket', 'message', 'channel'], 'req
288
288
  metaModels['eshopping'] = { load: [
289
289
  'order', 'currency', 'product', 'productVariant', 'discount', 'cart', 'cartItem',
290
290
  'brand', 'return', 'review', 'stock', 'returnItem', 'userSubscription',
291
- 'warehouse', 'shipment', 'campaign', 'stockAlert', 'invoice'],
291
+ 'warehouse', 'shipment', 'stockAlert', 'invoice'],
292
292
  'require': ['i18n', 'users', 'messaging'] };
293
293
  metaModels['workflow'] = { load: ['env', 'workflow', 'workflowRun', 'workflowAction', 'workflowStep', 'workflowTrigger']};
294
294
  metaModels['erp'] = { load: [ 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'dashboard', 'kpi'] };
@@ -543,19 +543,6 @@ export const defaultModels = {
543
543
  { name: 'createdAt', type: 'datetime', required: true }
544
544
  ]
545
545
  },
546
- campaign: {
547
- name: 'campaign',
548
- "description": "",
549
- fields: [
550
- { name: 'name', type: 'string_t', required: true },
551
- { name: 'description', type: 'richtext' },
552
- { name: 'startDate', type: 'datetime' },
553
- { name: 'endDate', type: 'datetime' },
554
- { name: 'type', type: 'enum', items: ['email', 'sms', 'advertisement', 'promotion'] },
555
- { name: 'status', type: 'enum', items: ['planified', 'in_progress', 'finished', 'cancelled'] },
556
- { name: 'budget', type: 'number' }
557
- ]
558
- },
559
546
  review: {
560
547
  name: "review",
561
548
  "description": "",
@@ -1014,7 +1001,7 @@ export const defaultModels = {
1014
1001
  { name: 'owner', type: 'relation', relation: 'user', required: false },
1015
1002
  { name: 'startedAt', type: 'datetime', required: true, hint: "Timestamp when the workflow run began." },
1016
1003
  { name: 'completedAt', type: 'datetime', hint: "Timestamp when the workflow run finished (successfully or failed)." },
1017
- { name: 'error', type: 'string', maxlength: 4096, hint: "Error message if the workflow run failed." }
1004
+ { name: 'log', type: 'string', maxlength: 4096, hint: "Error message if the workflow run failed." }
1018
1005
  ]
1019
1006
  },
1020
1007
  dashboard:{
package/src/email.js CHANGED
@@ -6,12 +6,12 @@ import {emailDefaultConfig} from "./constants.js";
6
6
 
7
7
  // Le transporteur par défaut, utilisé si aucune config spécifique n'est fournie.
8
8
  const defaultTransporter = nodemailer.createTransport({
9
- host: "mail.smtp2go.com",
10
- port: 587,
9
+ host: process.env.SMTP_HOST,
10
+ port: process.env.SMTP_PORT || 587,
11
11
  secure: false,
12
12
  auth: {
13
- user: process.env.MAIL_USER,
14
- pass: process.env.MAIL_PASS
13
+ user: process.env.SMTP_USER,
14
+ pass: process.env.SMTP_PASS
15
15
  }
16
16
  });
17
17
 
@@ -52,7 +52,9 @@ export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl =
52
52
  // Choisir le transporteur à utiliser
53
53
  const transporter = smtpConfig ? createTransporter(smtpConfig||emailDefaultConfig) : defaultTransporter;
54
54
 
55
- if (tpl === null) tpl = Event.Trigger("sendEmail:template", "system", "calls", data, lang);
55
+ Event.Listen("OnEmailTemplate", (data, lang) => data.content, "event", "system");
56
+
57
+ if (tpl === null) tpl = Event.Trigger("OnEmailTemplate", "event", "system", data, lang);
56
58
  let html = tpl;
57
59
  try {
58
60
  html = juice(tpl);
package/src/engine.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  import http from "http";
14
14
  import cookieParser from "cookie-parser";
15
15
  import requestIp from 'request-ip';
16
- import {createModel, deleteModels, getModels, validateModelStructure} from "./modules/data.js";
16
+ import {createModel, deleteModels, getModels, installAllPacks, validateModelStructure} from "./modules/data.js";
17
17
  import {defaultModels} from "./defaultModels.js";
18
18
  import {DefaultUserProvider} from "./providers.js";
19
19
  import formidableMiddleware from 'express-formidable';
@@ -175,6 +175,7 @@ export const Engine = {
175
175
  server.listen(port);
176
176
 
177
177
  await setupInitialModels();
178
+ await installAllPacks();
178
179
 
179
180
  if (cb)
180
181
  await cb();
package/src/events.js CHANGED
@@ -60,7 +60,7 @@ export const Event = {
60
60
  }
61
61
  return ret;
62
62
  },
63
- Listen: (name = "", callback = () => {}, system = "priority", layer = "medium") => {
63
+ Listen: (name = "", callback, system = "priority", layer = "medium") => {
64
64
  const validSystems = Object.keys(eventLayerSystems); // Récupération des clés pour une vérification plus performante
65
65
  if (!validSystems.includes(system)) {
66
66
  throw new Error(`System '${system}' does not exist. Valid systems are: ${validSystems.join(', ')}`); // Message d'erreur plus informatif
package/src/filter.js ADDED
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Evaluates a single condition against form data.
3
+ * @param {object} currentModelDef - The definition of the current model.
4
+ * @param {object} condition - The condition to evaluate.
5
+ * @param {object} formData - The form data.
6
+ * @param {object[]} allModels - An array of all model definitions.
7
+ * @param {object} user - The current user.
8
+ * @returns {boolean} - True if the condition is met, false otherwise.
9
+ */
10
+ const evaluateSingleCondition = (currentModelDef, condition, formData, allModels, user) => {
11
+ // Condition est directement un filtre MongoDB, donc on l'applique
12
+ // en utilisant les opérateurs et les valeurs qu'il contient.
13
+
14
+ if (!condition || typeof condition !== 'object') {
15
+ console.warn("[Client Eval] Condition is not an object:", condition);
16
+ return true; // Permissive default
17
+ }
18
+
19
+ // Si la condition est de la forme {field: value}, on la transforme en {$eq: value}
20
+ if (!Object.keys(condition)[0].startsWith('$') && typeof condition[Object.keys(condition)[0]] !== 'object') {
21
+ const fieldName = Object.keys(condition)[0];
22
+ const value = condition[fieldName];
23
+ return evaluateSingleCondition(currentModelDef, {[fieldName]: {$eq: value}}, formData, allModels, user);
24
+ }
25
+
26
+ // Gestion des opérateurs d'agrégation (commencent par $)
27
+ const operator = Object.keys(condition)[0];
28
+ if (operator === '$eq' && Array.isArray(condition[operator])) {
29
+ // Cas spécial pour {$eq: ['$field', value]}
30
+ const [fieldPath, expectedValue] = condition[operator];
31
+ if (typeof fieldPath === 'string' && fieldPath.startsWith('$')) {
32
+ const fieldName = fieldPath.substring(1); // Enlève le $ devant
33
+ return formData[fieldName] == expectedValue;
34
+ }
35
+ }
36
+
37
+ // Si la condition contient des opérateurs logiques, on les gère ici
38
+ if (condition.$and || condition.$or || condition.$not || condition.$nor) {
39
+ console.warn("[Client Eval] Condition logique détectée dans evaluateSingleCondition, ce n'est pas attendu. Devrait être géré par isConditionMet.");
40
+ return true; // Permissive default
41
+ }
42
+
43
+ if (condition.$find) {
44
+ const fieldName = Object.keys(condition)[0];
45
+ const fieldValue = formData[fieldName];
46
+ const findCondition = condition.$find;
47
+
48
+ if (!Array.isArray(fieldValue)) return false;
49
+
50
+ return fieldValue.some(item => {
51
+ // Gestion spéciale pour la syntaxe $eq: ["$$this.field", value]
52
+ if (findCondition.$eq && Array.isArray(findCondition.$eq)) {
53
+ const [fieldPath, value] = findCondition.$eq;
54
+ if (fieldPath.startsWith("$$this.")) {
55
+ const fieldToCheck = fieldPath.replace("$$this.", "");
56
+ return item[fieldToCheck] == value;
57
+ }
58
+ }
59
+
60
+ // Sinon, évaluation normale
61
+ const tempData = { ...item };
62
+ return evaluateSingleCondition(currentModelDef, findCondition, tempData, allModels, user);
63
+ });
64
+ }
65
+
66
+ // Si la condition contient un opérateur $exists, on le gère ici
67
+ if (condition.$exists !== undefined) {
68
+ const fieldName = Object.keys(condition)[0]; // Récupérer le nom du champ
69
+ const shouldExist = condition.$exists; // Récupérer la valeur de $exists (true ou false)
70
+ const exists = Object.prototype.hasOwnProperty.call(formData, fieldName) && formData[fieldName] !== undefined && formData[fieldName] !== null;
71
+ return exists === shouldExist;
72
+ }
73
+
74
+ // Si la condition contient un opérateur $find, on le gère ici
75
+ if (condition.$find) {
76
+ // Récupérer le nom du champ
77
+ const fieldName = Object.keys(condition)[0];
78
+ const fieldValue = formData[fieldName];
79
+ try {
80
+ // Assuming evaluateSingleCondition handles $find
81
+ return evaluateSingleCondition(currentModelDef, condition.$find, formData, allModels, user);
82
+ } catch (error) {
83
+ console.error("Error evaluating $find condition:", condition, error);
84
+ return false;
85
+ }
86
+ }
87
+
88
+ // Récupérer le nom du champ et la condition
89
+ const fieldName = Object.keys(condition)[0];
90
+ const fieldValue = condition[fieldName];
91
+
92
+ // Récupérer la définition du champ
93
+ const fieldDef = currentModelDef?.fields.find(f => f.name === fieldName);
94
+
95
+ // Si la définition du champ n'est pas trouvée, on retourne true
96
+ if (!fieldDef) {
97
+ console.warn(`[Client Eval] Field definition not found for field: ${fieldName}`);
98
+ return true; // Permissive default
99
+ }
100
+
101
+ let targetValue = formData[fieldName];
102
+ let processedConditionValue = fieldValue;
103
+
104
+ // 1. Handle $exists (on the first field)
105
+ // 2. Convert condition value based on operator's expected input type
106
+ const fieldType = fieldDef?.type; // Type of the first field
107
+
108
+ try {
109
+ processedConditionValue = convertValueType(fieldValue, fieldType);
110
+ } catch (e) {
111
+ logClientEvalWarning(`Error converting value type: ${e.message}`, condition);
112
+ return false;
113
+ }
114
+
115
+ return evaluateComparison(fieldValue, targetValue, processedConditionValue, condition);
116
+
117
+ function logClientEvalWarning(message, details) {
118
+ console.warn(`[Client Eval] ${message}:`, details);
119
+ }
120
+
121
+ function convertValueType(value, inputType) {
122
+ switch (inputType) {
123
+ case 'number':
124
+ const numValue = parseFloat(value);
125
+ if (isNaN(numValue)) {
126
+ throw new Error(`Invalid number value: ${value}`);
127
+ }
128
+ return numValue;
129
+ case 'boolean':
130
+ return String(value).toLowerCase() === 'true';
131
+ case 'csv':
132
+ return String(value).split(',').map(item => item.trim()).filter(Boolean);
133
+ case 'text':
134
+ default:
135
+ return String(value);
136
+ }
137
+ }
138
+
139
+ function evaluateComparison(operator, targetValue, processedConditionValue, condition) {
140
+ try {
141
+ switch (typeof operator === 'object' ? Object.keys(operator)[0] : null) {
142
+ case '$eq': return targetValue == processedConditionValue;
143
+ case '$ne': return targetValue != processedConditionValue;
144
+ case '$gt': return targetValue > processedConditionValue;
145
+ case '$lt': return targetValue < processedConditionValue;
146
+ case '$gte': return targetValue >= processedConditionValue;
147
+ case '$lte': return targetValue <= processedConditionValue;
148
+ case '$regex':
149
+ if (typeof targetValue !== 'string') return false;
150
+ if (typeof processedConditionValue !== 'string') return false;
151
+ try {
152
+ const regex = new RegExp(processedConditionValue, 'i');
153
+ return regex.test(targetValue);
154
+ } catch (e) {
155
+ logClientEvalWarning(`Invalid regex pattern: ${processedConditionValue}`, condition);
156
+ return false;
157
+ }
158
+ case '$in':
159
+ return Array.isArray(processedConditionValue) && processedConditionValue.includes(String(targetValue));
160
+ case '$nin':
161
+ return !Array.isArray(processedConditionValue) || !processedConditionValue.includes(String(targetValue));
162
+ default:
163
+ logClientEvalWarning(`Unhandled operator in client evaluation logic: ${operator}`, condition);
164
+ return true; // Permissive default
165
+ }
166
+ } catch (evalError) {
167
+ logClientEvalWarning(`Error during client condition evaluation: ${operator}, targetValue=${targetValue}, processedConditionValue=${processedConditionValue}`, condition);
168
+ return false;
169
+ }
170
+ }
171
+ };
172
+
173
+ export const isConditionMet = (model, cond, formData, allModels, user) => {
174
+ const condition = cond;
175
+
176
+ if (!condition) return true;
177
+
178
+ // Cas 1: Condition simple {field: value} → transformée en {field: {$eq: value}}
179
+ if (typeof condition === 'object' && !Array.isArray(condition)) {
180
+ const keys = Object.keys(condition);
181
+ if (keys.length === 1 && !keys[0].startsWith('$') &&
182
+ typeof condition[keys[0]] !== 'object') {
183
+ const simpleCondition = {
184
+ [keys[0]]: {$eq: condition[keys[0]]}
185
+ };
186
+ return evaluateSingleCondition(model, simpleCondition, formData, allModels, user);
187
+ }
188
+ }
189
+
190
+ // Cas 2: Opérateurs logiques ($and, $or, $not, $nor)
191
+ if (condition.$and && Array.isArray(condition.$and)) {
192
+ if (condition.$and.length === 0) return true;
193
+ return condition.$and.every(sub => isConditionMet(model, sub, formData, allModels, user));
194
+ }
195
+
196
+ if (condition.$or && Array.isArray(condition.$or)) {
197
+ if (condition.$or.length === 0) return false;
198
+ return condition.$or.some(sub => isConditionMet(model, sub, formData, allModels, user));
199
+ }
200
+
201
+ if (condition.$not) {
202
+ return !isConditionMet(model, condition.$not, formData, allModels, user);
203
+ }
204
+
205
+ if (condition.$nor && Array.isArray(condition.$nor)) {
206
+ if (condition.$nor.length === 0) return true;
207
+ return !condition.$nor.some(sub => isConditionMet(model, sub, formData, allModels, user));
208
+ }
209
+
210
+ // Cas 3: Syntaxe d'agrégation {$eq: ['$field', value]}
211
+ if (Object.keys(condition).length === 1) {
212
+ const operator = Object.keys(condition)[0];
213
+ if (operator.startsWith('$') && operator !== '$find' &&
214
+ Array.isArray(condition[operator])) {
215
+ return evaluateSingleCondition(model, condition, formData, allModels, user);
216
+ }
217
+ }
218
+
219
+ // Cas 4: Tous les autres cas (conditions normales avec opérateurs)
220
+ return evaluateSingleCondition(model, condition, formData, allModels, user);
221
+ };
@@ -47,14 +47,14 @@ import {
47
47
  } from "./mongodb.js";
48
48
  import {dbUrl, MongoClient, MongoDatabase} from "../engine.js";
49
49
  import path from "node:path";
50
- import {getFileExtension, getObjectHash, getRandom, isGUID, isPlainObject, randomDate, uuidv4} from "../core.js";
50
+ import {getFileExtension, getObjectHash, getRandom, isGUID, isPlainObject, randomDate, sleep, uuidv4} from "../core.js";
51
51
  import {Event} from "../events.js";
52
52
  import fs from "node:fs";
53
53
  import schedule from "node-schedule";
54
54
  import {middleware} from "../middlewares/middleware-mongodb.js";
55
55
  import i18n from "../i18n.js";
56
56
  import {
57
- executeSafeJavascript,
57
+ executeSafeJavascript, processWorkflowRun,
58
58
  runScheduledJobWithDbLock,
59
59
  scheduleWorkflowTriggers,
60
60
  triggerWorkflows
@@ -94,7 +94,6 @@ const sseConnections = new Map();
94
94
  const delay = ms => new Promise(res => setTimeout(res, ms));
95
95
 
96
96
  const getBackupDir = () => process.env.BACKUP_DIR || './backups'; // Répertoire de stockage des sauvegardes
97
- const execAsync = promisify(exec);
98
97
  const execFileAsync = promisify(execFile);
99
98
 
100
99
  let importJobs = {};
@@ -1386,6 +1385,7 @@ export async function onInit(defaultEngine) {
1386
1385
  schedule.scheduleJob("0 2 * * *", jobDumpUserData);
1387
1386
  //await jobDumpUserData();
1388
1387
 
1388
+
1389
1389
  schedule.scheduleJob("0 0 * * *", async () => {
1390
1390
  const dt = new Date();
1391
1391
  dt.setTime(dt.getTime()-1000*3600*24*14);
@@ -1967,7 +1967,7 @@ export async function onInit(defaultEngine) {
1967
1967
  const filter = req.fields.filter;
1968
1968
  const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1969
1969
  const data = req.fields.data || (req.fields._data && JSON.parse(req.fields._data));
1970
- const r = await editData(req.fields.model, filter || hash, data, req.files, req.me)
1970
+ const r = await editData(req.fields.model, filter || { "$eq": ["$_id", { "$toObjectId": hash}]}, data, req.files, req.me)
1971
1971
  if (r.error)
1972
1972
  res.status(400).json(r);
1973
1973
  else
@@ -2889,14 +2889,14 @@ export const deleteModels = async (filter) => {
2889
2889
  }
2890
2890
 
2891
2891
  export const getModel = async (modelName, user) => {
2892
- const modelInCache = modelsCache.get(user.username+"@@"+modelName);
2892
+ const modelInCache = modelsCache.get((user?.username||'')+"@@"+modelName);
2893
2893
  if(modelInCache)
2894
2894
  return modelInCache;
2895
- const model = await getCollection('models').findOne({name: modelName, $and: [{_user: {$exists: true}}, {$or: [{_user: user._user}, {_user: user.username}]}]});
2895
+ const model = await getCollection('models').findOne({name: modelName, $and: user ? [{_user: {$exists: true}}, {$or: [{_user: user._user}, {_user: user.username}]}] : [{_user: { $exists: false}}]});
2896
2896
  if (!model) {
2897
2897
  throw new Error(i18n.t('api.model.notFound', {model: modelName}));
2898
2898
  }
2899
- modelsCache.set(user.username+"@@"+modelName, model);
2899
+ modelsCache.set((user?.username||'')+"@@"+modelName, model);
2900
2900
  return model;
2901
2901
  }
2902
2902
  export const getModels = async () => {
@@ -4135,7 +4135,7 @@ export const deleteData = async (modelName, filter, user ={}, triggerWorkflow, w
4135
4135
  export const searchData = async (query, user) => {
4136
4136
  const { page, limit, sort, model, ids, timeout, pack } = query; // Les filtres de la requête (attention aux injections MongoDB !)
4137
4137
 
4138
- if( user.username !== 'demo' && isLocalUser(user) && (
4138
+ if( user && user.username !== 'demo' && isLocalUser(user) && (
4139
4139
  !await hasPermission(["API_ADMIN", "API_SEARCH_DATA", "API_SEARCH_DATA_"+model], user) ||
4140
4140
  await hasPermission(["API_SEARCH_DATA_NOT_"+model], user))){
4141
4141
  throw new Error(i18n.t('api.permission.searchData'));
@@ -5988,4 +5988,4 @@ export async function handleDemoInitialization(req, res) {
5988
5988
  logger.error(`[Demo Init] Critical error during initialization for user '${user.username}':`, error);
5989
5989
  res.status(500).json({ success: false, error: 'An internal server error occurred during initialization.' });
5990
5990
  }
5991
- }
5991
+ }