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
@@ -38,7 +38,7 @@ import {
38
38
  maxStringLength,
39
39
  maxTotalDataPerUser,
40
40
  megabytes,
41
- optionsSanitizer, plans,
41
+ optionsSanitizer,
42
42
  searchRequestTimeout, storageSafetyMargin
43
43
  } from "../constants.js";
44
44
  import {
@@ -72,7 +72,6 @@ import {
72
72
  } from "./workflow.js";
73
73
  import NodeCache from "node-cache";
74
74
  import AWS from 'aws-sdk';
75
- import {getUserStorageLimit} from "../user.js";
76
75
  import {openaiJobModel} from "../openai.jobs.js";
77
76
  import checkDiskSpace from "check-disk-space";
78
77
  import { fileURLToPath } from 'url';
@@ -82,7 +81,6 @@ import {listS3Backups, uploadToS3} from "./bucket.js";
82
81
  import {
83
82
  calculateTotalUserStorageUsage, generateLimiter, hasPermission,
84
83
  middlewareAuthenticator,
85
- myFreePremiumAnonymousLimiter,
86
84
  userInitiator
87
85
  } from "./user.js";
88
86
  import {assistantGlobalLimiter} from "./assistant.js";
@@ -176,7 +174,7 @@ export const dataTypes = {
176
174
  },
177
175
  cronSchedule: {
178
176
  validate: (value, field) => {
179
- if( value == null )
177
+ if( value === null )
180
178
  return true;
181
179
  try {
182
180
  cronstrue.toString(value, { throwExceptionOnParseError: true });
@@ -186,7 +184,7 @@ export const dataTypes = {
186
184
  }
187
185
  },
188
186
  filter: async (value, field)=>{
189
- if( value == null )
187
+ if( value === null )
190
188
  return null;
191
189
  if (field.cronMask && field.default) {
192
190
  return applyCronMask(value, field.cronMask, field.default);
@@ -242,7 +240,7 @@ export const dataTypes = {
242
240
  },
243
241
  'string_t': {
244
242
  validate: (value, field) => {
245
- if( value == null )
243
+ if( value === null )
246
244
  return true;
247
245
  const ml = Math.min(Math.max(field.maxlength, 0), maxStringLength);
248
246
  // La valeur peut être une chaîne de caractères...
@@ -430,7 +428,7 @@ export const dataTypes = {
430
428
  }
431
429
  return value === null || value === undefined || isObjectId(value) || typeof(value) === 'object';
432
430
  },
433
- anonymize: () => null,
431
+ anonymize: () => null
434
432
  },
435
433
  file: {
436
434
  validate: (value, field) => {
@@ -467,7 +465,7 @@ export const dataTypes = {
467
465
 
468
466
  return value;
469
467
  },
470
- anonymize: () => null,
468
+ anonymize: () => null
471
469
  },
472
470
  color: {
473
471
  validate: (value) => {
@@ -513,7 +511,7 @@ export const dataTypes = {
513
511
  return sanitizedObject;
514
512
  },
515
513
  anonymize: () => ({}) // Anonymisation en objet vide
516
- },
514
+ }
517
515
  };
518
516
 
519
517
  export const getAPILang = (langs) => {
@@ -660,138 +658,138 @@ const validateField = (field) => {
660
658
 
661
659
  // Check for specific field types
662
660
  switch (field.type) {
663
- case 'relation':
664
- allowedFieldTest(['relation', 'multiple', 'relationFilter']);
665
- if (!field.relation || typeof field.relation !== 'string' || field.relation.length > maxModelNameLength) {
666
- throw new Error(i18n.t('api.validate.requiredFieldString', "Le champ '{{0}}' est requis et doit être une chaîne de caractères.", ["relation"]));
667
- }
668
- if (field.multiple !== undefined && typeof field.multiple !== 'boolean') {
669
- throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["multiple"]));
670
- }
671
- if( field.relationFilter && typeof field.relationFilter !== 'object'){
672
- throw new Error(i18n.t('api.validate.fieldObject', "L'attribut '{{0}}' doit être un objet.", ["relationFilter"]));
673
- }
674
- break;
675
- case 'enum':
676
- {
677
- allowedFieldTest(['items']);
678
- if (!field.items || !Array.isArray(field.items) || field.items.length === 0) {
679
- throw new Error(i18n.t('api.validate.fieldStringArray', "L'attribut '{{0}}' doit être un tableau de chaines de caractères.", ["items"]));
680
- }
681
- let id = field.items.findIndex(item => typeof item !== 'string');
682
- if( id !== -1 ){
683
- throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["items["+id+"]"]));
684
- }
685
- break;
661
+ case 'relation':
662
+ allowedFieldTest(['relation', 'multiple', 'relationFilter']);
663
+ if (!field.relation || typeof field.relation !== 'string' || field.relation.length > maxModelNameLength) {
664
+ throw new Error(i18n.t('api.validate.requiredFieldString', "Le champ '{{0}}' est requis et doit être une chaîne de caractères.", ["relation"]));
686
665
  }
687
- case 'number':
688
- allowedFieldTest(['min', 'max', 'step', 'unit']);
689
- if (field.min !== undefined && typeof field.min !== 'number') {
690
- throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["min"]));
691
- }
692
- if (field.max !== undefined && typeof field.max !== 'number') {
693
- throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["max"]));
694
- }
695
- if (field.max < field.min ){
696
- throw new Error(i18n.t('api.validate.inferiorTo', "L'attribut '{{0}}' doit être inférieur à l'attribut '{{1}}'.", ["min", "max"]));
697
- }
698
- if (field.step !== undefined && typeof field.step !== 'number') {
699
- throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["step"]));
700
- }
701
- if (field.unit !== undefined && typeof field.unit !== 'string') {
702
- throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["unit"]));
703
- }
704
- break;
705
- case 'string':
706
- case 'string_t':
707
- case 'richtext':
708
- case 'richtext_t':
709
- case 'url':
710
- case 'email':
711
- case 'phone':
712
- case 'password':
713
- case 'code':
714
- if (field.type === 'code')
715
- allowedFieldTest(['maxlength', 'language', 'conditionBuilder', 'targetModel']);
716
- else if( ['string_t', 'string'].includes(field.type))
717
- allowedFieldTest(['maxlength', 'multiline']);
718
- else
719
- allowedFieldTest(['maxlength']);
720
- if (field.maxlength !== undefined && typeof field.maxlength !== 'number') {
721
- throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxlength"]));
722
- }
723
- break;
724
- case 'model':
725
- case 'modelField':
726
- allowedFieldTest([]);
727
- break;
728
- case 'object':
729
- allowedFieldTest([]);
730
- break;
731
- case 'boolean':
732
- allowedFieldTest([]);
733
- break;
734
- case 'date':
735
- case 'datetime':
736
- {
737
- allowedFieldTest(['min','max']);
738
- if (field.min !== undefined && typeof field.min !== 'string') {
739
- throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["min"]));
740
- }
741
- if (field.max !== undefined && typeof field.max !== 'string') {
742
- throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["max"]));
743
- }
744
- const dtMin = field.min ? new Date(field.min) : null;
745
- const dtMax = field.max ? new Date(field.max) : null;
746
- if( dtMin && dtMax && dtMin > dtMax){
747
- throw new Error(i18n.t('api.validate.inferiorTo', "L'attribut '{{0}}' doit être inférieur à l'attribut '{{1}}'.", ["min", "max"]));
748
- }
749
- break;
666
+ if (field.multiple !== undefined && typeof field.multiple !== 'boolean') {
667
+ throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["multiple"]));
750
668
  }
