data-primals-engine 1.2.3 → 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 (49) 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 -10
  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/filter.js +87 -0
  22. package/client/src/hooks/data.js +1 -3
  23. package/client/src/hooks/useTutorials.jsx +0 -1
  24. package/package.json +3 -3
  25. package/server.js +2 -2
  26. package/src/email.js +2 -2
  27. package/src/engine.js +59 -20
  28. package/src/index.js +1 -1
  29. package/src/middlewares/middleware-mongodb.js +0 -1
  30. package/src/modules/assistant.js +1 -3
  31. package/src/modules/bucket.js +3 -4
  32. package/src/modules/{data.js → data/data.js} +34 -51
  33. package/src/modules/data/index.js +1 -0
  34. package/src/modules/file.js +1 -1
  35. package/src/modules/mongodb.js +0 -1
  36. package/src/modules/user.js +1 -1
  37. package/src/modules/workflow.js +38 -38
  38. package/src/packs.js +4 -1
  39. package/test/data.backup.integration.test.js +4 -5
  40. package/test/data.integration.test.js +2 -6
  41. package/test/events.test.js +1 -1
  42. package/test/file.test.js +1 -4
  43. package/test/import_export.integration.test.js +20 -13
  44. package/test/model.integration.test.js +8 -10
  45. package/test/user.test.js +2 -2
  46. package/test/vm.test.js +1 -1
  47. package/test/workflow.integration.test.js +17 -14
  48. package/test/workflow.robustness.test.js +15 -10
  49. package/src/modules/test +0 -147
@@ -25,7 +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
+ import {isConditionMet} from "../../src/filter";
29
29
 
30
30
  // ... (fonction getInputType) ...
31
31
  // Fonction pour obtenir le type d'input HTML basé sur le type de champ du modèle
