data-primals-engine 1.0.11 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/client/README.md +8 -0
  2. package/client/index.html +31 -0
  3. package/client/index.js +25 -0
  4. package/client/package-lock.json +11664 -0
  5. package/client/package.json +70 -0
  6. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  7. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  8. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  9. package/client/public/doc/API.postman_collection.json +214 -0
  10. package/client/public/github.svg +1 -0
  11. package/client/public/profilCompany.jpg +0 -0
  12. package/client/public/profilDeveloper.jpg +0 -0
  13. package/client/public/profilEngineer.jpg +0 -0
  14. package/client/public/profilPersonal.jpg +0 -0
  15. package/client/public/profils .psd +0 -0
  16. package/client/public/react.svg +9 -0
  17. package/client/src/APIInfo.jsx +328 -0
  18. package/client/src/AddWidgetTypeModal.jsx +44 -0
  19. package/client/src/AddWidgetTypeModal.scss +87 -0
  20. package/client/src/App.css +42 -0
  21. package/client/src/App.jsx +586 -0
  22. package/client/src/App.scss +1466 -0
  23. package/client/src/AssistantChat.jsx +280 -0
  24. package/client/src/AssistantChat.scss +229 -0
  25. package/client/src/Button.jsx +12 -0
  26. package/client/src/Button.scss +197 -0
  27. package/client/src/CalculationBuilder.jsx +338 -0
  28. package/client/src/CalculationStepRow.jsx +199 -0
  29. package/client/src/CalendarConfigModal.jsx +49 -0
  30. package/client/src/ChartConfigModal.jsx +402 -0
  31. package/client/src/ConditionBuilder.jsx +622 -0
  32. package/client/src/ConditionBuilder.scss +672 -0
  33. package/client/src/ConditionBuilder2.jsx +757 -0
  34. package/client/src/ContentView.jsx +274 -0
  35. package/client/src/CronBuilder.jsx +126 -0
  36. package/client/src/CronBuilder.scss +128 -0
  37. package/client/src/CronPartBuilder.jsx +150 -0
  38. package/client/src/Dashboard.jsx +210 -0
  39. package/client/src/Dashboard.scss +445 -0
  40. package/client/src/DashboardChart.jsx +362 -0
  41. package/client/src/DashboardFlexViewItem.jsx +129 -0
  42. package/client/src/DashboardView.jsx +554 -0
  43. package/client/src/DataChart.jsx +349 -0
  44. package/client/src/DataEditor.jsx +556 -0
  45. package/client/src/DataLayout.jsx +709 -0
  46. package/client/src/DataTable.jsx +996 -0
  47. package/client/src/DataTable.scss +73 -0
  48. package/client/src/Dialog.jsx +90 -0
  49. package/client/src/Dialog.scss +117 -0
  50. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  51. package/client/src/DocumentationPageLayout.jsx +93 -0
  52. package/client/src/DocumentationPageLayout.scss +262 -0
  53. package/client/src/Draggable.jsx +222 -0
  54. package/client/src/Draggable.scss +49 -0
  55. package/client/src/ExportDialog.jsx +172 -0
  56. package/client/src/Field.jsx +1558 -0
  57. package/client/src/FlexBuilder.jsx +269 -0
  58. package/client/src/FlexBuilder.scss +226 -0
  59. package/client/src/FlexBuilderControls.jsx +170 -0
  60. package/client/src/FlexBuilderModal.jsx +75 -0
  61. package/client/src/FlexBuilderModal.scss +109 -0
  62. package/client/src/FlexBuilderPreview.jsx +48 -0
  63. package/client/src/FlexBuilderUtils.js +84 -0
  64. package/client/src/FlexDataRenderer.jsx +170 -0
  65. package/client/src/FlexDataRenderer.scss +80 -0
  66. package/client/src/FlexNode.jsx +290 -0
  67. package/client/src/FlexTreeUtils.js +129 -0
  68. package/client/src/HiddenableCell.jsx +46 -0
  69. package/client/src/HiddenableCell.scss +36 -0
  70. package/client/src/KPIDialog.jsx +39 -0
  71. package/client/src/KPIWidget.jsx +139 -0
  72. package/client/src/KanbanCard.jsx +92 -0
  73. package/client/src/KanbanColumn.jsx +29 -0
  74. package/client/src/KanbanConfigModal.jsx +115 -0
  75. package/client/src/KanbanView.jsx +113 -0
  76. package/client/src/KanbanView.scss +107 -0
  77. package/client/src/MessageRotator.jsx +77 -0
  78. package/client/src/MessageRotator.scss +14 -0
  79. package/client/src/ModelCreator.jsx +640 -0
  80. package/client/src/ModelCreator.scss +154 -0
  81. package/client/src/ModelCreatorField.jsx +810 -0
  82. package/client/src/ModelImporter.jsx +120 -0
  83. package/client/src/ModelList.jsx +205 -0
  84. package/client/src/Notification.jsx +137 -0
  85. package/client/src/Notification.scss +126 -0
  86. package/client/src/NotificationProvider.jsx +73 -0
  87. package/client/src/PackGallery.jsx +211 -0
  88. package/client/src/PackGallery.scss +156 -0
  89. package/client/src/Pagination.jsx +141 -0
  90. package/client/src/RTE.jsx +415 -0
  91. package/client/src/RTETrans.jsx +103 -0
  92. package/client/src/RelationField.jsx +247 -0
  93. package/client/src/RelationValue.jsx +230 -0
  94. package/client/src/RestoreConfirmationModal.jsx +183 -0
  95. package/client/src/RestoreDialog.jsx +134 -0
  96. package/client/src/RestoreDialog.scss +67 -0
  97. package/client/src/RichTextEditorModal.jsx +34 -0
  98. package/client/src/RichTextEditorModal.scss +54 -0
  99. package/client/src/TourSpotlight.jsx +234 -0
  100. package/client/src/TourSpotlight.scss +100 -0
  101. package/client/src/TutorialsMenu.jsx +83 -0
  102. package/client/src/TutorialsMenu.scss +217 -0
  103. package/client/src/TutorialsRewardModal.jsx +65 -0
  104. package/client/src/Utils.jsx +78 -0
  105. package/client/src/ViewSwitcher.jsx +54 -0
  106. package/client/src/ViewSwitcher.scss +74 -0
  107. package/client/src/Webpage.jsx +69 -0
  108. package/client/src/_variables.scss +21 -0
  109. package/client/src/assets/react.svg +1 -0
  110. package/client/src/constants.js +68 -0
  111. package/client/src/contexts/AuthContext.jsx +28 -0
  112. package/client/src/contexts/ModelContext.jsx +328 -0
  113. package/client/src/contexts/UIContext.jsx +52 -0
  114. package/client/src/core/data.js +35 -0
  115. package/client/src/entry-client.jsx +15 -0
  116. package/client/src/entry-server.jsx +13 -0
  117. package/client/src/filter.js +222 -0
  118. package/client/src/hooks/data.js +101 -0
  119. package/client/src/hooks/useDebounce.js +20 -0
  120. package/client/src/hooks/useDragAndDrop.js +59 -0
  121. package/client/src/hooks/useLocalStorage.js +100 -0
  122. package/client/src/hooks/useMount.js +42 -0
  123. package/client/src/hooks/useTutorials.jsx +227 -0
  124. package/client/src/hooks/useWindowSize.js +24 -0
  125. package/client/src/i18n.js +47 -0
  126. package/client/src/index.css +90 -0
  127. package/client/src/index.js +10 -0
  128. package/client/src/main.jsx +11 -0
  129. package/client/src/translations.js +15940 -0
  130. package/client/src/tutorials.js +112 -0
  131. package/client/vite.config.js +30 -0
  132. package/eslint.config.js +50 -0
  133. package/package.json +48 -27
  134. package/server.js +9 -0
  135. package/src/constants.js +12 -23
  136. package/src/core.js +9 -8
  137. package/src/data.js +81 -80
  138. package/src/defaultModels.js +114 -115
  139. package/src/email.js +8 -9
  140. package/src/engine.js +5 -5
  141. package/src/events.js +1 -2
  142. package/src/i18n.js +212 -211
  143. package/src/middlewares/middleware-mongodb.js +94 -93
  144. package/src/migrate.js +24 -24
  145. package/src/modules/assistant.js +55 -55
  146. package/src/modules/bucket.js +10 -9
  147. package/src/modules/data.js +387 -357
  148. package/src/modules/file.js +3 -6
  149. package/src/modules/mongodb.js +6 -7
  150. package/src/modules/user.js +24 -47
  151. package/src/modules/workflow.js +68 -67
  152. package/src/openai.jobs.js +2 -2
  153. package/src/packs.js +30 -31
  154. package/src/providers.js +101 -3
  155. package/src/setenv.js +1 -1
  156. package/src/user.js +0 -17
  157. package/src/workers/crypto-worker.js +21 -20
  158. package/src/workers/import-export-worker.js +18 -18
  159. package/test/data.backup.integration.test.js +7 -12
  160. package/test/data.integration.test.js +27 -25
  161. package/test/globalSetup.js +2 -2
  162. package/test/import_export.integration.test.js +6 -6
  163. package/test/model.integration.test.js +21 -22
  164. package/test/workflow.integration.test.js +2 -5
  165. package/test/workflow.robustness.test.js +6 -13
  166. package/vitest.config.js +2 -2