751
- case 'image':
752
- case 'file':
753
- {
754
- allowedFieldTest(['mimeTypes', 'maxSize']);
755
- if (field.mimeTypes !== undefined && !Array.isArray(field.mimeTypes)) {
756
- throw new Error(i18n.t('api.validate.fieldStringArray', "L'attribut '{{0}}' doit être un tableau de chaines de caractères.", ["mimeTypes"]));
757
- }
758
- let id;
759
- if (field.mimeTypes !== undefined && (id = field.mimeTypes.findIndex(item => typeof item !== 'string')) !== -1) {
760
- throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["mimeTypes["+id+"]"]));
761
- }
762
- if (field.maxSize !== undefined && typeof field.maxSize !== 'number') {
763
- throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxSize"]));
764
- }
765
- if (field.maxSize !== undefined && field.maxSize > maxFileSize) {
766
- throw new Error(i18n.t('api.validate.fileSize', `L'attribut 'maxSize' ne doit pas dépasser {{0}} octets.`, [maxFileSize]));
767
- }
768
- break;
769
- }
770
- case 'color':
771
- allowedFieldTest([]);
772
- return true;
773
- case 'cronSchedule':
774
- allowedFieldTest(['cronMask']);
775
- return true;
776
- case 'calculated':
777
- allowedFieldTest(['calculation']);
778
- return true;
779
- case 'array':
780
- if (!field.itemsType || typeof field.itemsType !== 'string') {
781
- throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["itemsType"]));
782
- }
783
- if (!dataTypes[field.itemsType]) {
784
- throw new Error(i18n.t('api.validate.invalidField', `Champ(s) non valide(s): '{{0}}'`, ["itemsType"]));
785
- }
786
- if (field.minItems !== undefined && typeof field.minItems !== 'number') {
787
- throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["minItems"]));
788
- }
789
- if (field.maxItems !== undefined && typeof field.maxItems !== 'number') {
790
- throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxItems"]));
791
- }
792
- break;
793
- default:
794
- throw new Error(i18n.t('api.validate.unknowType',`Le type '{{0}}' n'est pas reconnu.`, [field.type]));
669
+ if( field.relationFilter && typeof field.relationFilter !== 'object'){
670
+ throw new Error(i18n.t('api.validate.fieldObject', "L'attribut '{{0}}' doit être un objet.", ["relationFilter"]));
671
+ }
672
+ break;
673
+ case 'enum':
674
+ {
675
+ allowedFieldTest(['items']);
676
+ if (!field.items || !Array.isArray(field.items) || field.items.length === 0) {
677
+ throw new Error(i18n.t('api.validate.fieldStringArray', "L'attribut '{{0}}' doit être un tableau de chaines de caractères.", ["items"]));
678
+ }
679
+ let id = field.items.findIndex(item => typeof item !== 'string');
680
+ if( id !== -1 ){
681
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["items["+id+"]"]));
682
+ }
683
+ break;
684
+ }
685
+ case 'number':
686
+ allowedFieldTest(['min', 'max', 'step', 'unit']);
687
+ if (field.min !== undefined && typeof field.min !== 'number') {
688
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["min"]));
689
+ }
690
+ if (field.max !== undefined && typeof field.max !== 'number') {
691
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["max"]));
692
+ }
693
+ if (field.max < field.min ){
694
+ throw new Error(i18n.t('api.validate.inferiorTo', "L'attribut '{{0}}' doit être inférieur à l'attribut '{{1}}'.", ["min", "max"]));
695
+ }
696
+ if (field.step !== undefined && typeof field.step !== 'number') {
697
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["step"]));
698
+ }
699
+ if (field.unit !== undefined && typeof field.unit !== 'string') {
700
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["unit"]));
701
+ }
702
+ break;
703
+ case 'string':
704
+ case 'string_t':
705
+ case 'richtext':
706
+ case 'richtext_t':
707
+ case 'url':
708
+ case 'email':
709
+ case 'phone':
710
+ case 'password':
711
+ case 'code':
712
+ if (field.type === 'code')
713
+ allowedFieldTest(['maxlength', 'language', 'conditionBuilder', 'targetModel']);
714
+ else if( ['string_t', 'string'].includes(field.type))
715
+ allowedFieldTest(['maxlength', 'multiline']);
716
+ else
717
+ allowedFieldTest(['maxlength']);
718
+ if (field.maxlength !== undefined && typeof field.maxlength !== 'number') {
719
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxlength"]));
720
+ }
721
+ break;
722
+ case 'model':
723
+ case 'modelField':
724
+ allowedFieldTest([]);
725
+ break;
726
+ case 'object':
727
+ allowedFieldTest([]);
728
+ break;
729
+ case 'boolean':
730
+ allowedFieldTest([]);
731
+ break;
732
+ case 'date':
733
+ case 'datetime':
734
+ {
735
+ allowedFieldTest(['min','max']);
736
+ if (field.min !== undefined && typeof field.min !== 'string') {
737
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["min"]));
738
+ }
739
+ if (field.max !== undefined && typeof field.max !== 'string') {
740
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["max"]));
741
+ }
742
+ const dtMin = field.min ? new Date(field.min) : null;
743
+ const dtMax = field.max ? new Date(field.max) : null;
744
+ if( dtMin && dtMax && dtMin > dtMax){
745
+ throw new Error(i18n.t('api.validate.inferiorTo', "L'attribut '{{0}}' doit être inférieur à l'attribut '{{1}}'.", ["min", "max"]));
746
+ }
747
+ break;
748
+ }
749
+ case 'image':
750
+ case 'file':
751
+ {
752
+ allowedFieldTest(['mimeTypes', 'maxSize']);
753
+ if (field.mimeTypes !== undefined && !Array.isArray(field.mimeTypes)) {
754
+ throw new Error(i18n.t('api.validate.fieldStringArray', "L'attribut '{{0}}' doit être un tableau de chaines de caractères.", ["mimeTypes"]));
755
+ }
756
+ let id;
757
+ if (field.mimeTypes !== undefined && (id = field.mimeTypes.findIndex(item => typeof item !== 'string')) !== -1) {
758
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["mimeTypes["+id+"]"]));
759
+ }
760
+ if (field.maxSize !== undefined && typeof field.maxSize !== 'number') {
761
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxSize"]));
762
+ }
763
+ if (field.maxSize !== undefined && field.maxSize > maxFileSize) {
764
+ throw new Error(i18n.t('api.validate.fileSize', `L'attribut 'maxSize' ne doit pas dépasser {{0}} octets.`, [maxFileSize]));
765
+ }
766
+ break;
767
+ }
768
+ case 'color':
769
+ allowedFieldTest([]);
770
+ return true;
771
+ case 'cronSchedule':
772
+ allowedFieldTest(['cronMask']);
773
+ return true;
774
+ case 'calculated':
775
+ allowedFieldTest(['calculation']);
776
+ return true;
777
+ case 'array':
778
+ if (!field.itemsType || typeof field.itemsType !== 'string') {
779
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["itemsType"]));
780
+ }
781
+ if (!dataTypes[field.itemsType]) {
782
+ throw new Error(i18n.t('api.validate.invalidField', `Champ(s) non valide(s): '{{0}}'`, ["itemsType"]));
783
+ }
784
+ if (field.minItems !== undefined && typeof field.minItems !== 'number') {
785
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["minItems"]));
786
+ }
787
+ if (field.maxItems !== undefined && typeof field.maxItems !== 'number') {
788
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxItems"]));
789
+ }
790
+ break;
791
+ default:
792
+ throw new Error(i18n.t('api.validate.unknowType',`Le type '{{0}}' n'est pas reconnu.`, [field.type]));
795
793
  }
796
794
 
797
795
  // Check for optional fields
@@ -846,80 +844,80 @@ function convertDataTypes(dataArray, modelFields, sourceType = 'csv') {
846
844
  }
847
845
 
