data-primals-engine 1.0.10 → 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.
- package/client/README.md +8 -0
- package/client/index.html +31 -0
- package/client/index.js +25 -0
- package/client/package-lock.json +11664 -0
- package/client/package.json +70 -0
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
- package/client/public/doc/API.postman_collection.json +214 -0
- package/client/public/github.svg +1 -0
- package/client/public/profilCompany.jpg +0 -0
- package/client/public/profilDeveloper.jpg +0 -0
- package/client/public/profilEngineer.jpg +0 -0
- package/client/public/profilPersonal.jpg +0 -0
- package/client/public/profils .psd +0 -0
- package/client/public/react.svg +9 -0
- package/client/src/APIInfo.jsx +328 -0
- package/client/src/AddWidgetTypeModal.jsx +44 -0
- package/client/src/AddWidgetTypeModal.scss +87 -0
- package/client/src/App.css +42 -0
- package/client/src/App.jsx +586 -0
- package/client/src/App.scss +1466 -0
- package/client/src/AssistantChat.jsx +280 -0
- package/client/src/AssistantChat.scss +229 -0
- package/client/src/Button.jsx +12 -0
- package/client/src/Button.scss +197 -0
- package/client/src/CalculationBuilder.jsx +338 -0
- package/client/src/CalculationStepRow.jsx +199 -0
- package/client/src/CalendarConfigModal.jsx +49 -0
- package/client/src/ChartConfigModal.jsx +402 -0
- package/client/src/ConditionBuilder.jsx +622 -0
- package/client/src/ConditionBuilder.scss +672 -0
- package/client/src/ConditionBuilder2.jsx +757 -0
- package/client/src/ContentView.jsx +274 -0
- package/client/src/CronBuilder.jsx +126 -0
- package/client/src/CronBuilder.scss +128 -0
- package/client/src/CronPartBuilder.jsx +150 -0
- package/client/src/Dashboard.jsx +210 -0
- package/client/src/Dashboard.scss +445 -0
- package/client/src/DashboardChart.jsx +362 -0
- package/client/src/DashboardFlexViewItem.jsx +129 -0
- package/client/src/DashboardView.jsx +554 -0
- package/client/src/DataChart.jsx +349 -0
- package/client/src/DataEditor.jsx +556 -0
- package/client/src/DataLayout.jsx +709 -0
- package/client/src/DataTable.jsx +996 -0
- package/client/src/DataTable.scss +73 -0
- package/client/src/Dialog.jsx +90 -0
- package/client/src/Dialog.scss +117 -0
- package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
- package/client/src/DocumentationPageLayout.jsx +93 -0
- package/client/src/DocumentationPageLayout.scss +262 -0
- package/client/src/Draggable.jsx +222 -0
- package/client/src/Draggable.scss +49 -0
- package/client/src/ExportDialog.jsx +172 -0
- package/client/src/Field.jsx +1558 -0
- package/client/src/FlexBuilder.jsx +269 -0
- package/client/src/FlexBuilder.scss +226 -0
- package/client/src/FlexBuilderControls.jsx +170 -0
- package/client/src/FlexBuilderModal.jsx +75 -0
- package/client/src/FlexBuilderModal.scss +109 -0
- package/client/src/FlexBuilderPreview.jsx +48 -0
- package/client/src/FlexBuilderUtils.js +84 -0
- package/client/src/FlexDataRenderer.jsx +170 -0
- package/client/src/FlexDataRenderer.scss +80 -0
- package/client/src/FlexNode.jsx +290 -0
- package/client/src/FlexTreeUtils.js +129 -0
- package/client/src/HiddenableCell.jsx +46 -0
- package/client/src/HiddenableCell.scss +36 -0
- package/client/src/KPIDialog.jsx +39 -0
- package/client/src/KPIWidget.jsx +139 -0
- package/client/src/KanbanCard.jsx +92 -0
- package/client/src/KanbanColumn.jsx +29 -0
- package/client/src/KanbanConfigModal.jsx +115 -0
- package/client/src/KanbanView.jsx +113 -0
- package/client/src/KanbanView.scss +107 -0
- package/client/src/MessageRotator.jsx +77 -0
- package/client/src/MessageRotator.scss +14 -0
- package/client/src/ModelCreator.jsx +640 -0
- package/client/src/ModelCreator.scss +154 -0
- package/client/src/ModelCreatorField.jsx +810 -0
- package/client/src/ModelImporter.jsx +120 -0
- package/client/src/ModelList.jsx +205 -0
- package/client/src/Notification.jsx +137 -0
- package/client/src/Notification.scss +126 -0
- package/client/src/NotificationProvider.jsx +73 -0
- package/client/src/PackGallery.jsx +211 -0
- package/client/src/PackGallery.scss +156 -0
- package/client/src/Pagination.jsx +141 -0
- package/client/src/RTE.jsx +415 -0
- package/client/src/RTETrans.jsx +103 -0
- package/client/src/RelationField.jsx +247 -0
- package/client/src/RelationValue.jsx +230 -0
- package/client/src/RestoreConfirmationModal.jsx +183 -0
- package/client/src/RestoreDialog.jsx +134 -0
- package/client/src/RestoreDialog.scss +67 -0
- package/client/src/RichTextEditorModal.jsx +34 -0
- package/client/src/RichTextEditorModal.scss +54 -0
- package/client/src/TourSpotlight.jsx +234 -0
- package/client/src/TourSpotlight.scss +100 -0
- package/client/src/TutorialsMenu.jsx +83 -0
- package/client/src/TutorialsMenu.scss +217 -0
- package/client/src/TutorialsRewardModal.jsx +65 -0
- package/client/src/Utils.jsx +78 -0
- package/client/src/ViewSwitcher.jsx +54 -0
- package/client/src/ViewSwitcher.scss +74 -0
- package/client/src/Webpage.jsx +69 -0
- package/client/src/_variables.scss +21 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/constants.js +68 -0
- package/client/src/contexts/AuthContext.jsx +28 -0
- package/client/src/contexts/ModelContext.jsx +328 -0
- package/client/src/contexts/UIContext.jsx +52 -0
- package/client/src/core/data.js +35 -0
- package/client/src/entry-client.jsx +15 -0
- package/client/src/entry-server.jsx +13 -0
- package/client/src/filter.js +222 -0
- package/client/src/hooks/data.js +101 -0
- package/client/src/hooks/useDebounce.js +20 -0
- package/client/src/hooks/useDragAndDrop.js +59 -0
- package/client/src/hooks/useLocalStorage.js +100 -0
- package/client/src/hooks/useMount.js +42 -0
- package/client/src/hooks/useTutorials.jsx +227 -0
- package/client/src/hooks/useWindowSize.js +24 -0
- package/client/src/i18n.js +47 -0
- package/client/src/index.css +90 -0
- package/client/src/index.js +10 -0
- package/client/src/main.jsx +11 -0
- package/client/src/translations.js +15940 -0
- package/client/src/tutorials.js +112 -0
- package/client/vite.config.js +30 -0
- package/eslint.config.js +50 -0
- package/package.json +48 -27
- package/server.js +9 -0
- package/src/constants.js +12 -23
- package/src/core.js +9 -8
- package/src/data.js +81 -80
- package/src/defaultModels.js +114 -115
- package/src/email.js +8 -9
- package/src/engine.js +5 -5
- package/src/events.js +1 -2
- package/src/i18n.js +5390 -211
- package/src/middlewares/middleware-mongodb.js +94 -93
- package/src/migrate.js +24 -24
- package/src/modules/assistant.js +55 -55
- package/src/modules/bucket.js +10 -9
- package/src/modules/data.js +387 -357
- package/src/modules/file.js +3 -6
- package/src/modules/mongodb.js +6 -7
- package/src/modules/user.js +24 -47
- package/src/modules/workflow.js +68 -67
- package/src/openai.jobs.js +2 -2
- package/src/packs.js +30 -31
- package/src/providers.js +101 -3
- package/src/setenv.js +1 -1
- package/src/user.js +0 -17
- package/src/workers/crypto-worker.js +21 -20
- package/src/workers/import-export-worker.js +18 -18
- package/test/data.backup.integration.test.js +7 -12
- package/test/data.integration.test.js +27 -25
- package/test/globalSetup.js +2 -2
- package/test/import_export.integration.test.js +6 -6
- package/test/model.integration.test.js +21 -22
- package/test/workflow.integration.test.js +2 -5
- package/test/workflow.robustness.test.js +6 -13
- package/vitest.config.js +2 -2
package/src/modules/data.js
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
maxStringLength,
|
|
39
39
|
maxTotalDataPerUser,
|
|
40
40
|
megabytes,
|
|
41
|
-
optionsSanitizer,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
688
|
-
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
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
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
887
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
-
//
|
|
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
|
|
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)
|
|
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)
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
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
|
-
|
|
1741
|
+
engine.put('/api/data/:id', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
|
|
1734
1742
|
try {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
} catch (
|
|
1744
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
3702
|
-
|
|
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
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
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
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
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
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|