package/src/providers.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {compare} from "bcrypt";
2
+ import {maxTotalPrivateFilesSize} from "./constants.js";
2
3
 
3
4
  /**
4
5
  * @class UserProvider
@@ -6,11 +7,15 @@ import {compare} from "bcrypt";
6
7
  * C'est une classe de base abstraite, elle n'est pas destinée à être utilisée directement.
7
8
  */
8
9
  export class UserProvider {
10
+
11
+ plans = {};
12
+
9
13
  constructor(engine) {
10
14
  if (this.constructor === UserProvider) {
11
15
  throw new Error("Abstract classes can't be instantiated.");
12
16
  }
13
17
  this.engine = engine;
18
+ this.plans = this.getUserPlans();
14
19
  }
15
20
 
16
21
  /**
@@ -50,6 +55,45 @@ export class UserProvider {
50
55
  throw new Error("Method 'initiateUser()' must be implemented.");
51
56
  }
52
57
 
58
+ /**
59
+ * Récupère la limite de stockage d'un utilisateur.
60
+ * @param user
61
+ * @returns {Promise<number>}
62
+ */
63
+ async getUserStorageLimit(user) {
64
+ return maxTotalPrivateFilesSize;
65
+ }
66
+
67
+ /**
68
+ * Récupère la fréquence de sauvegarde d'un utilisateur.
69
+ * @param user
70
+ * @returns {Promise<void>}
71
+ */
72
+ async getBackupFrequency(user){
73
+ return 'daily';
74
+ }
75
+
76
+ /**
77
+ * Récupère les middlewares d'un utilisateur.
78
+ * @returns {Promise<*[]>}
79
+ */
80
+ async getMiddlewares(){
81
+ return [];
82
+ }
83
+
84
+ getUserPlans(){
85
+ return {
86
+ free: {
87
+ requestLimitPerHour: 10000,
88
+ features: ['indexes'] // Feature list : indexes is the only option for now. Use a dedicated collection for this user
89
+ }
90
+ };
91
+ }
92
+
93
+ async hasFeature(user, feature) {
94
+ return this.plans[user.userPlan]?.features.some(f => f === feature);
95
+ }
96
+
53
97
  // Ajoutez ici d'autres méthodes nécessaires : findUserById, createUser, etc.
54
98
  }