848
846
  switch (field.type) {
849
- case 'number':
850
- if (typeof value !== 'number') { // Convertir si ce n'est pas déjà un nombre
851
- const num = parseFloat(value);
852
- if (!isNaN(num)) {
853
- convertedRecord[field.name] = num;
854
- } else {
855
- logger.warn(`Import: Impossible de parser le nombre pour le champ ${field.name}, valeur: ${value}. Utilisation de la valeur par défaut/null.`);
856
- convertedRecord[field.name] = getDefaultForType(field);
857
- }
847
+ case 'number':
848
+ if (typeof value !== 'number') { // Convertir si ce n'est pas déjà un nombre
849
+ const num = parseFloat(value);
850
+ if (!isNaN(num)) {
851
+ convertedRecord[field.name] = num;
852
+ } else {
853
+ logger.warn(`Import: Impossible de parser le nombre pour le champ ${field.name}, valeur: ${value}. Utilisation de la valeur par défaut/null.`);
854
+ convertedRecord[field.name] = getDefaultForType(field);
858
855
  }
859
- break;
860
- case 'boolean':
861
- if (typeof value !== 'boolean') {
862
- convertedRecord[field.name] = ['true', '1', 'yes', 'on'].includes(String(value).toLowerCase());
856
+ }
857
+ break;
858
+ case 'boolean':
859
+ if (typeof value !== 'boolean') {
860
+ convertedRecord[field.name] = ['true', '1', 'yes', 'on'].includes(String(value).toLowerCase());
861
+ }
862
+ break;
863
+ case 'date':
864
+ case 'datetime':
865
+ if (String(value).toLowerCase() === 'now') {
866
+ convertedRecord[field.name] = 'now';
867
+ } else {
868
+ const parsedDate = new Date(value);
869
+ if (!isNaN(parsedDate.getTime())) {
870
+ convertedRecord[field.name] = field.type === 'date' ? parsedDate.toISOString().split("T")[0] : parsedDate.toISOString();
871
+ } else if (value) { // Ne pas logger si la valeur était initialement vide/null
872
+ logger.warn(`Import: Impossible de parser la date pour le champ ${field.name}, valeur: ${value}. La validation ulture s'en chargera.`);
863
873
  }
864
- break;
865
- case 'date':
866
- case 'datetime':
867
- if (String(value).toLowerCase() === 'now') {
868
- convertedRecord[field.name] = 'now';
874
+ }
875
+ break;
876
+ case 'array':
877
+ if (sourceType === 'csv' && typeof value === 'string') {
878
+ const arrayValues = value.split(/[,;]/).map(item => item.trim()).filter(item => item !== '');
879
+ if (field.itemsType === 'number') {
880
+ convertedRecord[field.name] = arrayValues.map(v => parseFloat(v)).filter(v => !isNaN(v));
869
881
  } else {
870
- const parsedDate = new Date(value);
871
- if (!isNaN(parsedDate.getTime())) {
872
- convertedRecord[field.name] = field.type === 'date' ? parsedDate.toISOString().split("T")[0] : parsedDate.toISOString();
873
- } else if (value) { // Ne pas logger si la valeur était initialement vide/null
874
- logger.warn(`Import: Impossible de parser la date pour le champ ${field.name}, valeur: ${value}. La validation ulture s'en chargera.`);
875
- }
882
+ convertedRecord[field.name] = arrayValues;
876
883
  }
877
- break;
878
- case 'array':
879
- if (sourceType === 'csv' && typeof value === 'string') {
880
- const arrayValues = value.split(/[,;]/).map(item => item.trim()).filter(item => item !== '');
881
- if (field.itemsType === 'number') {
882
- convertedRecord[field.name] = arrayValues.map(v => parseFloat(v)).filter(v => !isNaN(v));
884
+ } else if (sourceType === 'json' && typeof value === 'string') {
885
+ try {
886
+ const parsedArray = JSON.parse(value);
887
+ if (Array.isArray(parsedArray)) {
888
+ convertedRecord[field.name] = parsedArray;
889
+ // TODO: Potentiellement convertir les éléments de parsedArray ici si nécessaire
883
890
  } else {
884
- convertedRecord[field.name] = arrayValues;
891
+ logger.warn(`Import: La chaîne JSON pour le champ tableau ${field.name} n'a pas été parsée en tableau. Valeur: ${value}.`);
885
892
  }
886
- } else if (sourceType === 'json' && typeof value === 'string') {
887
- try {
888
- const parsedArray = JSON.parse(value);
889
- if (Array.isArray(parsedArray)) {
890
- convertedRecord[field.name] = parsedArray;
891
- // TODO: Potentiellement convertir les éléments de parsedArray ici si nécessaire
892
- } else {
893
- logger.warn(`Import: La chaîne JSON pour le champ tableau ${field.name} n'a pas été parsée en tableau. Valeur: ${value}.`);
894
- }
895
- } catch (e) {
896
- logger.warn(`Import: Impossible de parser la chaîne JSON pour le champ tableau ${field.name}. Valeur: ${value}.`);
897
- }
898
- }
899
- // Si c'est déjà un tableau (cas JSON typique), on suppose que les types des éléments sont corrects
900
- // ou seront validés par pushDataUnsecure.
901
- else if (!Array.isArray(convertedRecord[field.name])) {
902
- convertedRecord[field.name] = getDefaultForType(field);
893
+ } catch (e) {
894
+ logger.warn(`Import: Impossible de parser la chaîne JSON pour le champ tableau ${field.name}. Valeur: ${value}.`);
903
895
  }
904
- break;
905
- case 'object':
906
- if (typeof value === 'string') {
907
- try {
908
- convertedRecord[field.name] = JSON.parse(value);
909
- } catch (e) {
910
- logger.warn(`Import: Impossible de parser la chaîne JSON pour le champ objet ${field.name}. Valeur: ${value}.`);
911
- }
896
+ }
897
+ // Si c'est déjà un tableau (cas JSON typique), on suppose que les types des éléments sont corrects
898
+ // ou seront validés par pushDataUnsecure.
899
+ else if (!Array.isArray(convertedRecord[field.name])) {
900
+ convertedRecord[field.name] = getDefaultForType(field);
901
+ }
902
+ break;
903
+ case 'object':
904
+ if (typeof value === 'string') {
905
+ try {
906
+ convertedRecord[field.name] = JSON.parse(value);
907
+ } catch (e) {
908
+ logger.warn(`Import: Impossible de parser la chaîne JSON pour le champ objet ${field.name}. Valeur: ${value}.`);
912
909
  }
913
- break;
914
- case 'code':
915
- if (field.language === 'json' && typeof value === 'string') {
916
- try {
917
- convertedRecord[field.name] = JSON.parse(value);
918
- } catch (e) {
919
- logger.warn(`Import: Impossible de parser la chaîne JSON pour le champ code (json) ${field.name}. Valeur: ${value}.`);
920
- }
910
+ }
911
+ break;
912
+ case 'code':
913
+ if (field.language === 'json' && typeof value === 'string') {
914
+ try {
915
+ convertedRecord[field.name] = JSON.parse(value);
916
+ } catch (e) {
917
+ logger.warn(`Import: Impossible de parser la chaîne JSON pour le champ code (json) ${field.name}. Valeur: ${value}.`);
921
918
  }
922
- break;
919
+ }
920
+ break;
923
921
  }
924
922
  }
925
923
  }
@@ -1122,15 +1120,15 @@ export const editModel = async (user, id, data) => {
1122
1120
 
1123
1121
  const dataModel = data;
1124
1122
  try {
1125
- const collection = getCollectionForUser(user);
1123
+ const collection = await getCollectionForUser(user);
1126
1124
  validateModelStructure(dataModel);
1127
1125
 
1128
1126
  const el = await modelsCollection.findOne({ $and: [
1129
- {_user: {$exists: true}},
1130
- { _id: new ObjectId(id) },
1131
- {$and: [{_user: {$exists: true}}, {$or: [{_user: user._user}, {_user: user.username}]}]
1132
- }
1133
- ]});
1127
+ {_user: {$exists: true}},
1128
+ { _id: new ObjectId(id) },
1129
+ {$and: [{_user: {$exists: true}}, {$or: [{_user: user._user}, {_user: user.username}]}]
1130
+ }
1131
+ ]});
1134
1132
 
1135
1133
  if( !el ){
1136
1134
  return ({success: false, statusCode: 404, error: i18n.t("api.model.notFound", { model: dataModel.name })});
@@ -1140,18 +1138,18 @@ export const editModel = async (user, id, data) => {
1140
1138
  if (typeof (data.name)==='string'&&el.name !== data.name && data.name ){
1141
1139
  await collection.updateMany({ _model: el.name }, { $set: { _model: data.name }});
1142
1140
  await modelsCollection.updateMany({ 'fields' : {
1143
- '$elemMatch' : { relation: el.name }
1144
- }}, {
1141
+ '$elemMatch' : { relation: el.name }
1142
+ }}, {
1145
1143
  $set : {
1146
1144
  'fields.$.relation' : data.name
1147
1145
  }
1148
1146
  })
1149
1147
  }
1150
1148
 
1151
- const coll = getCollectionForUser(user);
1149
+ const coll = await getCollectionForUser(user);
1150
+ // Update indexes
1152
1151
  // Update indexes
1153
- // Update indexes
1154
- if (user.userPlan === 'premium') {
1152
+ if (await engine.userProvider.hasFeature(user, 'indexes')) {
1155
1153
  let indexes = [];
1156
1154
  try {
1157
1155
  // On essaie de récupérer les index existants
@@ -1224,16 +1222,18 @@ export async function onInit(defaultEngine) {
1224
1222
 
1225
1223
  engine.use(middleware({ whitelist: [
1226
1224
  "$$NOW", "$in", "$eq", "$gt", "$gte", "$in", "$lt", "$lte", "$ne", "$nin", "$type", "$size",
1227
- "$and", "$not", "$nor", "$or", "$regexMatch", "$find", "$elemMatch", "$filter", "$toString", "$toObjectId",
1228
- "$concat",
1229
- '$add', '$subtract', '$multiply', '$divide', '$mod', '$pow', "$sqrt",
1230
- "$rand",
1231
- "$abs", '$sin', '$cos', '$tan', '$asin', '$acos', '$atan',
1232
- "$toDate", "$toBool", "$toString", "$toInt", "$toDouble",
1233
- "$dateSubtract", "$dateAdd", "$dateToString",
1234
- '$year', '$month', '$week', '$dayOfMonth', '$dayOfWeek', '$dayOfYear', '$hour', '$minute', '$second', '$millisecond',
1225
+ "$and", "$not", "$nor", "$or", "$regexMatch", "$find", "$elemMatch", "$filter", "$toString", "$toObjectId",
1226
+ "$concat",
1227
+ '$add', '$subtract', '$multiply', '$divide', '$mod', '$pow', "$sqrt",
1228
+ "$rand",
1229
+ "$abs", '$sin', '$cos', '$tan', '$asin', '$acos', '$atan',
1230
+ "$toDate", "$toBool", "$toString", "$toInt", "$toDouble",
1231
+ "$dateSubtract", "$dateAdd", "$dateToString",
1232
+ '$year', '$month', '$week', '$dayOfMonth', '$dayOfWeek', '$dayOfYear', '$hour', '$minute', '$second', '$millisecond'
1235
1233
  ]}));
1236
1234
 
1235
+ let userMiddlewares = await engine.userProvider.getMiddlewares();
1236
+
1237
1237
  let modelsCollection, datasCollection, filesCollection, packsCollection, magnetsCollection;
1238
1238
 
1239
1239
  if( install ) {
@@ -1322,9 +1322,8 @@ export async function onInit(defaultEngine) {
1322
1322
  await scheduleWorkflowTriggers();
1323
1323
 
1324
1324
  await scheduleAlerts();
1325
-
1326
- // Dans onInit(defaultEngine) { ... }
1327
- // ...
1325
+ // Dans onInit(defaultEngine) { ... }
1326
+ // ...
1328
1327
 
1329
1328
  const saveUser = async (user, data) => {
1330
1329
 
@@ -1369,11 +1368,12 @@ export async function onInit(defaultEngine) {
1369
1368
  });
1370
1369
  await modelsCollection.insertMany(newModels);
1371
1370
 
1371
+ const coll = await getCollectionForUser(user);
1372
1372
  // 2. Copier les données associes marquer
1373
1373
  // C'est la partie la plus complexe à cause des relations
1374
1374
  const idMap = {}; // Map: old_id -> new_id
1375
1375
  for (const model of modelsToCopy) {
1376
- const datasToCopy = await getCollectionForUser(user).find({
1376
+ const datasToCopy = await coll.find({
1377
1377
  _model: model.name,
1378
1378
  _user: user.username
1379
1379
  }).limit(maxMagnetsDataPerModel).toArray();
@@ -1391,7 +1391,8 @@ export async function onInit(defaultEngine) {
1391
1391
  _user: null
1392
1392
  };
1393
1393
  });
1394
- await getCollectionForUser(user).insertMany(newDatas);
1394
+ const coll = await getCollectionForUser(user);
1395
+ await coll.insertMany(newDatas);
1395
1396
 
1396
1397
  // Passe 2: Mettre à jour les relations dans les documents fraîchement copiés
1397
1398
  for (const newDoc of newDatas) {
@@ -1409,7 +1410,8 @@ export async function onInit(defaultEngine) {
1409
1410
  }
1410
1411
  }
1411
1412
  if (Object.keys(updatePayload).length > 0) {
1412
- await getCollectionForUser(user).updateOne({ _id: newDoc._id }, { $set: updatePayload });
1413
+ const coll = await getCollectionForUser(user);
1414
+ await coll.updateOne({ _id: newDoc._id }, { $set: updatePayload });
1413
1415
  }
1414
1416
  }
1415
1417
  }
@@ -1505,7 +1507,7 @@ export async function onInit(defaultEngine) {
1505
1507
  });
1506
1508
  });
1507
1509
 
1508
- engine.post('/api/data/import', [middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1510
+ engine.post('/api/data/import', [middlewareAuthenticator, userInitiator, ...userMiddlewares, setTimeoutMiddleware(60000)], async (req, res) => {
1509
1511
  // ... (vérifications de permissions existantes) ...
1510
1512
  const result = await importData(req.fields, req.files, req.me);
1511
1513
  if( result.success ){
@@ -1572,7 +1574,7 @@ export async function onInit(defaultEngine) {
1572
1574
  }
1573
1575
  });
1574
1576
 
1575
- engine.post('/api/data/restore', [throttle, middlewareAuthenticator, userInitiator,myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1577
+ engine.post('/api/data/restore', [throttle, middlewareAuthenticator, userInitiator, ...userMiddlewares, setTimeoutMiddleware(60000)], async (req, res) => {
1576
1578
 
1577
1579
  if (!((user?.roles || []).includes("admin"))) {
1578
1580
  return res.status(403).json({success: false, error: 'Cannot backup data. Contact an administrator to get back your data'})
@@ -1587,7 +1589,7 @@ export async function onInit(defaultEngine) {
1587
1589
  }
1588
1590
  });
1589
1591
 
1590
- engine.post('/api/data/dump', [throttle, middlewareAuthenticator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1592
+ engine.post('/api/data/dump', [throttle, middlewareAuthenticator, ...userMiddlewares, setTimeoutMiddleware(60000)], async (req, res) => {
1591
1593
 
1592
1594
  if (!((req.me?.roles || []).includes("admin"))) {
1593
1595
  return res.status(403).json({success: false, error: 'Cannot dump data.'})
@@ -1606,7 +1608,7 @@ export async function onInit(defaultEngine) {
1606
1608
  }
1607
1609
  });
1608
1610
 
1609
- engine.post('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(15000)], async (req, res) => {
1611
+ engine.post('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, ...userMiddlewares, setTimeoutMiddleware(15000)], async (req, res) => {
1610
1612
  const body = req.files ? req.fields : req.fields;
1611
1613
  const modelName = body.model; // Les données à insérer/mettre à jour (assurez-vous de valider et nettoyer ces données côté client et serveur !)
1612
1614
  const data = body.data || (body._data && JSON.parse(body._data));
@@ -1622,7 +1624,7 @@ export async function onInit(defaultEngine) {
1622
1624
  }
1623
1625
  });
1624
1626
 
1625
- engine.post('/api/data/search', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(30000)], async (req, res) => {
1627
+ engine.post('/api/data/search', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, ...userMiddlewares, setTimeoutMiddleware(30000)], async (req, res) => {
1626
1628
  const { pack } = req.fields;
1627
1629
 
1628
1630
  try {
@@ -1668,8 +1670,8 @@ export async function onInit(defaultEngine) {
1668
1670
  }
1669
1671
  });
1670
1672
 
1671
- // --- Export Endpoint ---
1672
- engine.post('/api/data/export', [middlewareAuthenticator, throttle, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1673
+ // --- Export Endpoint ---
1674
+ engine.post('/api/data/export', [middlewareAuthenticator, throttle, userInitiator, ...userMiddlewares, setTimeoutMiddleware(60000)], async (req, res) => {
1673
1675
  try {
1674
1676
  const results = await exportData({...req.fields, depth:req.query.depth, lang: req.query.lang}, req.me);
1675
1677
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -1697,7 +1699,7 @@ export async function onInit(defaultEngine) {
1697
1699
  }
1698
1700
  });
1699
1701
 
1700
- engine.patch('/api/data/:id?', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1702
+ engine.patch('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1701
1703
  const filter = req.fields.filter;
1702
1704
  const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1703
1705
  const data = req.fields.data || (req.fields._data && JSON.parse(req.fields._data));
@@ -1709,7 +1711,7 @@ export async function onInit(defaultEngine) {
1709
1711
  }
1710
1712
  });
1711
1713
 
1712
- engine.put('/api/data/:id?', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1714
+ engine.put('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1713
1715
  try {
1714
1716
  const filter = req.fields.filter;
1715
1717
  const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
@@ -1724,27 +1726,33 @@ export async function onInit(defaultEngine) {
1724
1726
  }
1725
1727
  });
1726
1728
 
1727
- engine.get('/api/models', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1728
-
1729
- if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_GET_MODELS"], req.me)){
1730
- return res.status(403).json({success: false, error: i18n.t('api.permission.getModels')})
1729
+ engine.patch('/api/data/:id', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1730
+ const filter = req.fields.filter;
1731
+ const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1732
+ const data = req.fields.data || (req.fields._data && JSON.parse(req.fields._data));
1733
+ const r = await patchData(req.fields.model, filter || hash, data, req.files, req.me);
1734
+ if (r.error) {
1735
+ res.status(400).json(r);
1736
+ } else {
1737
+ res.status(200).json(r);
1731
1738
  }
1739
+ });
1732
1740
 
1733
- // get by name
1741
+ engine.put('/api/data/:id', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1734
1742
  try {
1735
- let models = await modelsCollection.find({$or: [{_user: {$exists: false}}]})
1736
- .sort({_user:-1, _id: 1 }).limit(maxModelsPerUser).toArray();
1737
- models = models
1738
- .concat(
1739
- await modelsCollection.find({$or: [{_user: req.me._user}, {_user: req.me.username}]})
1740
- .sort({_user:-1, _id: 1 })
1741
- .limit(maxModelsPerUser).toArray());
1742
- res.json(models);
1743
- } catch (error) {
1744
- console.log(error);
1745
- logger.error(error);
1743
+ const filter = req.fields.filter;
1744
+ const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1745
+ const data = req.fields.data || (req.fields._data && JSON.parse(req.fields._data));
1746
+ const r = await editData(req.fields.model, filter || hash, data, req.files, req.me)
1747
+ if (r.error)
1748
+ res.status(400).json(r);
1749
+ else
1750
+ res.status(200).json(r);
1751
+ } catch (e) {
1752
+ res.status(500).json({error: e.message});
1746
1753
  }
1747
1754
  });
1755
+
1748
1756
  engine.get('/api/model', [throttle, middlewareAuthenticator, userInitiator,middlewareLogger], async (req, res) => {
1749
1757
 
1750
1758
  // get by name
@@ -1762,9 +1770,32 @@ export async function onInit(defaultEngine) {
1762
1770
  res.json(model);
1763
1771
  } catch (error) {
1764
1772
  logger.error(error);
1773
+ res.status(404).json({ success: false, error: error.message });
1774
+ }
1775
+ });
1776
+ engine.get('/api/models', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1777
+
1778
+ if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_GET_MODELS"], req.me)){
1779
+ return res.status(403).json({success: false, error: i18n.t('api.permission.getModels')})
1780
+ }
1781
+
1782
+ // get by name
1783
+ try {
1784
+ let models = await modelsCollection.find({$or: [{_user: {$exists: false}}]})
1785
+ .sort({_user:-1, _id: 1 }).limit(maxModelsPerUser).toArray();
1786
+ models = models
1787
+ .concat(
1788
+ await modelsCollection.find({$or: [{_user: req.me._user}, {_user: req.me.username}]})
1789
+ .sort({_user:-1, _id: 1 })
1790
+ .limit(maxModelsPerUser).toArray());
1791
+ res.json(models);
1792
+ } catch (error) {
1793
+ console.log(error);
1794
+ logger.error(error);
1795
+ res.status(500).json({ success: false, error: error.message });
1765
1796
  }
1766
1797
  });
1767
- engine.post('/api/model', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter], async (req, res) => {
1798
+ engine.post('/api/model', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, ...userMiddlewares], async (req, res) => {
1768
1799
 
1769
1800
  if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_ADD_MODEL"], req.me) ){
1770
1801
  return res.status(403).json({success: false, error: i18n.t('api.permission.addModel')})
@@ -1786,7 +1817,7 @@ export async function onInit(defaultEngine) {
1786
1817
  $and: [{_user: {$exists: true}}, {_user: req.me.username}]
1787
1818
  });
1788
1819
  if( count < maxModelsPerUser) {
1789
- if( req.me.userPlan === 'premium' ){
1820
+ if(await engine.userProvider.hasFeature(req.me, 'indexes')){
1790
1821
  for (const field of modelData.fields) {
1791
1822
  if( field.index ) {
1792
1823
  await datasCollection.createIndex({[field.name]: 1}, {
@@ -1818,7 +1849,7 @@ export async function onInit(defaultEngine) {
1818
1849
  }
1819
1850
  });
1820
1851
 
1821
- engine.post('/api/models/import', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger,myFreePremiumAnonymousLimiter], async (req, res) => {
1852
+ engine.post('/api/models/import', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, ...userMiddlewares], async (req, res) => {
1822
1853
 
1823
1854
  if( isLocalUser(req.me) && !await hasPermission(["API_ADMIN","API_IMPORT_MODEL"], req.me)){
1824
1855
  return res.status(403).json({success: false, error: i18n.t('api.permission.importModels')})
@@ -1846,7 +1877,7 @@ export async function onInit(defaultEngine) {
1846
1877
  try {
1847
1878
  files.forEach(file =>removeFile(file.guid, req.me));
1848
1879
  } catch (e) {
1849
-
1880
+
1850
1881
  }
1851
1882
 
1852
1883
  await cancelAlerts(req.me);
@@ -1888,7 +1919,7 @@ export async function onInit(defaultEngine) {
1888
1919
  return res.status(404).json({error: i18n.t( "api.model.notFound", { model: modelName})});
1889
1920
  }
1890
1921
 
1891
- if( req.me.userPlan === 'premium' ) {
1922
+ if( await engine.userProvider.hasFeature(req.me, 'indexes') ) {
1892
1923
  const indexes = await datasCollection.indexes();
1893
1924
  for (const index of indexes) {
1894
1925
  if (index.partialFilterExpression?._model === model.name &&
@@ -1958,7 +1989,7 @@ export async function onInit(defaultEngine) {
1958
1989
  if (result.modifiedCount !== 1)
1959
1990
  return res.status(404).json({ error: i18n.t('api.model.notFound', {model: model.name})});
1960
1991
 
1961
- const collection = getCollectionForUser(req.me);
1992
+ const collection = await getCollectionForUser(req.me);
1962
1993
 
1963
1994
  await collection.updateMany(
1964
1995
  { _model: model.name },
@@ -1983,7 +2014,7 @@ export async function onInit(defaultEngine) {
1983
2014
  });
1984
2015
 
1985
2016
 
1986
- // Endpoint pour calculer la valeur d'UN KPI spécifique par son ID
2017
+ // Endpoint pour calculer la valeur d'UN KPI spécifique par son ID
1987
2018
  engine.get('/api/kpis/calculate/:id', [throttle, middlewareAuthenticator, userInitiator], async (req, res) => {
1988
2019
  const { id } = req.params;
1989
2020
 
@@ -2124,11 +2155,11 @@ export async function onInit(defaultEngine) {
2124
2155
  // Dans server/src/modules/data.js, modifiez la route POST /api/charts/aggregate
2125
2156
 
2126
2157
  // Dans server/src/modules/data.js, modifiez la route POST /api/charts/aggregate
2127
- // C:/Dev/hackersonline-engine/server/src/modules/data.js
2158
+ // C:/Dev/hackersonline-engine/server/src/modules/data.js
2128
2159
 
2129
- // ... (autres imports et code)
2160
+ // ... (autres imports et code)
2130
2161
 
2131
- engine.post('/api/charts/aggregate', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(15000)], async (req, res) => {
2162
+ engine.post('/api/charts/aggregate', [throttle, middlewareAuthenticator, userInitiator, ...userMiddlewares, setTimeoutMiddleware(15000)], async (req, res) => {
2132
2163
  // --- Récupérer groupByLabelField ---
2133
2164
  const { model: modelName, type, xAxis, yAxis, groupBy, aggregationType, groupByLabelField, filter: chartFilter } = req.fields;
2134
2165
 
@@ -2173,7 +2204,7 @@ export async function onInit(defaultEngine) {
2173
2204
  const isMultipleRelation = isRelationGroupBy && groupByFieldDefinition?.multiple === true;
2174
2205
 
2175
2206
  // --- Build Aggregation Pipeline ---
2176
- const collection = getCollectionForUser(req.me);
2207
+ const collection = await getCollectionForUser(req.me);
2177
2208
 
2178
2209
  // --- MODIFICATION ICI pour inclure chartFilter ---
2179
2210
  let initialMatchStage = { $and : [{
@@ -2198,7 +2229,7 @@ export async function onInit(defaultEngine) {
2198
2229
  sum: '$sum',
2199
2230
  avg: '$avg',
2200
2231
  min: '$min',
2201
- max: '$max',
2232
+ max: '$max'
2202
2233
  // median needs special handling later
2203
2234
  // *** AJOUT: 'value' sera géré par $first (ou $last) ***
2204
2235
  };
@@ -2350,7 +2381,7 @@ export async function onInit(defaultEngine) {
2350
2381
  });
2351
2382
 
2352
2383
 
2353
- engine.post('/api/data/removeFromPack', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
2384
+ engine.post('/api/data/removeFromPack', [throttle, middlewareAuthenticator, userInitiator, ...userMiddlewares], async (req, res) => {
2354
2385
  if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_CREATE_PACK"], req.me)){
2355
2386
  return res.status(403).json({success: false, error: i18n.t('api.permission.createPack')})
2356
2387
  }
@@ -2359,7 +2390,7 @@ export async function onInit(defaultEngine) {
2359
2390
  return res.status(400).json({ success: false, error: 'itemIds must be a non-empty array of valid ObjectIds.' });
2360
2391
  }
2361
2392
  const objectIds = itemIds.map(id => new ObjectId(id));
2362
- const collection = getCollectionForUser(req.me); // Obtenir la collection de l'utilisateur
2393
+ const collection = await getCollectionForUser(req.me); // Obtenir la collection de l'utilisateur
2363
2394
 
2364
2395
  const results = await collection.find({
2365
2396
  _id: { $in: objectIds },
@@ -2456,7 +2487,7 @@ export async function onInit(defaultEngine) {
2456
2487
  }
2457
2488
 
2458
2489
  // --- Logique de suppression ---
2459
- const collection = getCollectionForUser(user); // Obtenir la collection spécifique à l'utilisateur
2490
+ const collection = await getCollectionForUser(user); // Obtenir la collection spécifique à l'utilisateur
2460
2491
 
2461
2492
  const deleteFilter = {
2462
2493
  _pack: packName,
@@ -2483,7 +2514,7 @@ export async function onInit(defaultEngine) {
2483
2514
  }
2484
2515
  });
2485
2516
 
2486
- engine.post('/api/data/addToPack', [throttle, middlewareAuthenticator, userInitiator,myFreePremiumAnonymousLimiter], async (req, res) => {
2517
+ engine.post('/api/data/addToPack', [throttle, middlewareAuthenticator, userInitiator,...userMiddlewares], async (req, res) => {
2487
2518
  const { packName, itemIds } = req.fields;
2488
2519
  const user = req.me;
2489
2520
 
@@ -2500,13 +2531,13 @@ export async function onInit(defaultEngine) {
2500
2531
 
2501
2532
  // --- Logique Métier ---
2502
2533
  try {
2503
- const collection = getCollectionForUser(user); // Récupère la collection de l'utilisateur
2534
+ const collection = await getCollectionForUser(user); // Récupère la collection de l'utilisateur
2504
2535
  const objectIds = itemIds.map(id => new ObjectId(id)); // Convertit les strings en ObjectIds
2505
2536
 
2506
2537
  // Met à jour le champ _pack pour les documents sélectionnés appartenant à l'utilisateur
2507
2538
  const copyData = await collection.find({
2508
2539
  _id: { $in: objectIds },
2509
- _user: user.username,
2540
+ _user: user.username
2510
2541
  }).toArray();
2511
2542
 
2512
2543
 
@@ -2552,12 +2583,12 @@ export async function onInit(defaultEngine) {
2552
2583
  }
2553
2584
  });
2554
2585
  /*
2555
- engine.post('/api/packs/install', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
2586
+ engine.post('/api/packs/install', [throttle, middlewareAuthenticator, userInitiator, ...userMiddlewares], async (req, res) => {
2556
2587
 
2557
2588
  const { pack } = req.fields;
2558
2589
  const initialModelName = req.query.model; // The starting model
2559
2590
  const user = req.me;
2560
- const collection = getCollectionForUser(user);
2591
+ const collection = await getCollectionForUser(user);
2561
2592
 
2562
2593
  try {
2563
2594
  // --- Permission Check ---
@@ -2616,6 +2647,7 @@ export async function onInit(defaultEngine) {
2616
2647
  }
2617
2648
  });
2618
2649
 
2650
+ logger.info("Data module loaded");
2619
2651
  }
2620
2652
 
2621
2653
  export const createModel = async (data) => {
@@ -2740,7 +2772,7 @@ export async function checkServerCapacity(incomingDataSize = 0) {
2740
2772
  return {
2741
2773
  isSufficient: false,
2742
2774
  free,
2743
- total: size,
2775
+ total: size
2744
2776
  };
2745
2777
  }
2746
2778
  return { isSufficient: true, free, total: size };
@@ -2761,7 +2793,7 @@ export const insertData = async (modelName, data, files, user, triggerWorkflow =
2761
2793
  return { success: false, error: i18n.t('api.permission.addData'), statusCode: 403 };
2762
2794
  }
2763
2795
 
2764
- const collection = getCollectionForUser(user);
2796
+ const collection = await getCollectionForUser(user);
2765
2797
  let insertedIds = []; // Pour stocker les IDs retourn par pushDataUnsecure
2766
2798
 
2767
2799
  try {
@@ -2917,7 +2949,7 @@ async function initializeAndValidate(data, modelName, me) {
2917
2949
  if (datas.length === 0) return { datas: [], model: null, collection: null };
2918
2950
 
2919
2951
  const model = await getModel(modelName, me);
2920
- const collection = getCollectionForUser(me);
2952
+ const collection = await getCollectionForUser(me);
2921
2953
  validateModelStructure(model);
2922
2954
 
2923
2955
  return { datas, model, collection };
@@ -2937,7 +2969,7 @@ function normalizeInputData(data) {
2937
2969
  */
2938
2970
  async function checkLimits(datas, model, collection, me) {
2939
2971
  const incomingDataSize = calculateDataSize(datas);
2940
- const userStorageLimit = getUserStorageLimit(me);
2972
+ const userStorageLimit = await engine.userProvider.getUserStorageLimit(me);
2941
2973
 
2942
2974
  // Vérification des limites utilisateur
2943
2975
  const currentStorageUsage = await calculateTotalUserStorageUsage(me);
@@ -2989,7 +3021,7 @@ async function processDocuments(datas, model, collection, me) {
2989
3021
  }
2990
3022
  } catch (error) {
2991
3023
  // Modification clé ici : on ne catch plus les erreurs de validation
2992
- throw error;s
3024
+ throw error;
2993
3025
  }
2994
3026
  }
2995
3027
 
@@ -3351,7 +3383,7 @@ async function handleFilesIfNeeded(insertedIds, files, model, collection) {
3351
3383
  // Ex: association des fichiers uploadés aux documents insérés
3352
3384
  }
3353
3385
  const checkHash = async (me, model, hash, excludeId = null) => {
3354
- const collection = getCollectionForUser(me);
3386
+ const collection = await getCollectionForUser(me);
3355
3387
  const query = {
3356
3388
  _model: model.name,
3357
3389
  _hash: hash,
@@ -3406,7 +3438,7 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
3406
3438
  throw new Error(i18n.t("api.permission.editData"));
3407
3439
  }
3408
3440
 
3409
- const collection = getCollectionForUser(user);
3441
+ const collection = await getCollectionForUser(user);
3410
3442
  const model = await modelsCollection.findOne({name: modelName, _user: user.username});
3411
3443
  if (!model) {
3412
3444
  throw new Error(i18n.t("api.model.notFound", {model: modelName}));
@@ -3590,7 +3622,7 @@ async function handleScheduledJobs(modelName, existingDocs, collection, updateDa
3590
3622
  export const deleteData = async (modelName, ids = [], filter, user ={}, triggerWorkflow, waitForWorkflow = false) => {
3591
3623
 
3592
3624
  try {
3593
- const collection = getCollectionForUser(user);
3625
+ const collection = await getCollectionForUser(user);
3594
3626
 
3595
3627
  // --- Début de la logique de suppression ---
3596
3628
 
@@ -3698,9 +3730,9 @@ export const deleteData = async (modelName, ids = [], filter, user ={}, triggerW
3698
3730
  $and: [
3699
3731
  {_user: {$exists: true}},
3700
3732
  { $or: [
3701
- {_user: {$eq:user._user}},
3702
- {_user: {$eq:user.username}}
3703
- ]}
3733
+ {_user: {$eq:user._user}},
3734
+ {_user: {$eq:user.username}}
3735
+ ]}
3704
3736
  ]
3705
3737
  }
3706
3738
  ]
@@ -3811,7 +3843,7 @@ export const searchData = async ({user, query}) => {
3811
3843
  throw new Error(i18n.t('api.permission.searchData'));
3812
3844
  }
3813
3845
 
3814
- const collection = getCollectionForUser(user);
3846
+ const collection = await getCollectionForUser(user);
3815
3847
  const modelElement = await getModel(model, user);
3816
3848
 
3817
3849
  const allIds = (ids || '').split(",").map(m => m.trim()).filter(Boolean).map(m => {
@@ -3925,10 +3957,10 @@ export const searchData = async ({user, query}) => {
3925
3957
  ++i;
3926
3958
  const lookup = {
3927
3959
  $lookup: {
3928
- from: getUserCollectionName(user),
3960
+ from: await getUserCollectionName(user),
3929
3961
  as: "items" + i,
3930
3962
  let: {
3931
- dtid: {'$toString': '$_id'}, convertedId: '$' + fi.name,
3963
+ dtid: {'$toString': '$_id'}, convertedId: '$' + fi.name
3932
3964
  },
3933
3965
  pipeline: [
3934
3966
  {
@@ -4057,7 +4089,7 @@ export const searchData = async ({user, query}) => {
4057
4089
  $in: ['$guid', { $ifNull: ['$$localGuidsArray', []] }] // Handle null or missing array
4058
4090
  }
4059
4091
  }
4060
- },
4092
+ }
4061
4093
  // Optional: Project only necessary fields from the "files" collection if needed
4062
4094
  // {
4063
4095
  // $project: {
@@ -4141,7 +4173,7 @@ export const searchData = async ({user, query}) => {
4141
4173
  // et vous faites 'continue'. C'est bien.
4142
4174
 
4143
4175
  // Si tout va bien, on construit le lookup :
4144
- const targetCollectionName = getUserCollectionName(user);
4176
+ const targetCollectionName = await getUserCollectionName(user);
4145
4177
  const localFieldValueInPipeline = `$${lookupDef.localField}`;
4146
4178
 
4147
4179
  // Vérification basique du localField (déjà présente dans votre code précédent)
@@ -4168,7 +4200,7 @@ export const searchData = async ({user, query}) => {
4168
4200
  ]
4169
4201
  }
4170
4202
  }
4171
- },
4203
+ }
4172
4204
  // Optionnel: Projeter uniquement les champs nécessaires
4173
4205
  ],
4174
4206
  as: lookupDef.as
@@ -4778,14 +4810,14 @@ function isValidFieldReference(fieldName, modelElement) {
4778
4810
  function isValidAggregationOperator(operator) {
4779
4811
  const arithmeticOperators = [
4780
4812
  '$add', '$subtract', '$multiply', '$divide', '$mod', '$pow',
4781
- '$abs', '$ceil', '$floor', '$round', '$trunc', '$exp', '$log', '$log10',
4813
+ '$abs', '$ceil', '$floor', '$round', '$trunc', '$exp', '$log', '$log10'
4782
4814
  ];
4783
4815
  const comparisonOperators = [
4784
- '$eq', '$gt', '$gte', '$lt', '$lte', '$ne',
4816
+ '$eq', '$gt', '$gte', '$lt', '$lte', '$ne'
4785
4817
  // ... (others like $cmp, $strcasecmp, etc.)
4786
4818
  ];
4787
4819
  const stringOperators = [
4788
- '$concat', '$strLenCP', '$substrCP', '$toLower', '$toUpper',
4820
+ '$concat', '$strLenCP', '$substrCP', '$toLower', '$toUpper'
4789
4821
  // ... (others)
4790
4822
  ];
4791
4823
  const conditionalOperators = ['$cond', '$ifNull'];
@@ -4873,47 +4905,49 @@ const handleFields = async (model, data, user, isRecursiveCall = false) => {
4873
4905
 
4874
4906
 
4875
4907
  try {
4876
- // 1. Récupérer l'ID du document de langue de l'utilisateur pour la langue actuelle.
4877
- const userLangDoc = await getCollectionForUser(user).findOne({
4878
- _model: 'lang',
4879
- code: lang,
4880
- _user: user.username
4881
- });
4908
+ const coll = await getCollectionForUser(user);
4909
+ // 1. Récupérer l'ID du document de langue de l'utilisateur pour la langue actuelle.
4910
+ const userLangDoc = await coll.findOne({
4911
+ _model: 'lang',
4912
+ code: lang,
4913
+ _user: user.username
4914
+ });
4882
4915
 
4883
- if (userLangDoc) {
4884
- // 2. Récupérer les traductions de l'utilisateur pour cette langue.
4885
- const userTranslationsArray = await getCollectionForUser(user).find({
4886
- _model: 'translation',
4887
- _user: user.username,
4888
- lang: userLangDoc._id.toString()
4889
- }).toArray();
4890
-
4891
- if (userTranslationsArray.length > 0) {
4892
- // 3. Préparer le "bundle" de ressources pour i18n.
4893
- const newResourceBundle = userTranslationsArray.reduce((acc, tr) => {
4894
- if (tr.key && tr.value) {
4895
- acc[tr.key] = tr.value;
4896
- }
4897
- return acc;
4898
- }, {});
4899
-
4900
- // 4. Charger temporairement les traductions de l'utilisateur.
4901
- if (Object.keys(newResourceBundle).length > 0) {
4902
- // Sauvegarder les traductions originales si elles existent
4903
- if (i18n.store.data[lang] && i18n.store.data[lang].translation) {
4904
- originalTranslations = {...i18n.store.data[lang].translation};
4905
- }
4906
- // Ajoute/remplace les clés de traduction pour la langue et le namespace courants.
4907
- i18n.addResourceBundle(lang, 'translation', newResourceBundle, true, true);
4908
- userTranslationsLoaded = true;
4909
- logger.debug(`Chargement de ${userTranslationsArray.length} traductions personnalisées pour l'utilisateur '${user.username}' en '${lang}'.`);
4916
+ if (userLangDoc) {
4917
+ // 2. Récupérer les traductions de l'utilisateur pour cette langue.
4918
+ const userTranslationsArray = await coll.find({
4919
+ _model: 'translation',
4920
+ _user: user.username,
4921
+ lang: userLangDoc._id.toString()
4922
+ }).toArray();
4923
+
4924
+ if (userTranslationsArray.length > 0) {
4925
+ // 3. Préparer le "bundle" de ressources pour i18n.
4926
+ const newResourceBundle = userTranslationsArray.reduce((acc, tr) => {
4927
+ if (tr.key && tr.value) {
4928
+ acc[tr.key] = tr.value;
4929
+ }
4930
+ return acc;
4931
+ }, {});
4932
+
4933
+
4934
+ // 4. Charger temporairement les traductions de l'utilisateur.
4935
+ if (Object.keys(newResourceBundle).length > 0) {
4936
+ // Sauvegarder les traductions originales si elles existent
4937
+ if (i18n.store.data[lang] && i18n.store.data[lang].translation) {
4938
+ originalTranslations = {...i18n.store.data[lang].translation};
4910
4939
  }
4940
+ // Ajoute/remplace les clés de traduction pour la langue et le namespace courants.
4941
+ i18n.addResourceBundle(lang, 'translation', newResourceBundle, true, true);
4942
+ userTranslationsLoaded = true;
4943
+ logger.debug(`Chargement de ${userTranslationsArray.length} traductions personnalisées pour l'utilisateur '${user.username}' en '${lang}'.`);
4911
4944
  }
4912
4945
  }
4946
+ }
4913
4947
 
4914
- // 5. Traiter les données avec les traductions (personnalisées ou par défaut).
4915
- const processedData = await _processItems(dataArray);
4916
- return wasArray ? processedData : processedData[0];
4948
+ // 5. Traiter les données avec les traductions (personnalisées ou par défaut).
4949
+ const processedData = await _processItems(dataArray);
4950
+ return wasArray ? processedData : processedData[0];
4917
4951
  } finally {
4918
4952
  // 6. Nettoyage : décharger les traductions de l'utilisateur et restaurer les originales.
4919
4953
  // Ce bloc s'exécute toujours, même en cas d'erreur, garantissant l'isolation des requêtes.
@@ -5081,19 +5115,7 @@ export const dumpUserData = async (user) => {
5081
5115
 
5082
5116
  try {
5083
5117
  // Déterminer la fréquence de la sauvegarde
5084
- let backupFrequency;
5085
- switch (user.userPlan) {
5086
- case 'premium':
5087
- backupFrequency = 'daily';
5088
- break;
5089
- case 'standard':
5090
- backupFrequency = 'weekly';
5091
- break;
5092
- case 'free':
5093
- default:
5094
- backupFrequency = 'monthly';
5095
- break;
5096
- }
5118
+ const backupFrequency = await engine.userProvider.getBackupFrequency(user);
5097
5119
 
5098
5120
  logger.info(`Fréquence de sauvegarde : ${backupFrequency}.`);
5099
5121
 
@@ -5115,7 +5137,7 @@ export const dumpUserData = async (user) => {
5115
5137
  let col;
5116
5138
  for (const collection of collections) {
5117
5139
 
5118
- const colls = [getUserCollectionName(user), 'models'];
5140
+ const colls = [await getUserCollectionName(user), 'models'];
5119
5141
  if( colls.includes(collection.name) ){
5120
5142
 
5121
5143
  // Exécuter mongodump avec les filtres appropriés
@@ -5215,16 +5237,16 @@ async function manageBackupRotation(user, backupFrequency, s3Config = null) { //
5215
5237
  let maxFilesToKeep;
5216
5238
  // ... (ta logique existante pour maxFilesToKeep basée sur backupFrequency)
5217
5239
  switch (backupFrequency) {
5218
- case 'daily': // Premium
5219
- maxFilesToKeep = 7; // Garder 7 jours
5220
- break;
5221
- case 'weekly': // Standard
5222
- maxFilesToKeep = 4; // Garder 4 semaines
5223
- break;
5224
- case 'monthly': // Free
5225
- default:
5226
- maxFilesToKeep = 2; // Garder 2 mois
5227
- break;
5240
+ case 'daily': // Premium
5241
+ maxFilesToKeep = 7; // Garder 7 jours
5242
+ break;
5243
+ case 'weekly': // Standard
5244
+ maxFilesToKeep = 4; // Garder 4 semaines
5245
+ break;
5246
+ case 'monthly': // Free
5247
+ default:
5248
+ maxFilesToKeep = 2; // Garder 2 mois
5249
+ break;
5228
5250
  }
5229
5251
  logger.info(`Rotation pour ${userId}: fréquence ${backupFrequency}, garde ${maxFilesToKeep} sauvegardes.`);
5230
5252
 
@@ -5266,12 +5288,20 @@ const middlewareLogger = async (req, res, next) => {
5266
5288
  };
5267
5289
 
5268
5290
  res.on('finish', async () => {
5269
- await logApiRequest(req, res, req.me, startTime, ( !req.hideApiLogs ) ? JSON.parse(responseBodyChunk) : { message: "The request log has been encrypted because of a clear password in the request."}, res.locals.error); // req.user et res.locals.error dépendent de tes autres middlewares
5291
+ try {
5292
+ await logApiRequest(req, res, req.me, startTime, ( !req.hideApiLogs ) ? JSON.parse(responseBodyChunk) : { message: "The request log has been encrypted because of a clear password in the request."}, res.locals.error);
5293
+ } catch (e) {
5294
+
5295
+ }
5270
5296
  });
5271
5297
 
5272
5298
  res.on('error', async (err) => {
5273
5299
  // Logger aussi en cas d'erreur avant 'finish'
5274
- await logApiRequest(req, res, req.me, startTime, null, err);
5300
+ try{
5301
+ await logApiRequest(req, res, req.me, startTime, null, err);
5302
+ } catch (e) {
5303
+
5304
+ }
5275
5305
  });
5276
5306
 
5277
5307
  next();
@@ -5350,7 +5380,7 @@ export async function installPack(logger, packId, user, lang) {
5350
5380
  datas: { inserted: 0, updated: 0, skipped: 0, failed: 0 }
5351
5381
  };
5352
5382
  const errors = [];
5353
- const collection = getCollectionForUser(user);
5383
+ const collection = await getCollectionForUser(user);
5354
5384
  const tempIdToNewIdMap = {};
5355
5385
  const linkCache = new Map();
5356
5386