data-primals-engine 1.5.0 → 1.5.2

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 (63) hide show
  1. package/README.md +37 -0
  2. package/client/src/AddWidgetTypeModal.jsx +47 -43
  3. package/client/src/App.jsx +2 -6
  4. package/client/src/App.scss +13 -1
  5. package/client/src/AssistantChat.jsx +363 -323
  6. package/client/src/AssistantChat.scss +27 -10
  7. package/client/src/Dashboard.jsx +480 -396
  8. package/client/src/Dashboard.scss +1 -1
  9. package/client/src/DashboardHtmlViewItem.jsx +147 -0
  10. package/client/src/DashboardView.jsx +654 -569
  11. package/client/src/DataEditor.jsx +10 -3
  12. package/client/src/DataLayout.jsx +807 -755
  13. package/client/src/DataLayout.scss +14 -0
  14. package/client/src/DataTable.jsx +39 -75
  15. package/client/src/Dialog.scss +1 -1
  16. package/client/src/Field.jsx +2057 -1825
  17. package/client/src/FlexViewCard.jsx +44 -0
  18. package/client/src/HistoryDialog.jsx +69 -14
  19. package/client/src/HtmlViewBuilderModal.jsx +91 -0
  20. package/client/src/HtmlViewBuilderModal.scss +18 -0
  21. package/client/src/HtmlViewCard.jsx +44 -0
  22. package/client/src/HtmlViewCard.scss +35 -0
  23. package/client/src/KanbanCard.jsx +1 -2
  24. package/client/src/ModelCreator.jsx +5 -4
  25. package/client/src/ModelCreatorField.jsx +51 -4
  26. package/client/src/ModelList.jsx +280 -236
  27. package/client/src/Notification.jsx +136 -136
  28. package/client/src/Notification.scss +0 -18
  29. package/client/src/Pagination.jsx +5 -3
  30. package/client/src/RelationField.jsx +354 -258
  31. package/client/src/RelationSelectorWidget.jsx +173 -0
  32. package/client/src/contexts/ModelContext.jsx +10 -1
  33. package/client/src/contexts/UIContext.jsx +72 -63
  34. package/client/src/filter.js +263 -212
  35. package/client/src/hooks/useValidation.js +75 -0
  36. package/client/src/translations.js +24 -24
  37. package/package.json +7 -6
  38. package/src/constants.js +1 -1
  39. package/src/core.js +8 -1
  40. package/src/defaultModels.js +1596 -1544
  41. package/src/engine.js +85 -43
  42. package/src/events.js +137 -113
  43. package/src/i18n.js +710 -10
  44. package/src/index.js +3 -0
  45. package/src/modules/assistant/assistant.js +253 -134
  46. package/src/modules/assistant/constants.js +2 -1
  47. package/src/modules/bucket.js +2 -1
  48. package/src/modules/data/data.core.js +118 -92
  49. package/src/modules/data/data.history.js +555 -492
  50. package/src/modules/data/data.js +3 -53
  51. package/src/modules/data/data.operations.js +3381 -3231
  52. package/src/modules/data/data.relations.js +686 -686
  53. package/src/modules/data/data.routes.js +1879 -1821
  54. package/src/modules/data/data.validation.js +81 -2
  55. package/src/modules/file.js +247 -238
  56. package/src/modules/user.js +1 -0
  57. package/src/modules/workflow.js +2 -2
  58. package/src/openai.jobs.js +3 -2
  59. package/src/packs.js +5482 -5478
  60. package/src/sso.js +2 -2
  61. package/src/workers/import-export-worker.js +1 -1
  62. package/test/data.history.integration.test.js +264 -192
  63. package/test/data.integration.test.js +149 -3
@@ -1,6 +1,9 @@
1
1
  // __tests__/data.integration.test.js
2
2
  import { ObjectId } from 'mongodb';
3
3
  import {vi, expect, describe, it, afterEach,beforeEach, beforeAll, afterAll} from 'vitest';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import os from 'os';
4
7
  import { Config } from '../src/config.js';