55
99
 
@@ -58,19 +102,73 @@ export class DefaultUserProvider extends UserProvider {
58
102
 
59
103
  users= [{ username: "demo", password: "demo" }];
60
104
 
105
+ /**
106
+ * Trouve un utilisateur. Gère spécifiquement les utilisateurs de démo.
107
+ */
61
108
  async findUserByUsername(username) {
109
+ // Si le nom d'utilisateur commence par "demo", on le considère comme un utilisateur de démo valide et volatile.
110
+ if (typeof username === 'string' && username.startsWith('demo')) {
111
+ // On retourne un objet utilisateur de démo avec la structure attendue.
112
+ return { username: username, password: "demo", userPlan: 'free' }; // Ajout de userPlan pour la cohérence
113
+ }
114
+ // Logique pour les vrais utilisateurs (si vous en ajoutez plus tard)
62
115
  return this.users.find(user => user.username === username);
63
116
  }
64
117
 
118
+ async getUserStorageLimit(user) {
119
+ return maxTotalPrivateFilesSize;
120
+ }
121
+
122
+ async getBackupFrequency(user){
123
+ return 'daily';
124
+ }
125
+
126
+ async getMiddlewares(){
127
+ return []
128
+ }
129
+
65
130
  async validatePassword(user, password) {
66
- return true;
131
+ // Pour un utilisateur de démo, le mot de passe est toujours valide.
132
+ if (user.username.startsWith('demo')) {
133
+ return true;
134
+ }
135
+ // Logique pour les vrais utilisateurs
136
+ return true; // ou votre logique de comparaison bcrypt
67
137
  }
68
138
 
69
139
  async updateUser(user, data) {
70
140
  this.users = this.users.map(user => user.username === user.username ? {...user, ...data} : user);
71
141
  }
72
-
142
+ /**
143
+ * Initialise l'utilisateur sur la requête. C'est ici que la magie opère.
144
+ */
73
145
  async initiateUser(req) {
74
- req.me = this.users[0];
146
+ // Priorité 1: Un utilisateur est déjà authentifié via une session (pour les vrais comptes).
147
+ if (req.session?.user?.username) {
148
+ const user = await this.findUserByUsername(req.session.user.username);
149
+ if (user) {
150
+ req.me = user;
151
+ return; // L'utilisateur est trouvé, on s'arrête là.
152
+ }
153
+ }
154
+
155
+ // Priorité 2: PasF de session, mais on vérifie la présence d'un cookie "username" pour la démo.
156
+ const demoUsername = req.cookies?.username;
157
+ if (demoUsername && typeof demoUsername === 'string' && demoUsername.startsWith('demo')) {
158
+ // On a trouvé un cookie de démo. On crée l'objet utilisateur correspondant.
159
+ const demoUser = await this.findUserByUsername(demoUsername);
160
+ if (demoUser) {
161
+ req.me = demoUser;
162
+ return;
163
+ }
164
+ }
165
+ }
166
+
167
+ getUserPlans(){
168
+ return {
169
+ free: {
170
+ features: ['indexes']
171
+ }
172
+ }
75
173
  }
76
174
  }
