data-primals-engine 1.2.2 → 1.2.4

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 (54) hide show
  1. package/CONTRIBUTING.md +91 -0
  2. package/README.md +33 -17
  3. package/client/src/App.jsx +0 -5
  4. package/client/src/ConditionBuilder.scss +34 -1
  5. package/client/src/ConditionBuilder2.jsx +179 -53
  6. package/client/src/ContentView.jsx +0 -3
  7. package/client/src/CronBuilder.jsx +0 -1
  8. package/client/src/CronPartBuilder.jsx +0 -2
  9. package/client/src/DashboardView.jsx +0 -5
  10. package/client/src/DataEditor.jsx +8 -211
  11. package/client/src/DataLayout.jsx +0 -1
  12. package/client/src/DataTable.jsx +1 -3
  13. package/client/src/Field.jsx +0 -5
  14. package/client/src/FlexBuilder.jsx +1 -1
  15. package/client/src/ModelCreatorField.jsx +1 -5
  16. package/client/src/RTE.jsx +1 -6
  17. package/client/src/RTETrans.jsx +0 -2
  18. package/client/src/RelationField.jsx +1 -1
  19. package/client/src/RelationValue.jsx +1 -2
  20. package/client/src/TourSpotlight.jsx +0 -2
  21. package/client/src/constants.js +1 -1
  22. package/client/src/filter.js +87 -0
  23. package/client/src/hooks/data.js +1 -3
  24. package/client/src/hooks/useTutorials.jsx +0 -1
  25. package/package.json +3 -3
  26. package/server.js +2 -2
  27. package/src/constants.js +2 -2
  28. package/src/defaultModels.js +1 -14
  29. package/src/email.js +9 -7
  30. package/src/engine.js +60 -20
  31. package/src/events.js +1 -1
  32. package/src/filter.js +221 -0
  33. package/src/index.js +1 -1
  34. package/src/middlewares/middleware-mongodb.js +0 -1
  35. package/src/modules/assistant.js +1 -3
  36. package/src/modules/bucket.js +3 -4
  37. package/src/modules/{data.js → data/data.js} +42 -59
  38. package/src/modules/data/index.js +1 -0
  39. package/src/modules/file.js +1 -1
  40. package/src/modules/mongodb.js +0 -1
  41. package/src/modules/user.js +1 -1
  42. package/src/modules/workflow.js +299 -133
  43. package/src/packs.js +249 -8
  44. package/test/data.backup.integration.test.js +7 -5
  45. package/test/data.integration.test.js +8 -6
  46. package/test/events.test.js +1 -1
  47. package/test/file.test.js +11 -17
  48. package/test/import_export.integration.test.js +38 -27
  49. package/test/model.integration.test.js +20 -21
  50. package/test/user.test.js +32 -25
  51. package/test/vm.test.js +51 -0
  52. package/test/workflow.integration.test.js +22 -14
  53. package/test/workflow.robustness.test.js +19 -9
  54. package/src/modules/test +0 -147
