data-primals-engine 1.1.8 → 1.2.1

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 (44) hide show
  1. package/README.md +72 -25
  2. package/client/src/App.scss +11 -6
  3. package/client/src/AssistantChat.scss +0 -1
  4. package/client/src/ChartConfigModal.jsx +0 -1
  5. package/client/src/ConditionBuilder.jsx +0 -2
  6. package/client/src/ConditionBuilder2.jsx +47 -6
  7. package/client/src/ContentView.jsx +0 -1
  8. package/client/src/CronBuilder.scss +0 -1
  9. package/client/src/CronPartBuilder.jsx +0 -1
  10. package/client/src/Dashboard.jsx +0 -1
  11. package/client/src/Dashboard.scss +0 -1
  12. package/client/src/DashboardChart.jsx +0 -1
  13. package/client/src/DashboardView.jsx +0 -1
  14. package/client/src/DataEditor.jsx +0 -4
  15. package/client/src/DisplayFlexNodeRenderer.jsx +0 -1
  16. package/client/src/DocumentationPageLayout.jsx +0 -2
  17. package/client/src/DocumentationPageLayout.scss +0 -1
  18. package/client/src/Field.jsx +1 -1
  19. package/client/src/FlexNode.jsx +0 -1
  20. package/client/src/KanbanView.scss +0 -1
  21. package/client/src/ModelCreator.jsx +4 -4
  22. package/client/src/ModelCreator.scss +10 -0
  23. package/client/src/ModelCreatorField.jsx +34 -34
  24. package/client/src/RTE.jsx +2 -1
  25. package/client/src/RestoreDialog.jsx +0 -1
  26. package/client/src/TourSpotlight.jsx +0 -1
  27. package/client/src/filter.js +2 -0
  28. package/client/src/hooks/useDragAndDrop.js +0 -1
  29. package/issues.txt +0 -0
  30. package/package.json +3 -2
  31. package/src/constants.js +6 -0
  32. package/src/core.js +6 -0
  33. package/src/data.js +0 -2
  34. package/src/defaultModels.js +33 -0
  35. package/src/migrate.js +0 -1
  36. package/src/modules/assistant.js +211 -133
  37. package/src/modules/data.js +88 -69
  38. package/src/modules/user.js +111 -56
  39. package/src/modules/workflow.js +10 -12
  40. package/src/providers.js +1 -1
  41. package/src/workers/crypto-worker.js +0 -1
  42. package/test/data.integration.test.js +53 -70
  43. package/test/user.test.js +203 -0
  44. package/test/workflow.integration.test.js +1 -1
@@ -471,7 +471,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
471
471
  const insertResult = await insertData(simpleModel.name, dataToDelete, {}, currentTestUser, false);
472
472
  const docId = insertResult.insertedIds[0];
473
473
 
474
- const deleteResult = await deleteData(simpleModel.name, [docId], {}, currentTestUser, false);
474
+ const deleteResult = await deleteData(simpleModel.name, [docId], currentTestUser);
475
475
 
476
476
  expect(deleteResult.success).toBe(true);
477
477
  expect(deleteResult.deletedCount).toBe(1);
@@ -493,7 +493,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
493
493
  input: '$name',
494
494
  regex: "^Item Filtrable"
495
495
  } };
496
- const deleteResult = await deleteData(simpleModel.name, [], filter, currentTestUser, false);
496
+ const deleteResult = await deleteData(simpleModel.name, filter, currentTestUser);
497
497
 
498
498
  expect(deleteResult.success).toBe(true);
499
499
  expect(deleteResult.deletedCount).toBe(2);