@@ -62,7 +62,6 @@ export const DataEditor = forwardRef(function MyDataEditor({
62
62
  formData,
63
63
  setFormData, record, setRecord}, ref){
64
64
 
65
- const [focusedField, setFocusedField] = useState({});
66
65
  const {me} = useAuthContext()
67
66
  const {models} = useModelContext()
68
67
 
@@ -103,7 +102,7 @@ export const DataEditor = forwardRef(function MyDataEditor({
103
102
  case 'textarea':
104
103
  return <textarea key={field.name} {...inputProps} />
105
104
  case 'richtext':
106
- 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} />;
107
106
  case 'richtext_t':
108
107
  return <RTETrans
109
108
  key={field.name}
@@ -144,7 +143,6 @@ export const DataEditor = forwardRef(function MyDataEditor({
144
143
  // C'est un nom de modèle statique
145
144
  builderModelName = field.targetModel;
146
145
  }
147
- console.log({builderModelName})
148
146
  }
149
147
  return <div className={"flex flex-1"} style={{width:'100%'}} key={field.name}>
150
148
  {currentViewMode !== 'builder' && ( <div className="condition-builder-toggle">
@@ -219,10 +217,10 @@ export const DataEditor = forwardRef(function MyDataEditor({
219
217
  inputProps["min"] = field.min;
220
218
  if( field.max)
221
219
  inputProps["max"] = field.max;
222
- 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(',', '.'))})} />
223
221
  case 'relation':
224
222
  return (
225
- <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) => {
226
224
  handleChange(e)
227
225
  }} refreshTime={refreshTime} />
228
226
  );
@@ -255,8 +253,8 @@ export const DataEditor = forwardRef(function MyDataEditor({
255
253
  const displayValue = (typeof value === 'object' && value !== null) ? value.key : (value || '');
256
254
 
257
255
  return <TextField
258
- help={focusedField?.name === field.name ? t('field_' + model.name + '_' + field.name + '_hint', field.hint || '') : ''}
259
- onFocus={() => setFocusedField(field)} onBlur={() => setFocusedField(null)} key={field.name}
256
+ help={t('field_' + model.name + '_' + field.name + '_hint', field.hint || '')}
257
+ key={field.name}
260
258
  type={getInputType(field.type)} {...inputProps}
261
259
  value={displayValue}
262
260
  onChange={(e) => handleChange({name: field.name, value: e.target.value})} />
@@ -266,9 +264,9 @@ export const DataEditor = forwardRef(function MyDataEditor({
266
264
  case 'color':
267
265
  return <ColorField help={t('field_'+model.name+'_'+field.name+'_hint', field.hint || '')} key={field.name} name={field.name} value={value} onChange={handleChange} />
268
266
  case 'email':
269
- 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})} />
270
268
  default:
271
- 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})} />
272
270
  }
273
271
  }
274
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;
@@ -57,7 +57,7 @@ import PackGallery from "./PackGallery.jsx";
57
57
  import {HiddenableCell} from "./HiddenableCell.jsx";
58
58
  import ConditionBuilder from "./ConditionBuilder.jsx";
59
59
  import {pagedFilterToMongoConds} from "./filter.js";
60
- import {isConditionMet} from "data-primals-engine/filter";
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;
@@ -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.3",
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/email.js CHANGED
@@ -75,9 +75,9 @@ export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl =
75
75
 
76
76
  try {
77
77
  await Promise.all(sendPromises);
78
- console.log(`Email(s) envoyé(s) avec succès à: ${emails.join(', ')}`);
78
+ console.log(`Email(s) sent successfully to : ${emails.join(', ')}`);
79
79
  } catch (error) {
80
- console.error("Erreur lors de l'envoi d'un ou plusieurs emails :", error);
80
+ console.error("Error when sending to one ore more emails :", error);
81
81
  // Vous pouvez relancer l'erreur si vous voulez que l'appelant la gère
82
82
  throw error;
83
83
  }
package/src/engine.js CHANGED
@@ -13,13 +13,15 @@ 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, installAllPacks, validateModelStructure} from "./modules/data.js";
16
+ import {createModel, deleteModels, getModels, installAllPacks, validateModelStructure} from "./modules/data/data.js";
17
17
  import {defaultModels} from "./defaultModels.js";
18
18
  import {DefaultUserProvider} from "./providers.js";
19
19
  import formidableMiddleware from 'express-formidable';
20
20
  import sirv from "sirv";
21
21
  import * as tls from "node:tls";
22
22
  import {Event} from "./events.js";
23
+ import path from "node:path";
24
+ import {isPathRelativeTo, isValidPath} from "./core.js";
23
25
 
24
26
  // Constants
25
27
  const isProduction = process.env.NODE_ENV === 'production'
@@ -49,23 +51,35 @@ const isTlsActive = !(!process.env.TLS || ["0", "false"].includes(process.env.TL
49
51
  const clientOptions = {
50
52
  maxPoolSize: databasePoolSize
51
53
  };
52
- // On ajoute les options TLS si elles sont activées
54
+
55
+ // We add TLS options if enabled
53
56
  if (isTlsActive) {
54
57
  clientOptions.tls = true;
55
- // Chemin vers le certificat de l'autorité de certification (pour faire confiance au serveur)
56
- if (process.env.CA_CERT) {
57
- clientOptions.tlsCAFile = process.env.CA_CERT;
58
- }
59
- // Chemin vers le certificat et la clé du CLIENT (pour que le serveur vous fasse confiance)
60
- if (process.env.CERT_KEY) {
61
- clientOptions.tlsCertificateKeyFile = process.env.CERT_KEY;
58
+
59
+ // is mTLS ? (client certificate required instead of password)
60
+ if (process.env.CERT) {
61
+ clientOptions.secureContext = tls.createSecureContext({
62
+ ca: fs.readFileSync(process.env.CA_CERT),
63
+ cert: fs.readFileSync(process.env.CERT),
64
+ key: fs.readFileSync(process.env.CERT_KEY)
65
+ });
66
+ }else {
67
+ // Path to the authority certificate
68
+ if (process.env.CA_CERT) {
69
+ clientOptions.tlsCAFile = process.env.CA_CERT;
70
+ }
71
+ // Path to the certificate key
72
+ if (process.env.CERT_KEY) {
73
+ clientOptions.tlsCertificateKeyFile = process.env.CERT_KEY;
74
+ }
62
75
  }
63
- // Options pour le développement (à utiliser avec prudence)
64
76
  if (tlsAllowInvalidCertificates) {
65
77
  clientOptions.tlsAllowInvalidCertificates = true;
78
+ console.warn("🚨 [SECURITY WARNING] tlsAllowInvalidCertificates is ON. Server certificate will not be validated.");
66
79
  }
67
80
  if (tlsAllowInvalidHostnames) {
68
81
  clientOptions.tlsAllowInvalidHostnames = true;
82
+ console.warn("🚨 [SECURITY WARNING] tlsAllowInvalidHostnames is ON. Server hostname will not be validated.");
69
83
  }
70
84
  }
71
85
 
@@ -143,18 +157,44 @@ export const Engine = {
143
157
  }
144
158
  };
145
159
 
146
- await Promise.all(Config.Get('modules', []).map(async module => {
160
+ await Promise.all(Config.Get('modules', []).map(async moduleIdentifier => {
147
161
  try {
148
- if( fs.existsSync(module) ){
149
- return await importModule(module);
150
- }else {
151
- return await importModule('./modules/' + module + ".js");
162
+ let moduleDir;
163
+ const moduleName = path.basename(moduleIdentifier);
164
+
165
+ const directPath = path.resolve(moduleIdentifier);
166
+ let isDir = fs.existsSync(directPath) && fs.statSync(directPath).isDirectory();
167
+ if (isDir) {
168
+ moduleDir = directPath;
169
+ if (!fs.existsSync(moduleDir) || !fs.statSync(moduleDir).isDirectory()) {
170
+ logger.warn(`Le dossier du module est introuvable pour l'identifiant : '${moduleIdentifier}'. Chemin cherché : '${moduleDir}'.`);
171
+ return null;
172
+ }
173
+ } else {
174
+ moduleDir = path.resolve('./src/modules', moduleIdentifier);
175
+ }
176
+
177
+ let moduleEntryPoint;
178
+ const jsPath = moduleDir+'.js';
179
+ const indexJsPath = path.join(moduleDir, 'index.js');
180
+ const moduleJsPath = path.join(moduleDir, `${moduleName}.js`);
181
+
182
+ if (fs.existsSync(jsPath)) {
183
+ moduleEntryPoint = 'file://'+jsPath;
184
+ } else if (fs.existsSync(indexJsPath)) {
185
+ moduleEntryPoint = 'file://'+indexJsPath;
186
+ } else if (fs.existsSync(moduleJsPath)) {
187
+ moduleEntryPoint = 'file://'+moduleJsPath;
152
188
  }
153
- } catch (e){
154
- logger.info('ERROR at loading module '+ module, e.stack);
189
+
190
+ return await importModule(moduleEntryPoint);
191
+ } catch (e) {
192
+ logger.error(`Échec du chargement du module '${moduleIdentifier}':`, e.stack);
193
+ return null;
155
194
  }
156
- })).then(async e => {
157
- engine._modules = e;
195
+ })).then(async results => {
196
+ // On filtre les modules qui n'ont pas pu être chargés
197
+ engine._modules = results.filter(Boolean);
158
198
  return Promise.resolve();
159
199
  });
160
200
 
@@ -239,4 +279,3 @@ export const Engine = {
239
279
  return engine;
240
280
  }
241
281
  }
242
-
package/src/index.js CHANGED
@@ -10,6 +10,6 @@ export { UserProvider } from './providers.js';
10
10
 
11
11
  // --- Database & Data Modules ---
12
12
  export { datasCollection, filesCollection, modelsCollection, packsCollection } from './modules/mongodb.js';
13
- export { searchData, insertData, editData, exportData, importData, scheduleAlerts, cancelAlerts, validateModelStructure, installPack, jobDumpUserData, loadFromDump, dumpUserData, validateRestoreRequest, patchData, deleteData, createModel, editModel, deleteModels, getModel, getModels } from './modules/data.js';
13
+ export { searchData, insertData, editData, exportData, importData, scheduleAlerts, cancelAlerts, validateModelStructure, installPack, jobDumpUserData, loadFromDump, dumpUserData, validateRestoreRequest, patchData, deleteData, createModel, editModel, deleteModels, getModel, getModels } from './modules/data/index.js';
14
14
 
15
15
 
@@ -60,7 +60,6 @@ function _sanitize(target, options) {
60
60
 
61
61
  if( (key === 'regex' && obj.input) || key === '$regex' ){
62
62
  obj[key] = new RegExp(val, "ui");
63
- console.log('regex san', obj[key]);
64
63
  }
65
64
  else if (!wl.includes(key) && regex.test(key)) {
66
65
  isSanitized = true;
@@ -1,11 +1,9 @@
1
- // server/src/modules/assistant.js
2
-
3
1
  import { getCollectionForUser, modelsCollection } from "./mongodb.js";
4
2
  import { Logger } from "../gameObject.js";
5
3
  import { ChatOpenAI } from "@langchain/openai";
6
4
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
7
5
  import { HumanMessage, SystemMessage } from "@langchain/core/messages";
8
- import {searchData, patchData, deleteData, insertData} from "./data.js";
6
+ import {searchData, patchData, deleteData, insertData} from "./data/index.js";
9
7
  import { getDataAsString } from "../data.js";
10
8
  import i18n from "../../src/i18n.js";
11
9
  import {generateLimiter} from "./user.js";
@@ -3,7 +3,7 @@ import AWS from "aws-sdk";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import {decryptValue, encryptValue} from "../data.js";
6
- import {loadFromDump, validateRestoreRequest} from "./data.js";
6
+ import {loadFromDump, validateRestoreRequest} from "./data/index.js";
7
7
  import {Logger} from "../gameObject.js";
8
8
  import {middlewareAuthenticator, userInitiator} from "./user.js";
9
9
  import {awsDefaultConfig, maxBytesPerSecondThrottleData} from "../constants.js";
@@ -117,7 +117,6 @@ export async function getUserS3Config(user) {
117
117
  bucketName: userConfig.bucketName || defaultConfig.bucketName
118
118
  };
119
119
  }
120
- console.log({defaultConfig})
121
120
  return defaultConfig;
122
121
  }
123
122
  const getS3Client = (s3Config) => {
@@ -212,9 +211,9 @@ export const deleteFromS3 = async (s3Config, remoteFilename) => {
212
211
 
213
212
  try {
214
213
  await s3.deleteObject(params).promise();
215
- console.log(`Fichier supprimé avec succès de S3 : ${bucketPath}`);
214
+ console.log(`File successfully removed from S3 : ${bucketPath}`);
216
215
  } catch (err) {
217
- console.error("Erreur lors de la suppression sur S3 :", err);
216
+ console.error("Error when deleting S3 file :", err);
218
217
  throw err;
219
218
  }
220
219
  };