5
8
  import {
6
9
  insertData,
@@ -17,6 +20,7 @@ import {
17
20
  import {getRandom} from "../src/core.js";
18
21
  import {generateUniqueName, initEngine} from "../src/setenv.js";
19
22
  import {purgeData} from "../src/modules/data/data.history.js";
23
+ import {removeFile} from "../src/modules/file.js";
20
24
 
21
25
  let testModelsColInstance;
22
26
  let testDatasColInstance;
@@ -88,7 +92,7 @@ async function setupTestContext() {
88
92
  {name: 'relationSingle', type: 'relation', relation: currentRelatedModelName},
89
93
  {name: 'relationMultiple', type: 'relation', relation: currentRelatedModelName, multiple: true},
90
94
  // File (metadata validation)
91
- {name: 'fileField', type: 'file', mimeTypes: ['image/png', 'application/pdf'], maxSize: 1024 * 10}, // 10KB
95
+ {name: 'fileField', type: 'file', mimeTypes: ['image/png', 'application/pdf', 'text/plain'], maxSize: 1024 * 10}, // 10KB
92
96
  // Color
93
97
  {name: 'colorField', type: 'color'},
94
98
  // Code
@@ -126,7 +130,7 @@ async function setupTestContext() {
126
130
  }
127
131
 
128
132
  let engine;
129
- describe('Intégration des fonctions CRUD de données avec validation complète', () => {
133
+ describe('Data integration tests (CRUD, validation...)', () => {
130
134
 
131
135
  beforeAll(async () =>{
132
136
  Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
@@ -285,7 +289,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
285
289
  // --- FILE VALIDATIONS (metadata) ---
286
290
  it('devrait rejeter un file avec un mimeType non autorisé', async () => {
287
291
  const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition, purge } = await setupTestContext();
288
- const mockFile = { name: 'test.txt', type: 'text/plain', size: 1024, guid: 'dummy-txt' };
292
+ const mockFile = { name: 'test.txt', type: 'text/csv', size: 1024, guid: 'dummy-txt' };
289
293
  const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', fileField: mockFile }, {}, currentTestUser, false);
290
294
  expect(result.success).toBe(false);
291
295
  expect(result.error).toContain('image/png'); // Ou un message plus spécifique sur mimeTypes
@@ -414,6 +418,57 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
414
418
 
415
419
  await purge();
416
420
  }, 10000); // Augmenter le timeout si nécessaire
421
+
422
+ it('devrait insérer des données avec un nouveau fichier et le sauvegarder', async () => {
423
+ const { currentTestUser, comprehensiveTestModelDefinition, purge } = await setupTestContext();
424
+
425
+ // --- Simuler un vrai fichier uploadé comme le ferait formidable/multer ---
426
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'data-primals-test-'));
427
+ const filePath = path.join(tmpDir, 'test-upload.txt');
428
+ const fileContent = 'Ceci est un vrai test de fichier binaire.';
429
+ await fs.writeFile(filePath, fileContent);
430
+ const stats = await fs.stat(filePath);
431
+
432
+ const uploadedFile = {
433
+ filepath: filePath,
434
+ originalFilename: 'test-upload-on-insert.txt',
435
+ mimetype: 'text/plain',
436
+ size: stats.size,
437
+ lastModifiedDate: new Date(),
438
+ hash: 'test-hash' // Optionnel, mais souvent présent
439
+ };
440
+ // --- Fin de la simulation ---
441
+
442
+ const dataWithFile = {
443
+ stringRequired: 'DocWithNewFile',
444
+ stringUnique: 'UniqueFileValueForInsert',
445
+ passwordField: 'filePass',
446
+ enumField: 'gamma',
447
+ number: 123
448
+ // Le champ 'fileField' est absent ici, il sera fourni dans l'objet 'files'
449
+ };
450
+
451
+ const result = await insertData(comprehensiveTestModelDefinition.name, dataWithFile, { 'fileField[0]': uploadedFile }, currentTestUser, false);
452
+
453
+ expect(result.success, `L'insertion avec un nouveau fichier a échoué: ${result.error}`).toBe(true);
454
+ expect(result.insertedIds).toHaveLength(1);
455
+
456
+ const query = await searchData( { model: comprehensiveTestModelDefinition.name, filter: { _id: new ObjectId(result.insertedIds[0])}}, currentTestUser);
457
+ const insertedDoc = query.data[0];
458
+ expect(insertedDoc).not.toBeNull();
459
+ console.log(insertedDoc.fileField)
460
+ expect(insertedDoc.fileField).toBeInstanceOf(Object);
461
+ expect(insertedDoc.fileField.newFile).toBeUndefined(); // La propriété 'newFile' était une hallucination
462
+ expect(insertedDoc.fileField._id).toBeDefined();
463
+ expect(insertedDoc.fileField.name).toBe('test-upload-on-insert.txt');
464
+ expect(insertedDoc.fileField.guid).toBeDefined();
465
+
466
+ await removeFile(insertedDoc.fileField.guid, currentTestUser);
467
+
468
+ // Nettoyage du répertoire et fichier temporaire
469
+ await fs.rm(tmpDir, { recursive: true, force: true });
470
+ await purge();
471
+ });
417
472
  });
418
473
 
419
474
  describe('editData avec comprehensiveTestModel', async () => {
@@ -488,6 +543,97 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
488
543
  // TODO: Ajouter plus de tests de validation pour editData, similaires à ceux de insertData
489
544
  });
490
545
 
546
+ describe('$push operation using patchData', () =>{
547
+ describe('$push sur tableau existant', () => {
548
+ let docToPatchId;
549
+ let setupContext;
550
+
551
+ beforeEach(async () => {
552
+ setupContext = await setupTestContext();
553
+ const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
554
+
555
+ const initialData = {
556
+ stringRequired: 'InitialForPush',
557
+ stringUnique: 'UniqueForPush',
558
+ enumField: 'alpha',
559
+ passwordField: 'pushPass',
560
+ arrayString: ['tag1'],
561
+ arrayNumber: [100]
562
+ };
563
+ const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
564
+ docToPatchId = insertResult.insertedIds[0];
565
+ });
566
+
567
+ afterEach(async () => {
568
+ await setupContext.purge();
569
+ });
570
+
571
+ it('devrait ajouter une seule valeur à un tableau existant', async () => {
572
+ const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
573
+
574
+ const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayString: { $push: 'tag2' } }, {}, currentTestUser);
575
+
576
+ expect(result.success, `patchData avec $push a échoué: ${result.error}`).toBe(true);
577
+ expect(result.modifiedCount).toBe(1);
578
+
579
+ const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docToPatchId) });
580
+ expect(patchedDoc.arrayString).toEqual(['tag1', 'tag2']);
581
+ });
582
+
583
+ it('devrait ajouter plusieurs valeurs à un tableau existant', async () => {
584
+ const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
585
+
586
+ const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayNumber: { $push: [200, 300] } }, {}, currentTestUser);
587
+
588
+ expect(result.success, `patchData avec $push multiple a échoué: ${result.error}`).toBe(true);
589
+ expect(result.modifiedCount).toBe(1);
590
+
591
+ const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docToPatchId) });
592
+ expect(patchedDoc.arrayNumber).toEqual([100, 200, 300]);
593
+ });
594
+
595
+ it('devrait rejeter un push si la valeur est invalide', async () => {
596
+ const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
597
+
598
+ // Tenter de pusher un nombre dans un tableau de strings
599
+ const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayString: { $push: 12345 } }, {}, currentTestUser);
600
+
601
+ expect(result.success).toBe(false);
602
+ expect(result.error).toContain("Valeur invalide fournie pour le tableau 'arrayString'");
603
+ });
604
+
605
+ it('devrait rejeter un push si une des valeurs du tableau est invalide', async () => {
606
+ const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
607
+
608
+ // Tenter de pusher un tableau contenant une valeur invalide
609
+ const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayNumber: { $push: [400, 'not-a-number', 500] } }, {}, currentTestUser);
610
+
611
+ expect(result.success).toBe(false);
612
+ expect(result.error).toContain("Valeur invalide fournie pour le tableau 'arrayNumber'");
613
+ });
614
+
615
+ });
616
+ describe('$push sur un tableau inexistant', () => {
617
+ it('devrait créer le tableau et pusher les valeurs s\'il n\'existe pas', async () => {
618
+ // On utilise un setup de contexte complètement isolé pour ce test
619
+ const { currentTestUser, comprehensiveTestModelDefinition, purge } = await setupTestContext();
620
+
621
+ const initialData = { stringRequired: 'NoArrayField', stringUnique: 'UniqueForNoArrayPush', enumField: 'beta', passwordField: 'pass' };
622
+ const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
623
+ const newDocId = insertResult.insertedIds[0];
624
+
625
+ const result = await patchData(comprehensiveTestModelDefinition.name, { _id: newDocId }, { arrayString: { $push: ['first', 'second'] } }, {}, currentTestUser);
626
+
627
+ expect(result.success, `patchData sur un tableau inexistant a échoué: ${result.error}`).toBe(true);
628
+ expect(result.modifiedCount).toBe(1);
629
+
630
+ const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(newDocId) });
631
+ expect(patchedDoc.arrayString).toEqual(['first', 'second']);
632
+ await purge();
633
+ });
634
+ });
635
+ });
636
+
491
637
  // --- Tests deleteData (repris de votre exemple, peuvent être gardés tels quels ou adaptés) ---
492
638
  describe('deleteData (tests existants)', () => {
493
639
  it('supprimer des données par ID (deleteData)', async () => {