@@ -551,19 +551,16 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
551
551
  it('devrait trouver un document par une condition sur une relation simple ($find)', async () => {
552
552
  const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
553
553
  const searchParams = {
554
- user: currentTestUser,
555
- query: {
556
- model: comprehensiveTestModelDefinition.name,
557
- filter: {
558
- relationSingle: {
559
- "$find": { "$and": [{"$eq": ["$$this.relatedValue", 101]}] }
560
- }
561
- },
562
- autoExpand: true,
563
- depth: 8
564
- }
554
+ model: comprehensiveTestModelDefinition.name,
555
+ filter: {
556
+ relationSingle: {
557
+ "$find": { "$and": [{"$eq": ["$$this.relatedValue", 101]}] }
558
+ }
559
+ },
560
+ autoExpand: true,
561
+ depth: 8
565
562
  };
566
- const { data, count } = await searchData(searchParams);
563
+ const { data, count } = await searchData(searchParams, currentTestUser);
567
564
  expect(count).toBe(1);
568
565
  expect(data).toHaveLength(1);
569
566
  expect(data[0]._id.toString()).toBe(docId1.toString());
@@ -575,18 +572,15 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
575
572
  it('ne devrait pas trouver de document si la condition $find sur relation simple ne correspond pas', async () => {
576
573
  const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
577
574
  const searchParams = {
578
- user: currentTestUser,
579
- query: {
580
- model: comprehensiveTestModelDefinition.name,
581
- filter: {
582
- relationSingle: {
583
- "$find": { "$eq": ["$$this.relatedName", "NonExistentRelName"] }
584
- }
585
- },
586
- depth: 1
587
- }
575
+ model: comprehensiveTestModelDefinition.name,
576
+ filter: {
577
+ relationSingle: {
578
+ "$find": { "$eq": ["$$this.relatedName", "NonExistentRelName"] }
579
+ }
580
+ },
581
+ depth: 1
588
582
  };
589
- const { data, count } = await searchData(searchParams);
583
+ const { data, count } = await searchData(searchParams, currentTestUser);
590
584
  expect(count).toBe(0);
591
585
  expect(data).toHaveLength(0);
592
586
  });
@@ -594,20 +588,17 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
594
588
  it('devrait trouver des documents par une condition sur une relation multiple ($find)', async () => {
595
589
  const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
596
590
  const searchParams = {
597
- user: currentTestUser,
598
- query: {
599
- model: comprehensiveTestModelDefinition.name,
600
- filter: {
601
- $and: [{$ne:["$stringRequired", null]}, {
602
- relationMultiple: { // Le champ qui est un tableau de relations
603
- "$find": {"$eq": ["$$this.relatedValue", 102]} // Condition sur les documents liés
604
- }
605
- }]
606
- },
607
- depth: 1
608
- }
591
+ model: comprehensiveTestModelDefinition.name,
592
+ filter: {
593
+ $and: [{$ne:["$stringRequired", null]}, {
594
+ relationMultiple: { // Le champ qui est un tableau de relations
595
+ "$find": {"$eq": ["$$this.relatedValue", 102]} // Condition sur les documents liés
596
+ }
597
+ }]
598
+ },
599
+ depth: 1
609
600
  };
610
- const { data, count } = await searchData(searchParams);
601
+ const { data, count } = await searchData(searchParams, currentTestUser);
611
602
  expect(count).toBe(1);
612
603
  expect(data).toHaveLength(1);
613
604
  expect(data[0]._id.toString()).toBe(docId1.toString());
@@ -620,19 +611,16 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
620
611
  it('devrait trouver des documents en combinant un filtre normal et un filtre $find', async () => {
621
612
  const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
622
613
  const searchParams = {
623
- user: currentTestUser,
624
- query: {
625
- model: comprehensiveTestModelDefinition.name,
626
- filter: {
627
- stringRequired: "SearchDoc1", // Filtre sur le modèle principal
628
- relationSingle: {
629
- "$find": {"$gt": ["$$this.relatedValue", 100] } // Filtre sur la relation
630
- }
631
- },
632
- depth: 1
633
- }
614
+ model: comprehensiveTestModelDefinition.name,
615
+ filter: {
616
+ stringRequired: "SearchDoc1", // Filtre sur le modèle principal
617
+ relationSingle: {
618
+ "$find": {"$gt": ["$$this.relatedValue", 100] } // Filtre sur la relation
619
+ }
620
+ },
621
+ depth: 1
634
622
  };
635
- const { data, count } = await searchData(searchParams);
623
+ const { data, count } = await searchData(searchParams,currentTestUser);
636
624
  expect(count).toBe(1);
637
625
  expect(data).toHaveLength(1);
638
626
  expect(data[0]._id.toString()).toBe(docId1.toString());
@@ -641,26 +629,21 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
641
629
  it('devrait retourner un tableau vide si $find ne correspond à aucun document lié', async () => {
642
630
  const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
643
631
  const searchParams = {
644
- user: currentTestUser,
645
- query: {
646
- model: comprehensiveTestModelDefinition.name,
647
- filter: {
648
- relationMultiple: {
649
- "$find": { "$eq": ["$$this.relatedName", "NonExistentRelNameForMultiple"] }
650
- }
651
- },
652
- depth: 1
653
- }
632
+ model: comprehensiveTestModelDefinition.name,
633
+ filter: {
634
+ relationMultiple: {
635
+ "$find": { "$eq": ["$$this.relatedName", "NonExistentRelNameForMultiple"] }
636
+ }
637
+ },
638
+ depth: 1
654
639
  };
655
- const { data, count } = await searchData(searchParams);
640
+ const { data, count } = await searchData(searchParams,currentTestUser);
656
641
  expect(count).toBe(0);
657
642
  expect(data).toHaveLength(0);
658
643
  });
659
644
 
660
645
  });
661
646
 
662
- // Dans C:/Dev/hackersonline-engine/test/data.integration.test.js
663
-
664
647
  describe('installPack', () => {
665
648
  let testPacksColInstance;
666
649
 
@@ -824,8 +807,8 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
824
807
  // Cleanup before starting to ensure a clean state
825
808
  await deleteModels({ name: productModel.name, _user: user.username });
826
809
  await deleteModels({ name: orderModel.name, _user: user.username });
827
- await deleteData(productModel.name, [], {}, user);
828
- await deleteData(orderModel.name, [], {}, user);
810
+ await deleteData(productModel.name, {}, user);
811
+ await deleteData(orderModel.name, {}, user);
829
812
 
830
813
  // Create models
831
814
  await createModel({ ...productModel, _user: user.username });
@@ -843,8 +826,8 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
843
826
  afterAll(async () => {
844
827
  await deleteModels({ name: productModel.name, _user: user.username });
845
828
  await deleteModels({ name: orderModel.name, _user: user.username });
846
- await deleteData(productModel.name, [], {}, user);
847
- await deleteData(orderModel.name, [], {}, user);
829
+ await deleteData(productModel.name, {}, user);
830
+ await deleteData(orderModel.name, {}, user);
848
831
  });
849
832
 
850
833
  it('should ALLOW inserting data with a valid relation', async () => {
@@ -852,7 +835,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
852
835
  expect(result.success).toBe(true);
853
836
  expect(result.insertedIds).toHaveLength(1);
854
837
  // Cleanup the created order for test isolation
855
- await deleteData(orderModel.name, result.insertedIds, {}, user);
838
+ await deleteData(orderModel.name, result.insertedIds, user);
856
839
  });
857
840
 
858
841
  it('should REJECT inserting data with a relation that does not respect the filter', async () => {
@@ -873,7 +856,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
873
856
  expect(result.modifiedCount).toBeGreaterThanOrEqual(0);
874
857
 
875
858
  // Cleanup
876
- await deleteData(orderModel.name, initialOrder.insertedIds, {}, user);
859
+ await deleteData(orderModel.name, initialOrder.insertedIds, user);
877
860
  });
878
861
 
879
862
  it('should REJECT updating data with a relation that does not respect the filter', async () => {
@@ -885,7 +868,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
885
868
  expect(result.success).toBe(false);
886
869
  expect(result.error).toContain('produit');
887
870
  // Cleanup
888
- await deleteData(orderModel.name, initialOrder.insertedIds, {}, user);
871
+ await deleteData(orderModel.name, initialOrder.insertedIds, user);
889
872
  });
890
873
  });
891
874
  });
@@ -0,0 +1,203 @@
1
+ import { expect, describe, it, beforeEach, beforeAll, afterAll } from 'vitest';
2
+ import { hasPermission, getUserActivePermissions } from '../src/modules/user.js';
3
+ import {initEngine} from "data-primals-engine/setenv";
4
+ import {generateUniqueName} from "../src/setenv.js";
5
+ import {
6
+ getCollectionForUser as getAppUserCollection,
7
+ modelsCollection as getAppModelsCollection
8
+ } from "data-primals-engine/modules/mongodb";
9
+ import {Config} from "../src/index.js";
10
+ let permRead, permWrite, permDelete, permManage;
11
+ let roleEditor;
12
+ let testUser, adminUser, roleViewer;
13
+ let currentTestUser;
14
+ let testModelsColInstance, testDatasColInstance;
15
+ // Cette fonction va remplacer la logique de votre beforeEach pour la création de contexte
16
+ async function setupTestContext() {
17
+
18
+
19
+ // Créer un utilisateur unique pour ce test
20
+ const username = generateUniqueName('testuserDataIntegration');
21
+ currentTestUser = {
22
+ username,
23
+ userPlan: 'free',
24
+ _model: 'user',
25
+ _user: username,
26
+ email: generateUniqueName('test') + '@example.com'
27
+ };
28
+
29
+ // Initialize collection instances after the engine is ready
30
+ testModelsColInstance = getAppModelsCollection;
31
+ testDatasColInstance = await getAppUserCollection(currentTestUser);
32
+
33
+ await testDatasColInstance.deleteMany({}); // Nettoyage de la base de test
34
+ // 1. Créer les permissions
35
+ const permissions = await testDatasColInstance.insertMany([
36
+ { _model: 'permission', name: 'post:read', description: 'Lire les articles' },
37
+ { _model: 'permission', name: 'post:write', description: 'Écrire des articles' },
38
+ { _model: 'permission', name: 'post:delete', description: 'Supprimer des articles' },
39
+ { _model: 'permission', name: 'user:manage', description: 'Gérer les utilisateurs' }
40
+ ]);
41
+ permRead = permissions.insertedIds[0];
42
+ permWrite = permissions.insertedIds[1];
43
+ permDelete = permissions.insertedIds[2];
44
+ permManage = permissions.insertedIds[3];
45
+
46
+ // 2. Créer les rôles
47
+ const roles = await testDatasColInstance.insertMany([
48
+ { _user: currentTestUser.username, _model: 'role', name: 'Viewer', permissions: [permRead.toString()] },
49
+ { _user: currentTestUser.username, _model: 'role', name: 'Editor', permissions: [permRead.toString(), permWrite.toString()] }
50
+ ]);
51
+ roleViewer = roles.insertedIds[0];
52
+ roleEditor = roles.insertedIds[1];
53
+
54
+ // 3. Créer les utilisateurs
55
+ const users = await testDatasColInstance.insertMany([
56
+ { _user: currentTestUser.username, _model: 'user', roles: [roleEditor.toString()] },
57
+ { _user: currentTestUser.username, _model: 'user', roles: [roleEditor.toString()] }
58
+ ]);
59
+ testUser = {...currentTestUser, _id: users.insertedIds[0].toString(), roles: [roleEditor.toString()] };
60
+ adminUser = {...currentTestUser, _id: users.insertedIds[1].toString(), roles: [roleEditor.toString()] };
61
+ };
62
+ let engine;
63
+ describe('User Permission Logic', () => {
64
+
65
+ // --- SETUP : Création des données de test avant tous les tests ---
66
+ beforeAll(async () => {
67
+ Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
68
+ engine = await initEngine();
69
+
70
+ });
71
+
72
+ // --- Tests pour la fonction principale de calcul ---
73
+ describe('getUserActivePermissions()', () => {
74
+
75
+
76
+ beforeEach(async () =>{
77
+ await setupTestContext();
78
+ })
79
+ it('should return base permissions from the user\'s role', async () => {
80
+ const permissions = await getUserActivePermissions(testUser);
81
+ expect(permissions).to.be.an.instanceOf(Set);
82
+ expect(permissions.size).to.equal(2);
83
+ expect(permissions.has('post:read')).to.be.true;
84
+ expect(permissions.has('post:write')).to.be.true;
85
+ expect(permissions.has('post:delete')).to.be.false;
86
+ });
87
+
88
+ it('should grant a temporary permission via an exception', async () => {
89
+
90
+ // Ajoute une exception qui donne la permission de supprimer, expirant demain
91
+ const futureDate = new Date();
92
+ futureDate.setDate(futureDate.getDate() + 1);
93
+
94
+ await testDatasColInstance.insertOne({
95
+ _model: 'userPermission',
96
+ user: testUser._id,
97
+ permission: permDelete,
98
+ isGranted: true,
99
+ expiresAt: futureDate
100
+ });
101
+
102
+ const permissions = await getUserActivePermissions(testUser);
103
+ expect(permissions.size).to.equal(3);
104
+ expect(permissions.has('post:delete')).to.be.true;
105
+
106
+ // Nettoyage de l'exception pour ne pas affecter les autres tests
107
+ await testDatasColInstance.deleteOne({ _model: 'userPermission', user: testUser._id });
108
+ });
109
+
110
+ it('should NOT grant an expired temporary permission', async () => {
111
+ // Ajoute une exception qui a expiré hier
112
+ const pastDate = new Date();
113
+ pastDate.setDate(pastDate.getDate() - 1);
114
+
115
+ await testDatasColInstance.insertOne({
116
+ _model: 'userPermission',
117
+ user: testUser._id,
118
+ permission: permDelete,
119
+ isGranted: true,
120
+ expiresAt: pastDate
121
+ });
122
+
123
+ const permissions = await getUserActivePermissions(testUser);
124
+ expect(permissions.size).to.equal(2); // Revient la normale
125
+ expect(permissions.has('post:delete')).to.be.false;
126
+
127
+ await testDatasColInstance.deleteOne({ _model: 'userPermission', user: testUser._id });
128
+ });
129
+
130
+ it('should revoke a base permission via an exception', async () => {
131
+ // L'utilisateur est "Editor", mais on lui retire le droit d'écriture temporairement
132
+ const futureDate = new Date();
133
+ futureDate.setDate(futureDate.getDate() + 1);
134
+
135
+ await testDatasColInstance.insertOne({
136
+ _model: 'userPermission',
137
+ user: testUser._id,
138
+ permission: permWrite,
139
+ isGranted: false, // Révocation
140
+ expiresAt: futureDate
141
+ });
142
+
143
+ const permissions = await getUserActivePermissions(testUser);
144
+ expect(permissions.size).to.equal(1);
145
+ expect(permissions.has('post:read')).to.be.true;
146
+ expect(permissions.has('post:write')).to.be.false; // La permission a été retirée
147
+
148
+ await testDatasColInstance.deleteOne({ _model: 'userPermission', user: testUser._id });
149
+ });
150
+
151
+ it('should restore a revoked permission if the revocation has expired', async () => {
152
+ // La révocation a expiré, l'utilisateur devrait retrouver son droit d'écriture
153
+ const pastDate = new Date();
154
+ pastDate.setDate(pastDate.getDate() - 1);
155
+
156
+ await testDatasColInstance.insertOne({
157
+ _model: 'userPermission',
158
+ user: testUser._id,
159
+ permission: permWrite,
160
+ isGranted: false,
161
+ expiresAt: pastDate
162
+ });
163
+
164
+ const permissions = await getUserActivePermissions(testUser);
165
+ expect(permissions.size).to.equal(2);
166
+ expect(permissions.has('post:write')).to.be.true; // La permission est de retour
167
+
168
+ await testDatasColInstance.deleteOne({ _model: 'userPermission', user: testUser._id });
169
+ });
170
+ });
171
+
172
+ // --- Tests pour la fonction publique `hasPermission` ---
173
+ describe('hasPermission()', () => {
174
+ beforeEach(async () => {
175
+ await setupTestContext();
176
+ });
177
+ it('should return true for a permission the user has', async () => {
178
+
179
+
180
+ console.log(await testDatasColInstance.find({ _model: 'permission' }).toArray());
181
+ console.log(await testDatasColInstance.find({ _model: 'role' }).toArray());
182
+ console.log(await testDatasColInstance.find({ _model: 'user' }).toArray());
183
+ console.log(await testDatasColInstance.find({ _model: 'userPermission' }).toArray());
184
+ const result = await hasPermission('post:read', testUser);
185
+ expect(result).to.be.true;
186
+ });
187
+
188
+ it('should return false for a permission the user does not have', async () => {
189
+ const result = await hasPermission('user:manage', testUser);
190
+ expect(result).to.be.false;
191
+ });
192
+
193
+ it('should return true if user has at least one of the required permissions', async () => {
194
+ const result = await hasPermission(['user:manage', 'post:write'], testUser);
195
+ expect(result).to.be.true;
196
+ });
197
+
198
+ it('should return false if user has none of the required permissions', async () => {
199
+ const result = await hasPermission(['user:manage', 'post:delete'], testUser);
200
+ expect(result).to.be.false;
201
+ });
202
+ });
203
+ });
@@ -299,7 +299,7 @@ describe('Intégration des Workflows - triggerWorkflows', () => {
299
299
  }, {}, mockUser, false);
300
300
 
301
301
  // 2. Act: Supprimer la donnée
302
- const deleteResult = await deleteData(targetDataModel.name, [projectId], {}, mockUser, true, true);
302
+ const deleteResult = await deleteData(targetDataModel.name, [projectId], mockUser, true, true);
303
303
  expect(deleteResult.success).toBe(true);
304
304
 
305
305
  // 3. Assert: Un `workflowRun` doit être créé