package/src/setenv.js CHANGED
@@ -2,7 +2,7 @@ import {getRandom} from "data-primals-engine/core";
2
2
  import process from "node:process";
3
3
  import {Engine} from "./index.js";
4
4
 
5
- let ports = [], engineInstance, mongod;
5
+ let ports = [], engineInstance;
6
6
  export const getUniquePort = () =>{
7
7
  let d, it=0;
8
8
  do{
package/src/user.js CHANGED
@@ -1,17 +0,0 @@
1
- import {
2
- maxTotalPrivateFilesSizeFree,
3
- maxTotalPrivateFilesSizePremium,
4
- maxTotalPrivateFilesSizeStandard
5
- } from "./constants.js";
6
-
7
- export const getUserStorageLimit = (user) => {
8
- switch (user.userPlan) {
9
- case 'premium':
10
- return maxTotalPrivateFilesSizePremium;
11
- case 'standard':
12
- return maxTotalPrivateFilesSizeStandard;
13
- case 'free':
14
- default:
15
- return maxTotalPrivateFilesSizeFree;
16
- }
17
- };
@@ -46,29 +46,30 @@ parentPort.on('message', async ({ action, payload }) => {
46
46
  try {
47
47
  let result;
48
48
  switch (action) {
49
- case 'encrypt':
50
- console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
51
- await encryptFile(payload.filePath, payload.password);
52
- parentPort.postMessage({ success: true }); // Pas de données à retourner
53
- break;
49
+ case 'encrypt':
50
+ console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
51
+ await encryptFile(payload.filePath, payload.password);
52
+ parentPort.postMessage({ success: true }); // Pas de données à retourner
53
+ break;
54
54
 
55
- case 'decrypt':
56
- console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
57
- await decryptFile(payload.filePath, payload.password);
58
- parentPort.postMessage({ success: true }); // Pas de données à retourner
59
- break;
55
+ case 'decrypt':
56
+ console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
57
+ await decryptFile(payload.filePath, payload.password);
58
+ parentPort.postMessage({ success: true }); // Pas de données à retourner
59
+ break;
60
+
60
61
 
61
- case 'hash':
62
- console.log(`[Worker] Received action: hash`);
63
- if (!payload.data) throw new Error('Data to hash is missing in payload.');
64
- // Le worker gère à la fois la génération du "salt" et le hachage
65
- const salt = await bcrypt.genSalt(10);
66
- result = await bcrypt.hash(payload.data, salt);
67
- parentPort.postMessage({ success: true, data: result }); // On retourne le hash
68
- break;
62
+ case 'hash':
63
+ console.log(`[Worker] Received action: hash`);
64
+ if (!payload.data) throw new Error('Data to hash is missing in payload.');
65
+ // Le worker gère à la fois la génération du "salt" et le hachage
66
+ const salt = await bcrypt.genSalt(10);
67
+ result = await bcrypt.hash(payload.data, salt);
68
+ parentPort.postMessage({ success: true, data: result }); // On retourne le hash
69
+ break;
69
70
 
70
- default:
71
- throw new Error(`Unknown crypto action: ${action}`);
71
+ default:
72
+ throw new Error(`Unknown crypto action: ${action}`);
72
73
  }
73
74
  } catch (error) {
74
75
  // Envoie l'erreur au thread principal
@@ -5,27 +5,27 @@ parentPort.on('message', ({ action, payload }) => {
5
5
  try {
6
6
  let result;
7
7
  switch (action) {
8
- case 'parse-json':
9
- // Tâche: Parser une chaîne/buffer JSON en objet JS
10
- result = JSON.parse(payload.fileContent.toString('utf-8'));
11
- break;
8
+ case 'parse-json':
9
+ // Tâche: Parser une chaîne/buffer JSON en objet JS
10
+ result = JSON.parse(payload.fileContent.toString('utf-8'));
11
+ break;
12
12
 
13
- case 'parse-csv':
14
- // Tâche: Parser une chaîne/buffer CSV en tableau d'objets JS
15
- result = parse(payload.fileContent, {
16
- columns: payload.options.columns,
17
- skip_empty_lines: true,
18
- trim: true,
19
- });
20
- break;
13
+ case 'parse-csv':
14
+ // Tâche: Parser une chaîne/buffer CSV en tableau d'objets JS
15
+ result = parse(payload.fileContent, {
16
+ columns: payload.options.columns,
17
+ skip_empty_lines: true,
18
+ trim: true
19
+ });
20
+ break;
21
21
 
22
- case 'stringify-json':
23
- // Tâche: Sérialiser un objet/tableau JS en chaîne JSON
24
- result = JSON.stringify(payload.data);
25
- break;
22
+ case 'stringify-json':
23
+ // Tâche: Sérialiser un objet/tableau JS en chaîne JSON
24
+ result = JSON.stringify(payload.data);
25
+ break;
26
26
 
27
- default:
28
- throw new Error(`Unknown action: ${action}`);
27
+ default:
28
+ throw new Error(`Unknown action: ${action}`);
29
29
  }
30
30
  // Renvoyer le résultat au thread principal
31
31
  parentPort.postMessage({ success: true, data: result });
@@ -6,25 +6,21 @@ import { Config } from '../src/config.js';
6
6
  import { ObjectId } from 'mongodb';
7
7
  import {expect, describe, it, beforeAll, afterAll, beforeEach} from 'vitest';
8
8
  import { vi } from 'vitest'
9
- import { Buffer } from 'node:buffer'; // Explicitly import Buffer
10
- import crypto from 'node:crypto'; //Explicitly import crypto
11
9
 
12
10
  import {
13
11
  createModel,
14
- getModel, insertData
12
+ insertData
15
13
  } from 'data-primals-engine/modules/data';
16
14
 
17
15
  import {
18
16
  modelsCollection as getAppModelsCollection,
19
- getCollectionForUser as getAppUserCollection,
17
+ getCollectionForUser as getAppUserCollection
20
18
  } from 'data-primals-engine/modules/mongodb';
21
- import { Engine } from "data-primals-engine/engine";
22
19
  import process from "node:process";
23
20
 
24
- import { dumpUserData, loadFromDump, getUserHash } from 'data-primals-engine/modules/data';
21
+ import { dumpUserData, loadFromDump } from 'data-primals-engine/modules/data';
25
22
  import fs from "node:fs";
26
- import {getRandom} from "data-primals-engine/core";
27
- import {getUniquePort, initEngine, stopEngine} from "../src/setenv.js";
23
+ import {initEngine} from "../src/setenv.js";
28
24
 
29
25
  vi.mock('data-primals-engine/engine', async(importOriginal) => {
30
26
  const mod = await importOriginal() // type is inferred
@@ -37,7 +33,6 @@ vi.mock('data-primals-engine/engine', async(importOriginal) => {
37
33
  const mockUser = {
38
34
  username: 'testuserBackup',
39
35
  _user: 'testuserBackup',
40
- userPlan: 'premium',
41
36
  email: 'testBackup@example.com',
42
37
  configS3: {
43
38
  bucketName: null
@@ -50,9 +45,9 @@ const testModelDefinition = {
50
45
  description: 'Model for testing backup/restore',
51
46
  fields: [
52
47
  { name: 'testField', type: 'string', required: true },
53
- { name: 'optionalField', type: 'number' },
48
+ { name: 'optionalField', type: 'number' }
54
49
  ],
55
- maxRequestData: 10,
50
+ maxRequestData: 10
56
51
  };
57
52
 
58
53
  let testModelsColInstance;
@@ -101,7 +96,7 @@ beforeAll(async () =>{
101
96
  })
102
97
  beforeEach(async () => {
103
98
  testModelsColInstance = getAppModelsCollection;
104
- testDatasColInstance = getAppUserCollection(mockUser);
99
+ testDatasColInstance = await getAppUserCollection(mockUser);
105
100
  });
106
101
 
107
102
  describe('Data Backup and Restore Integration', () => {
@@ -14,8 +14,7 @@ import {
14
14
  import {
15
15
  modelsCollection as getAppModelsCollection,
16
16
  getCollection,
17
- getCollectionForUser as getAppUserCollection,
18
- datasCollection // Accès direct pour vérifications
17
+ getCollectionForUser as getAppUserCollection
19
18
  } from 'data-primals-engine/modules/mongodb';
20
19
  import { Engine } from "data-primals-engine/engine";
21
20
  import process from "node:process";
@@ -41,6 +40,9 @@ async function setupTestContext() {
41
40
  email: generateUniqueName('test') + '@example.com'
42
41
  };
43
42
 
43
+ // Initialize collection instances after the engine is ready
44
+ testModelsColInstance = getAppModelsCollection;
45
+ testDatasColInstance = await getAppUserCollection(currentTestUser);
44
46
 
45
47
  const relatedModelDefinition = {
46
48
  name: currentRelatedModelName,
@@ -50,7 +52,7 @@ async function setupTestContext() {
50
52
  {name: 'relatedName', type: 'string', required: true, unique: true},
51
53
  {name: 'relatedValue', type: 'number'}
52
54
  ],
53
- maxRequestData: 10,
55
+ maxRequestData: 10
54
56
  };
55
57
 
56
58
  const comprehensiveTestModelDefinition = {
@@ -102,23 +104,25 @@ async function setupTestContext() {
102
104
  {name: 'objectField', type: 'object'},
103
105
  // Model & ModelField (validation of string format, not existence)
104
106
  {name: 'modelNameField', type: 'model'},
105
- {name: 'modelFieldNameField', type: 'modelField'}, // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
107
+ {name: 'modelFieldNameField', type: 'modelField'} // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
106
108
  ],
107
- maxRequestData: 50,
109
+ maxRequestData: 50
108
110
  };
109
111
 
110
- // Insérer les modèles en base
111
- await testModelsColInstance.insertMany([
112
- comprehensiveTestModelDefinition,
113
- relatedModelDefinition
114
- ]);
112
+ if( await testModelsColInstance.find({ $or: [{name: comprehensiveTestModelDefinition.name}, {name: relatedModelDefinition.name}]})) {
113
+ // Insérer les modèles en base
114
+ await testModelsColInstance.insertMany([
115
+ comprehensiveTestModelDefinition,
116
+ relatedModelDefinition
117
+ ]);
118
+ }
115
119
  await testDatasColInstance.deleteMany({ _user: currentTestUser.username });
116
120
 
117
121
  // Retourner toutes les variables nécessaires pour un test
118
122
  return {
119
123
  currentTestUser,
120
124
  comprehensiveTestModelDefinition,
121
- relatedModelDefinition,
125
+ relatedModelDefinition
122
126
  };
123
127
  }
124
128
 
@@ -128,9 +132,6 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
128
132
  Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
129
133
  await initEngine();
130
134
 
131
- // Initialize collection instances after the engine is ready
132
- testModelsColInstance = getAppModelsCollection;
133
- testDatasColInstance = datasCollection;
134
135
  })
135
136
 
136
137
  describe('insertData avec comprehensiveTestModel', () => {
@@ -186,13 +187,14 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
186
187
  codeJsField: 'console.log("hello");',
187
188
  objectField: { a: 1, b: "test" },
188
189
  modelNameField: 'someModelName',
189
- modelFieldNameField: { model: 'someModelName', field: 'someFieldName' },
190
+ modelFieldNameField: { model: 'someModelName', field: 'someFieldName' }
190
191
  };
191
192
 
192
193
  const result = await insertData(comprehensiveTestModelDefinition.name, validData, {}, currentTestUser, false);
193
194
  expect(result.success, `L'insertion principale a échoué: ${result.error}`).toBe(true);
194
195
  expect(result.insertedIds).toHaveLength(1);
195
196
 
197
+ console.log(result);
196
198
  const insertedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(result.insertedIds[0]) });
197
199
  expect(insertedDoc).not.toBeNull();
198
200
  expect(insertedDoc.stringRequired).toBe('Valid String');
@@ -365,7 +367,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
365
367
  codeJsField: 'console.log("find");',
366
368
  objectField: { find: "test" },
367
369
  modelNameField: 'findModel',
368
- modelFieldNameField: { model: 'findModel', field: 'findField' },
370
+ modelFieldNameField: { model: 'findModel', field: 'findField' }
369
371
  };
370
372
 
371
373
  // 3. Insérer le document principal
@@ -411,7 +413,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
411
413
  stringUnique: 'UniqueForEdit',
412
414
  enumField: 'alpha',
413
415
  passwordField: 'initialPass',
414
- relationSingle: relatedDocId,
416
+ relationSingle: relatedDocId
415
417
  };
416
418
  const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
417
419
  docToEditId = insertResult.insertedIds[0];
@@ -493,17 +495,17 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
493
495
  await insertData(simpleModel.name, { name: 'Autre Item', value: 403 }, {}, currentTestUser, false);
494
496
 
495
497
  const filter = { $regexMatch: {
496
- input: '$name',
497
- regex: "^Item Filtrable"
498
- } };
498
+ input: '$name',
499
+ regex: "^Item Filtrable"
500
+ } };
499
501
  const deleteResult = await deleteData(simpleModel.name, [], filter, currentTestUser, false);
500
502
 
501
503
  expect(deleteResult.success).toBe(true);
502
504
  expect(deleteResult.deletedCount).toBe(2);
503
505
 
504
- const remainingCount = await testDatasColInstance.countDocuments({ _model: simpleModel.name, name: { $regex: "^Item Filtrable" } });
506
+ const remainingCount = await testDatasColInstance.countDocuments({ _user: currentTestUser.username, _model: simpleModel.name, name: { $regex: "^Item Filtrable" } });
505
507
  expect(remainingCount).toBe(0);
506
- const totalRemaining = await testDatasColInstance.countDocuments({ _model: simpleModel.name });
508
+ const totalRemaining = await testDatasColInstance.countDocuments({ _user: currentTestUser.username, _model: simpleModel.name });
507
509
  expect(totalRemaining).toBe(1);
508
510
  });
509
511
  });
@@ -531,7 +533,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
531
533
  passwordField: 'searchPass1',
532
534
  relationSingle: relatedDocId_A1,
533
535
  relationMultiple: [relatedDocId_A1, relatedDocId_A2],
534
- number: 10,
536
+ number: 10
535
537
  };
536
538
  const insertResult1 = await insertData(comprehensiveTestModelDefinition.name, data1, {}, currentTestUser, false);
537
539
  docId1 = insertResult1.insertedIds[0].toString();
@@ -543,7 +545,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
543
545
  passwordField: 'searchPass2',
544
546
  relationSingle: relatedDocId_B1,
545
547
  relationMultiple: [relatedDocId_B1],
546
- number: 20,
548
+ number: 20
547
549
  };
548
550
  const insertResult2 = await insertData(comprehensiveTestModelDefinition.name, data2, {}, currentTestUser, false);
549
551
  docId2 = insertResult2.insertedIds[0].toString();
@@ -564,7 +566,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
564
566
  },
565
567
  autoExpand: true,
566
568
  depth: 8
567
- },
569
+ }
568
570
  };