@@ -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 "../../src/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,
@@ -263,7 +62,6 @@ export const DataEditor = forwardRef(function MyDataEditor({
263
62
  formData,
264
63
  setFormData, record, setRecord}, ref){
265
64
 
266
- const [focusedField, setFocusedField] = useState({});
267
65
  const {me} = useAuthContext()
268
66
  const {models} = useModelContext()
269
67
 
@@ -304,7 +102,7 @@ export const DataEditor = forwardRef(function MyDataEditor({
304
102
  case 'textarea':
305
103
  return <textarea key={field.name} {...inputProps} />
306
104
  case 'richtext':
307
- return <RTE help={focusedField?.name === field.name ? t('field_'+model.name+'_'+field.name+'_hint', field.hint || '') : ''} onFocus={() => setFocusedField(field)} onBlur={() => setFocusedField(null)} key={field.name} {...inputProps} field={field} name={formData._id} />;
105
+ return <RTE help={t('field_'+model.name+'_'+field.name+'_hint', field.hint || '')} key={field.name} {...inputProps} field={field} name={formData._id} />;
308
106
  case 'richtext_t':
309
107
  return <RTETrans
310
108
  key={field.name}
@@ -345,7 +143,6 @@ export const DataEditor = forwardRef(function MyDataEditor({
345
143
  // C'est un nom de modèle statique
346
144
  builderModelName = field.targetModel;
347
145
  }
348
- console.log({builderModelName})
349
146
  }
350
147
  return <div className={"flex flex-1"} style={{width:'100%'}} key={field.name}>
351
148
  {currentViewMode !== 'builder' && ( <div className="condition-builder-toggle">
@@ -420,10 +217,10 @@ export const DataEditor = forwardRef(function MyDataEditor({
420
217
  inputProps["min"] = field.min;
421
218
  if( field.max)
422
219
  inputProps["max"] = field.max;
423
- return <NumberField help={focusedField?.name === field.name ? t('field_'+model.name+'_'+field.name+'_hint', field.hint || '') : ''} onFocus={() => setFocusedField(field)} onBlur={() => setFocusedField(null)} unit={field.unit} key={field.name} {...inputProps} onChange={(e) => handleChange({name: field.name, value: parseFloat(e.target.value.replace(',', '.'))})} />
220
+ return <NumberField help={t('field_'+model.name+'_'+field.name+'_hint', field.hint || '')} unit={field.unit} key={field.name} {...inputProps} onChange={(e) => handleChange({name: field.name, value: parseFloat(e.target.value.replace(',', '.'))})} />
424
221
  case 'relation':
425
222
  return (
426
- <RelationField onFocus={() => setFocusedField(field)} onBlur={() => setFocusedField(null)} help={focusedField?.name === field.name ? t('field_'+model.name+'_'+field.name+'_hint', field.hint || '') : ''} key={field.name} model={model} field={field} value={value} onChange={(e) => {
223
+ <RelationField help={t('field_'+model.name+'_'+field.name+'_hint', field.hint || '')} key={field.name} model={model} field={field} value={value} onChange={(e) => {
427
224
  handleChange(e)
428
225
  }} refreshTime={refreshTime} />
429
226
  );
@@ -456,8 +253,8 @@ export const DataEditor = forwardRef(function MyDataEditor({
456
253
  const displayValue = (typeof value === 'object' && value !== null) ? value.key : (value || '');
457
254
 
458
255
  return <TextField
459
- help={focusedField?.name === field.name ? t('field_' + model.name + '_' + field.name + '_hint', field.hint || '') : ''}
460
- onFocus={() => setFocusedField(field)} onBlur={() => setFocusedField(null)} key={field.name}
256
+ help={t('field_' + model.name + '_' + field.name + '_hint', field.hint || '')}
257
+ key={field.name}
461
258
  type={getInputType(field.type)} {...inputProps}
462
259
  value={displayValue}
463
260
  onChange={(e) => handleChange({name: field.name, value: e.target.value})} />
@@ -467,9 +264,9 @@ export const DataEditor = forwardRef(function MyDataEditor({
467
264
  case 'color':
468
265
  return <ColorField help={t('field_'+model.name+'_'+field.name+'_hint', field.hint || '')} key={field.name} name={field.name} value={value} onChange={handleChange} />
469
266
  case 'email':
470
- return <EmailField onFocus={() => setFocusedField(field)} onBlur={() => setFocusedField(null)} help={focusedField?.name === field.name ? t('field_'+model.name+'_'+field.name+'_hint', field.hint || '') : ''} key={field.name} type={getInputType(field.type)} {...inputProps} onChange={(e) => handleChange({name: field.name, value: e.target.value})} />
267
+ return <EmailField help={t('field_'+model.name+'_'+field.name+'_hint', field.hint || '')} key={field.name} type={getInputType(field.type)} {...inputProps} onChange={(e) => handleChange({name: field.name, value: e.target.value})} />
471
268
  default:
472
- return <TextField onFocus={() => setFocusedField(field)} onBlur={() => setFocusedField(null)} help={focusedField?.name === field.name ? t('field_'+model.name+'_'+field.name+'_hint', field.hint || '') : ''} key={field.name} type={getInputType(field.type)} {...inputProps} onChange={(e) => handleChange({name: field.name, value: e.target.value})} />
269
+ return <TextField help={t('field_'+model.name+'_'+field.name+'_hint', field.hint || '')} key={field.name} type={getInputType(field.type)} {...inputProps} onChange={(e) => handleChange({name: field.name, value: e.target.value})} />
473
270
  }
474
271
  }
475
272
 
@@ -97,7 +97,6 @@ function DataLayout() {
97
97
 
98
98
  // --- MODIFICATION : Logique de changement de vue mise à jour ---
99
99
  const handleSwitchView = (viewName) => {
100
- console.log({viewName})
101
100
  if (viewName === 'table') {
102
101
  setCurrentView('table');
103
102
  return;
@@ -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 "../../src/filter";
61
61
 
62
62
  // Ajoutez cette constante pour la clé de sessionStorage
63
63
  const SESSION_STORAGE_IMPORT_JOBS_KEY = 'activeImportJobs';
@@ -685,10 +685,8 @@ export function DataTable({
685
685
  />
686
686
  <ExportDialog isOpen={showExportDialog} onClose={() => {
687
687
  setExportDialogVisible(false);
688
- console.log("close")
689
688
  }} availableModels={models} currentModel={selectedModel.name} hasSelection={true} onExport={(data)=>{
690
689
  exportMutation(data);
691
- console.log(data);
692
690
  }} />
693
691
  {showPackGallery && (
694
692
  <Dialog isClosable={true} isModal={true} onClose={() => setShowPackGallery(false)}>
@@ -33,9 +33,7 @@ import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
33
33
 
34
34
  import { PhoneInput } from 'react-international-phone';
35
35
  import 'react-international-phone/style.css';
36
- import CodeMirror, {basicSetup} from "@uiw/react-codemirror";
37
36
  import {useAuthContext} from "./contexts/AuthContext.jsx";
38
- import {maxStringLength} from "data-primals-engine/constants";
39
37
 
40
38
  export const Form = ({
41
39
  name,
@@ -893,7 +891,6 @@ const FileField = ({ inputProps, value, onChange, name, mimeTypes, maxSize, mult
893
891
  }
894
892
  }, [value]);
895
893
 
896
- console.log(fileInfos)
897
894
  return (
898
895
  <div className="field field-file">
899
896
  <input
@@ -1421,12 +1418,10 @@ export const ModelField = ({field, disableable=false, showModel=true, value, fie
1421
1418
  const itemsFields = [...models.find(f=>f.name === modelValue && me?.username === f._user)?.fields.map(m => ({label: m.name, value: m.name})) || []];
1422
1419
 
1423
1420
  useEffect(() => {
1424
- console.log({value})
1425
1421
  setModelValue(value)
1426
1422
  }, [value]);
1427
1423
 
1428
1424
  useEffect(() => {
1429
- console.log(modelValue)
1430
1425
  onChange({name: field.name, value: modelValue});
1431
1426
  }, [modelValue]);
1432
1427
 
@@ -19,7 +19,7 @@ import {
19
19
  clearMappingsRecursive
20
20
  } from './FlexTreeUtils.js';
21
21
  import {Dialog, DialogProvider} from "./Dialog.jsx";
22
- import {safeAssignObject} from "data-primals-engine/core";
22
+ import {safeAssignObject} from "../../src/core";
23
23
 
24
24
  const FlexBuilder = ({ initialConfig = null, models = [], onChange, data = [], lang = 'fr' }) => {
25
25
  const { me: user } = useAuthContext();
@@ -43,7 +43,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
43
43
  const [showMore, setMoreVisible] = useState(false);
44
44
  const hint = (description) => t(description, '') && <div className="hint-icon"><FaCircleInfo data-tooltip-id={`tooltipHint`} data-tooltip-content={description} /></div>
45
45
 
46
- const [focusedField, setFocusedField] = useState({});
47
46
  return (
48
47
  <div className="field-edit">
49
48
 
@@ -73,9 +72,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
73
72
  newFields[index].name = e.target.value;
74
73
  setFields(newFields);
75
74
  }}
76
- help={focusedField?.name === field.name && t('modelcreator.name.hint')}
77
- onFocus={() => setFocusedField(field)}
78
- onBlur={() => setFocusedField(null)}
75
+ help={t('modelcreator.name.hint')}
79
76
  required
80
77
  />
81
78
  {!(!modelLocked && isLocalUser(me) && field.locked) && !field._isNewField && (
@@ -206,7 +203,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
206
203
  initialSteps={field.calculation?.steps || []}
207
204
  onCalculationChange={(calc) => {
208
205
  const newFields = [...fields];
209
- console.log({calc});
210
206
  newFields[index].calculation = calc; // 'index' est l'index du champ calculé dans 'fields'
211
207
  setFields(newFields);
212
208
  }}
@@ -28,7 +28,7 @@ import css from 'highlight.js/lib/languages/css'
28
28
  import js from 'highlight.js/lib/languages/javascript'
29
29
  import ts from 'highlight.js/lib/languages/typescript'
30
30
  import html from 'highlight.js/lib/languages/xml'
31
- import {escapeHtml, escapeRegex} from "data-primals-engine/core";
31
+ import {escapeHtml} from "../../src/core";
32
32
  // create a lowlight instance
33
33
  const lowlight = createLowlight(all)
34
34
 
@@ -293,11 +293,6 @@ export const ExtendedImage = Image.extend({
293
293
  }
294
294
  })
295
295
 
296
- const CodeBlockComponent = ({children, ...rest}) => {
297
- console.log(rest);
298
- return children;
299
- };
300
-
301
296
  const extensions = [
302
297
  HardBreak,
303
298
  CodeBlock,
@@ -1,8 +1,6 @@
1
- // Dans C:/Dev/hackersonline-engine/client/src/RTETrans.jsx (Nouveau fichier)
2
1
  import React, { useState, useEffect } from 'react';
3
2
  import { useTranslation } from 'react-i18next';
4
3
  import { RTE } from './RTE.jsx';
5
- import {useModelContext} from "./contexts/ModelContext.jsx";
6
4
  import {useQuery} from "react-query";
7
5
  import {getUserId} from "../../src/data.js";
8
6
  import {useAuthContext} from "./contexts/AuthContext.jsx";
@@ -3,7 +3,7 @@ import { useQuery, useQueryClient } from 'react-query';
3
3
  import { useModelContext } from './contexts/ModelContext.jsx';
4
4
  import { TextField } from './Field.jsx';
5
5
  import { useAuthContext } from './contexts/AuthContext.jsx';
6
- import { getDataAsString, getUserId } from 'data-primals-engine/data';
6
+ import { getDataAsString } from '../../src/data.js';
7
7
  import { FaEdit, FaTrash } from 'react-icons/fa';
8
8
  import Button from './Button.jsx';
9
9
  import { mainFieldsTypes } from "../../src/constants.js";
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import { useQuery } from 'react-query';
3
3
  import { useModelContext } from './contexts/ModelContext.jsx';
4
- import {getDataAsString, getUserId} from 'data-primals-engine/data';
4
+ import {getDataAsString, getUserId} from '../../src/data.js';
5
5
  import { Trans, useTranslation } from 'react-i18next';
6
6
  import {Dialog, DialogProvider} from './Dialog.jsx';
7
7
  import {FaMagnifyingGlass} from "react-icons/fa6";
@@ -133,7 +133,6 @@ const RelationValue = ({ field, data, align }) => {
133
133
  } catch (e) {
134
134
  console.log(e);
135
135
  }
136
- //console.log(model, rel, displayValue)
137
136
  const bgColor = // Returns a bright color in RGB
138
137
  randomColor({
139
138
  seed: rel._hash+rel._id,
@@ -61,8 +61,6 @@ function TourSpotlight({ name, steps = [], isOpen, onComplete, onClose, initialS
61
61
  useEffect(() => {
62
62
  if (!isOpen || !name) return;
63
63
 
64
-
65
- console.log({currentStep})
66
64
  if (!currentStep?.selector) {
67
65
  console.warn(`[TourSpotlight] Élément cible non défini pour le tour ${name}`);
68
66
  return;
@@ -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
 
@@ -62,6 +62,93 @@ export const MONGO_CALC_OPERATORS = {
62
62
  $minute: { label: 'minute', multi: false, isDate: true },
63
63
  $second: { label: 'second', multi: false, isDate: true },
64
64
  $millisecond: { label: 'ms', multi: false, isDate: true },
65
+ // Date operators
66
+ $dateAdd: {
67
+ label: 'Add to date',
68
+ description: 'Adds a duration to a date',
69
+ isDate: true,
70
+ specialStructure: true,
71
+ args: [
72
+ { name: 'startDate', label: 'Start Date', type: 'date' },
73
+ { name: 'unit', label: 'Unit', type: 'select', options: ['year', 'month', 'day', 'hour', 'minute', 'second'] },
74
+ { name: 'amount', label: 'Amount', type: 'number' },
75
+ { name: 'timezone', label: 'Timezone', type: 'text', optional: true }
76
+ ]
77
+ },
78
+ $dateSubtract: {
79
+ label: 'Subtract from date',
80
+ description: 'Subtracts a duration from a date',
81
+ isDate: true,
82
+ specialStructure: true,
83
+ args: [
84
+ { name: 'startDate', label: 'Start Date', type: 'date' },
85
+ { name: 'unit', label: 'Unit', type: 'select', options: ['year', 'month', 'day', 'hour', 'minute', 'second'] },
86
+ { name: 'amount', label: 'Amount', type: 'number' },
87
+ { name: 'timezone', label: 'Timezone', type: 'text', optional: true }
88
+ ]
89
+ },
90
+ $dateDiff: {
91
+ label: 'Date Difference',
92
+ description: 'Calculates the difference between two dates in specified units',
93
+ isDate: true,
94
+ specialStructure: true,
95
+ args: [
96
+ {
97
+ name: 'startDate',
98
+ label: 'Start Date',
99
+ type: 'date',
100
+ description: 'The starting date (inclusive)'
101
+ },
102
+ {
103
+ name: 'endDate',
104
+ label: 'End Date',
105
+ type: 'date',
106
+ description: 'The ending date (exclusive)'
107
+ },
108
+ {
109
+ name: 'unit',
110
+ label: 'Unit',
111
+ type: 'select',
112
+ options: ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
113
+ description: 'The unit for the result'
114
+ },
115
+ {
116
+ name: 'timezone',
117
+ label: 'Timezone',
118
+ type: 'text',
119
+ optional: true,
120
+ description: 'The timezone (e.g. "Europe/Paris")'
121
+ }
122
+ ]
123
+ },
124
+ $dateToString: {
125
+ label: 'Format date as string',
126
+ description: 'Formats a date as a string',
127
+ isDate: true,
128
+ specialStructure: true,
129
+ args: [
130
+ {
131
+ name: 'date',
132
+ label: 'Date',
133
+ type: 'date'
134
+ },
135
+ {
136
+ name: 'format',
137
+ label: 'Format',
138
+ optional: true,
139
+ type: 'text'
140
+ },
141
+ {
142
+ name: 'timezone',
143
+ label: 'Timezone',
144
+ type: 'text',
145
+ optional: true,
146
+ description: 'The timezone (e.g. "Europe/Paris")'
147
+ }
148
+ ]
149
+ },
150
+
151
+ // Converters
65
152
  $toBool: { label: 'toBool', multi: false, converter: true },
66
153
  $toString: { label: 'toString', multi: false, converter: true },
67
154
  $toInt: { label: 'toInt', multi: false, converter: true },
@@ -1,10 +1,8 @@
1
1
  import {useQuery} from "react-query";
2
- import {getObjectHash} from "data-primals-engine/core";
3
2
  import {useAuthContext} from "../contexts/AuthContext.jsx";
4
- import {getUserHash, getUserId} from "../../../src/data";
3
+ import {getUserId} from "../../../src/data";
5
4
  import {useTranslation} from "react-i18next";
6
5
  import {useEffect, useState} from "react";
7
- import {useUI} from "../contexts/UIContext.jsx";
8
6
  import {useNotificationContext} from "../NotificationProvider.jsx";
9
7
 
10
8
  export const useData = (model, filter, options) => {
@@ -138,7 +138,6 @@ export const useTutorials = () => {
138
138
  const tutorial = tutorials.find(t => t._id === currentActiveState.id);
139
139
  if (!tutorial) break;
140
140
 
141
- console.log({currentActiveState})
142
141
  const stageConfig = tutorial.stages.find(s => s.stage === currentActiveState.stage);
143
142
  if (!stageConfig) {
144
143
  console.log('[Tutoriels] Fin du tutoriel, attribution des récompenses.');
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.4",
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",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "exports": {
41
41
  ".": "./src/index.js",
42
- "./modules/*": "./src/modules/*.js",
42
+ "./modules/*": "./src/modules/*/index.js",
43
43
  "./client": "./client/index.js",
44
44
  "./*": "./src/*.js"
45
45
  },
@@ -50,6 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@langchain/core": "^0.3.66",
53
+ "@langchain/deepseek": "^0.1.0",
53
54
  "@langchain/google-genai": "^0.2.16",
54
55
  "@langchain/openai": "^0.6.3",
55
56
  "archiver": "^7.0.1",
@@ -62,7 +63,6 @@
62
63
  "cookie-parser": "^1.4.7",
63
64
  "cronstrue": "^3.2.0",
64
65
  "csv-parse": "^6.1.0",
65
- "data-primals-engine": "^1.0.14",
66
66
  "date-fns": "^4.1.0",
67
67
  "express-csrf-double-submit-cookie": "^2.0.0",
68
68
  "express-formidable": "^1.2.0",
package/server.js CHANGED
@@ -8,7 +8,7 @@ import {Config, Engine, BenchmarkTool, GameObject, Logger} from "./src/index.js"
8
8
  import sirv from "sirv";
9
9
  import express from "express";
10
10
 
11
- Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant", "swagger"])
11
+ Config.Set("modules", ["data", "mongodb", "file", "bucket", "workflow","user", "assistant", "swagger"])
12
12
  Config.Set("middlewares", []);
13
13
 
14
14
  const bench = GameObject.Create("Benchmark");
@@ -30,7 +30,7 @@ if (process.argv.length === 3) {
30
30
  const port = process.env.PORT || 7633;
31
31
  engine.start(port, async (r) => {
32
32
  const logger = engine.getComponent(Logger);
33
- console.log("Server started on port" + port);
33
+ console.log("Server started on port " + port);
34
34
  timer.stop();
35
35
 
36
36
  });
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'] };