569
571
  const { data, count } = await searchData(searchParams);
570
572
  expect(count).toBe(1);
@@ -4,7 +4,7 @@ import {getCollection} from "../src/modules/mongodb.js";
4
4
 
5
5
  export default async function () {
6
6
 
7
- // --- Configuration initiale ---
7
+ // --- Configuration initiale ---
8
8
  Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
9
9
 
10
10
  console.log('\n--- Global Setup: Initializing engine and database for all tests ---');
@@ -12,4 +12,4 @@ export default async function () {
12
12
 
13
13
  // Initialisez et exportez les collections
14
14
  console.log('--- Global Setup: Complete. Engine is running. ---');
15
- }
15
+ }
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  import {
12
12
  modelsCollection as getAppModelsCollection,
13
- getCollectionForUser as getAppUserCollection,
13
+ getCollectionForUser as getAppUserCollection
14
14
  } from 'data-primals-engine/modules/mongodb';
15
15
  import {sleep} from "data-primals-engine/core";
16
16
  import fs from "node:fs";
@@ -33,8 +33,8 @@ const impexTestModel = {
33
33
  { name: 'name', type: 'string', required: true },
34
34
  { name: 'sku', type: 'string', unique: true },
35
35
  { name: 'price', type: 'number', required: true },
36
- { name: 'inStock', type: 'boolean', default: true },
37
- ],
36
+ { name: 'inStock', type: 'boolean', default: true }
37
+ ]
38
38
  };
39
39
 
40
40
  // --- Setup de l'environnement de test ---
@@ -62,7 +62,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
62
62
  // Préparation avant chaque test du bloc
63
63
  beforeEach(async () => {
64
64
  testModelsColInstance = getAppModelsCollection;
65
- testDatasColInstance = getAppUserCollection(mockUser);
65
+ testDatasColInstance = await getAppUserCollection(mockUser);
66
66
 
67
67
  // Nettoyage complet pour un état propre
68
68
  await testDatasColInstance.deleteMany({ _user: "testuserImpex"});
@@ -74,7 +74,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
74
74
  await insertData(impexTestModel.name, [
75
75
  { name: 'Produit A', sku: 'SKU-A', price: 10.50, inStock: true },
76
76
  { name: 'Produit B', sku: 'SKU-B', price: 25.00, inStock: false },
77
- { name: 'Produit C', sku: 'SKU-C', price: 99.99, inStock: true },
77
+ { name: 'Produit C', sku: 'SKU-C', price: 99.99, inStock: true }
78
78
  ], {}, mockUser, false);
79
79
  });
80
80
 
@@ -82,7 +82,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
82
82
  it('devrait exporter les données en format JSON', async () => {
83
83
  const res= await exportData({
84
84
  models: [impexTestModel.name],
85
- depth: 1,
85
+ depth: 1
86
86
  }, mockUser);
87
87
 
88
88
  expect(res.success).toBeTruthy();
@@ -38,7 +38,7 @@ async function setupTestContext() {
38
38
  {name: 'relatedName', type: 'string', required: true, unique: true},
39
39
  {name: 'relatedValue', type: 'number'}
40
40
  ],
41
- maxRequestData: 10,
41
+ maxRequestData: 10
42
42
  };
43
43
 
44
44
  const comprehensiveTestModelDefinition = {
@@ -90,9 +90,9 @@ async function setupTestContext() {
90
90
  {name: 'objectField', type: 'object'},
91
91
  // Model & ModelField (validation of string format, not existence)
92
92
  {name: 'modelNameField', type: 'model'},
93
- {name: 'modelFieldNameField', type: 'modelField'}, // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
93
+ {name: 'modelFieldNameField', type: 'modelField'} // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
94
94
  ],
95
- maxRequestData: 50,
95
+ maxRequestData: 50
96
96
  };
97
97
 
98
98
  // Insérer les modèles en base
@@ -108,7 +108,7 @@ async function setupTestContext() {
108
108
  return {
109
109
  currentTestUser,
110
110
  comprehensiveTestModelDefinition,
111
- relatedModelDefinition,
111
+ relatedModelDefinition
112
112
  };
113
113
  }
114
114
 
@@ -127,22 +127,21 @@ describe('CRUD on model definitions and integrity tests', () => {
127
127
  it('should create and drop index when field.index is toggled (premium user)', async () => {
128
128
  // --- SETUP ---
129
129
  const { currentTestUser, comprehensiveTestModelDefinition } = await setupTestContext();
130
- const premiumUser = { ...currentTestUser, userPlan: 'premium' };
131
- const dataCollection = getCollectionForUser(premiumUser);
130
+ const dataCollection = await getCollectionForUser(currentTestUser);
132
131
  const fieldToIndex = 'stringUnique'; // Utiliser un champ qui existe vraiment dans le modèle
133
132
 
133
+ // --- FIX: Ensure the collection exists before any operation ---
134
+ // By inserting and deleting a dummy document, we force MongoDB to create the
135
+ // collection and its default indexes. This prevents the "ns does not exist"
136
+ // error in asynchronous listeners (like workflow triggers).
137
+ const dummyDoc = await dataCollection.insertOne({ _model: 'dummy', _user: currentTestUser.username });
138
+ await dataCollection.deleteOne({ _id: dummyDoc.insertedId });
139
+
140
+
134
141
  // --- VERIFICATION INITIALE ---
135
142
  // S'assurer qu'aucun index n'existe au départ.
136
- // On enveloppe l'appel dans un try/catch pour gérer le cas où la collection n'existe pas.
137
- let initialIndexes = [];
138
- try {
139
- initialIndexes = await dataCollection.indexes();
140
- } catch (e) {
141
- // C'est normal si la collection n'existe pas encore. On considère qu'il n'y a pas d'index.
142
- if (e.codeName !== 'NamespaceNotFound') {
143
- throw e; // Relancer les autres erreurs inattendues
144
- }
145
- }
143
+ // Cet appel ne plantera plus car la collection est maintenant créée.
144
+ const initialIndexes = await dataCollection.indexes();
146
145
  expect(initialIndexes.some(i => i.key[fieldToIndex] === 1)).toBe(false);
147
146
 
148
147
  // --- ACTION 1 : AJOUTER UN INDEX ---
@@ -150,9 +149,9 @@ describe('CRUD on model definitions and integrity tests', () => {
150
149
  ...comprehensiveTestModelDefinition,
151
150
  fields: comprehensiveTestModelDefinition.fields.map(f =>
152
151
  f.name === fieldToIndex ? { ...f, index: true } : f
153
- ),
152
+ )
154
153
  };
155
- await editModel(premiumUser, testModelId, modelWithIndex);
154
+ await editModel(currentTestUser, testModelId, modelWithIndex);
156
155
 
157
156
  // --- VERIFICATION 1 ---
158
157
  // Maintenant, la collection et l'index doivent exister.
@@ -163,7 +162,7 @@ describe('CRUD on model definitions and integrity tests', () => {
163
162
  // Le filtre partiel est crucial pour que l'index ne s'applique qu'aux bonnes données
164
163
  expect(newIndex.partialFilterExpression).toEqual({
165
164
  _model: comprehensiveTestModelDefinition.name,
166
- _user: premiumUser.username,
165
+ _user: currentTestUser.username
167
166
  });
168
167
 
169
168
  // --- ACTION 2 : SUPPRIMER L'INDEX ---
@@ -171,14 +170,14 @@ describe('CRUD on model definitions and integrity tests', () => {
171
170
  ...comprehensiveTestModelDefinition,
172
171
  fields: comprehensiveTestModelDefinition.fields.map(f =>
173
172
  f.name === fieldToIndex ? { ...f, index: false } : f
174
- ),
173
+ )
175
174
  };
176
- await editModel(premiumUser, testModelId, modelWithoutIndex);
175
+ await editModel(currentTestUser, testModelId, modelWithoutIndex);
177
176
 
178
177
  // --- VERIFICATION 2 ---
179
178
  const indexesAfterDeletion = await dataCollection.indexes();
180
179
  expect(indexesAfterDeletion.some(i => i.key[fieldToIndex] === 1)).toBe(false);
181
- }, 20000); // Augmenter le timeout si nécessaire pour les opérations d'index
180
+ }, 20000);
182
181
 
183
182
  it('should not save extra, non-defined fields in the model definition', async () => {
184
183
  const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();