data-primals-engine 1.2.6-rc3 → 1.2.6
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/package-lock.json +247 -4354
- package/client/package.json +16 -15
- package/client/src/App.scss +1 -0
- package/client/src/DataEditor.jsx +1 -1
- package/client/src/Field.jsx +4 -16
- package/client/src/translations.js +17 -0
- package/package.json +1 -1
- package/src/filter.js +35 -5
- package/src/modules/data/data.core.js +1 -3
- package/src/modules/data/data.js +58 -31
- package/src/modules/mongodb.js +3 -1
- package/src/modules/workflow.js +66 -70
- package/test/data.integration.test.js +1 -1
- package/test/workflow.actions.integration.test.js +474 -0
package/client/package.json
CHANGED
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"@tiptap/react": "^2.11.5",
|
|
16
|
-
"react-router-dom": "^7.1.5",
|
|
17
|
-
"react-query": "^3.39.3",
|
|
18
|
-
"react-icons": "^5.0.1",
|
|
19
|
-
"react-tooltip": "^5.28.1",
|
|
20
|
-
"react-helmet": "^6.1.0",
|
|
21
|
-
"react-cookie": "^8.0.0",
|
|
22
|
-
"react-syntax-highlighter": "^15.6.1",
|
|
23
|
-
"react-international-phone": "^4.5.0",
|
|
24
16
|
"react-chartjs-2": "^5.3.0",
|
|
25
17
|
"react-code-blocks": "^0.1.6",
|
|
26
|
-
"react-
|
|
18
|
+
"react-cookie": "^8.0.0",
|
|
19
|
+
"react-ga": "^3.3.1",
|
|
20
|
+
"react-helmet": "^6.1.0",
|
|
21
|
+
"react-icons": "^5.0.1",
|
|
22
|
+
"react-international-phone": "^4.5.0",
|
|
23
|
+
"react-query": "^3.39.3",
|
|
24
|
+
"react-router-dom": "^7.1.5",
|
|
25
|
+
"react-syntax-highlighter": "^15.6.1",
|
|
26
|
+
"react-tooltip": "^5.28.1"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@babel/runtime": "^7.24.7",
|
|
@@ -43,15 +43,16 @@
|
|
|
43
43
|
"@uiw/react-codemirror": "^4.23.10",
|
|
44
44
|
"chart.js": "^4.4.9",
|
|
45
45
|
"chartjs-adapter-date-fns": "^3.0.0",
|
|
46
|
-
"react-dom": "^18.3.1",
|
|
47
|
-
"react-router": "^7.2.0",
|
|
48
|
-
"yet-another-react-lightbox": "^3.22.0",
|
|
49
|
-
"react": ">=18.0.0",
|
|
50
46
|
"classnames": "^2.5.1",
|
|
51
47
|
"date-fns": "^4.1.0",
|
|
52
48
|
"express-rate-limit": "^7.5.1",
|
|
53
49
|
"lowlight": "^3.3.0",
|
|
54
|
-
"
|
|
50
|
+
"react": ">=18.0.0",
|
|
51
|
+
"react-dom": "^18.3.1",
|
|
52
|
+
"react-router": "^7.2.0",
|
|
53
|
+
"react-switch": "^7.1.0",
|
|
54
|
+
"uniqid": "^5.4.0",
|
|
55
|
+
"yet-another-react-lightbox": "^3.22.0"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@eslint/js": "^9.17.0",
|
|
@@ -67,4 +68,4 @@
|
|
|
67
68
|
"sass-embedded": "^1.83.4",
|
|
68
69
|
"vite": "^6.1.1"
|
|
69
70
|
}
|
|
70
|
-
}
|
|
71
|
+
}
|
package/client/src/App.scss
CHANGED
|
@@ -239,7 +239,7 @@ export const DataEditor = forwardRef(function MyDataEditor({
|
|
|
239
239
|
onChange={(e) => handleChange({name: field.name, value: e})}/>
|
|
240
240
|
case 'boolean':
|
|
241
241
|
return <CheckboxField
|
|
242
|
-
help={
|
|
242
|
+
help={t('field_' + model.name + '_' + field.name + '_hint', field.hint || '')}
|
|
243
243
|
key={field.name} {...inputProps} checked={value}
|
|
244
244
|
onChange={(e) => handleChange({name: field.name, value: e.target.checked})}/>
|
|
245
245
|
case 'string':
|
package/client/src/Field.jsx
CHANGED
|
@@ -34,7 +34,7 @@ import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
|
|
34
34
|
import { PhoneInput } from 'react-international-phone';
|
|
35
35
|
import 'react-international-phone/style.css';
|
|
36
36
|
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
37
|
-
|
|
37
|
+
import Switch from "react-switch";
|
|
38
38
|
export const Form = ({
|
|
39
39
|
name,
|
|
40
40
|
onValidate,
|
|
@@ -492,20 +492,6 @@ const CheckboxField = forwardRef(
|
|
|
492
492
|
return (
|
|
493
493
|
<>
|
|
494
494
|
<div className={cn({field: true, "field-checkbox": true,"field-bg": true})}>
|
|
495
|
-
<div className="inline"><input
|
|
496
|
-
aria-required={required}
|
|
497
|
-
aria-readonly={readOnly}
|
|
498
|
-
readOnly={readOnly}
|
|
499
|
-
type="checkbox"
|
|
500
|
-
checked={value}
|
|
501
|
-
placeholder={placeholder}
|
|
502
|
-
id={id}
|
|
503
|
-
name={name}
|
|
504
|
-
onChange={handleChange}
|
|
505
|
-
minLength={minlength}
|
|
506
|
-
maxLength={maxlength}
|
|
507
|
-
{...rest}
|
|
508
|
-
/>
|
|
509
495
|
{label && (
|
|
510
496
|
<label
|
|
511
497
|
contentEditable={editable}
|
|
@@ -522,7 +508,9 @@ const CheckboxField = forwardRef(
|
|
|
522
508
|
)}
|
|
523
509
|
</label>
|
|
524
510
|
)}
|
|
525
|
-
|
|
511
|
+
<Switch
|
|
512
|
+
onChange={handleChange}
|
|
513
|
+
checked={value} />
|
|
526
514
|
{help && <div className="flex help">{help}</div>}
|
|
527
515
|
</div>
|
|
528
516
|
{errors.length > 0 && (
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
export const websiteTranslations = {
|
|
3
3
|
fr: {
|
|
4
4
|
translation: {
|
|
5
|
+
|
|
6
|
+
"api.data.relationFilterFailed": "La valeur {{value}} pour le champ {{field}} ne respecte pas le filtre de relation défini.",
|
|
7
|
+
|
|
5
8
|
"dataimporter.excelPreview":"Aperçu des données Excel",
|
|
6
9
|
"dataimporter.previewNote": "Note: Ceci est un aperçu des premières lignes. Les cellules vides sont affichées comme \"(vide)\".",
|
|
7
10
|
"dataimporter.nullValue": "(vide)",
|
|
@@ -567,6 +570,8 @@ export const websiteTranslations = {
|
|
|
567
570
|
"suggested_prompt.recipes.title": "Cooking Recipes",
|
|
568
571
|
"suggested_prompt.recipes.prompt": "A collection of cooking recipes, with ingredients, instructions, and preparation time.",
|
|
569
572
|
|
|
573
|
+
"api.data.relationFilterFailed": "The value {{value}} for field {{field}} does not match the defined relationship filter.",
|
|
574
|
+
|
|
570
575
|
"api.data.duplicateValue": "The value '{{value}}' already exists for the unique field '{{field}}'.",
|
|
571
576
|
"api.model.invalidStructure": "The model is invalid. Some fields are incorrect.",
|
|
572
577
|
"api.validate.fieldArray": "Field '{{0}}' must be an array.",
|
|
@@ -1975,6 +1980,7 @@ export const websiteTranslations = {
|
|
|
1975
1980
|
"suggested_prompt.recipes.prompt": "Una colección de recetas de cocina, con ingredientes, instrucciones y tiempo de preparación.",
|
|
1976
1981
|
|
|
1977
1982
|
"api.data.duplicateValue": "El valor '{{value}}' ya existe para el campo único '{{field}}'.",
|
|
1983
|
+
"api.data.relationFilterFailed": "El valor {{value}} del campo {{field}} no coincide con el filtro de relación definido.",
|
|
1978
1984
|
"api.model.invalidStructure": "El modelo no es válido. Algunos campos son incorrectos.",
|
|
1979
1985
|
"api.validate.fieldArray": "El campo '{{0}}' debe ser un array.",
|
|
1980
1986
|
"api.validate.invalidMimeType": "Tipo de archivo '{{type}}' no válido. Los tipos permitidos son: {{authorized}}.",
|
|
@@ -3388,6 +3394,7 @@ export const websiteTranslations = {
|
|
|
3388
3394
|
"suggested_prompt.recipes.prompt": "Uma coleção de receitas culinárias, com ingredientes, instruções e tempo de preparação.",
|
|
3389
3395
|
|
|
3390
3396
|
"api.data.duplicateValue": "O valor '{{value}}' já existe para o campo exclusivo '{{field}}'.",
|
|
3397
|
+
"api.data.relationFilterFailed": "O valor {{value}} para o campo {{field}} não corresponde ao filtro de relacionamento definido.",
|
|
3391
3398
|
"api.model.invalidStructure": "O modelo é inválido. Alguns campos estão incorretos.",
|
|
3392
3399
|
"api.validate.fieldArray": "O campo '{{0}}' deve ser um array.",
|
|
3393
3400
|
"api.validate.invalidMimeType": "Tipo de ficheiro inválido '{{type}}'. Os tipos permitidos são: {{authorized}}.",
|
|
@@ -4782,6 +4789,7 @@ export const websiteTranslations = {
|
|
|
4782
4789
|
"suggested_prompt.recipes.prompt": "Eine Sammlung von Kochrezepten mit Zutaten, Anweisungen und Vorbereitungszeit.",
|
|
4783
4790
|
|
|
4784
4791
|
"api.data.duplicateValue": "Der Wert '{{value}}' ist für das eindeutige Feld '{{field}}' bereits vorhanden.",
|
|
4792
|
+
"api.data.relationFilterFailed": "Der Wert {{value}} für das Feld {{field}} entspricht nicht dem definierten Beziehungsfilter.",
|
|
4785
4793
|
"api.model.invalidStructure": "Das Modell ist ungültig. Einige Felder sind fehlerhaft.",
|
|
4786
4794
|
"api.validate.fieldArray": "Feld '{{0}}' muss ein Array sein.",
|
|
4787
4795
|
"api.validate.invalidMimeType": "Ungültiger Dateityp '{{type}}'. Zulässige Typen sind: {{authorized}}.",
|
|
@@ -6185,6 +6193,7 @@ export const websiteTranslations = {
|
|
|
6185
6193
|
"suggested_prompt.recipes.prompt": "Una raccolta di ricette di cucina, con ingredienti, istruzioni e tempi di preparazione.",
|
|
6186
6194
|
|
|
6187
6195
|
"api.data.duplicateValue": "Il valore '{{value}}' esiste già per il campo univoco '{{field}}'.",
|
|
6196
|
+
"api.data.relationFilterFailed": "Il valore {{value}} per il campo {{field}} non corrisponde al filtro di relazione definito.",
|
|
6188
6197
|
"api.model.invalidStructure": "Il modello non è valido. Alcuni campi sono errati.",
|
|
6189
6198
|
"api.validate.fieldArray": "Il campo '{{0}}' deve essere un array.",
|
|
6190
6199
|
"api.validate.invalidMimeType": "Tipo di file '{{type}}' non valido. I tipi consentiti sono: {{authorized}}.",
|
|
@@ -7589,6 +7598,7 @@ export const websiteTranslations = {
|
|
|
7589
7598
|
"suggested_prompt.recipes.prompt": "Sbírka receptů na vaření s ingrediencemi, návodem a přípravou čas.",
|
|
7590
7599
|
|
|
7591
7600
|
"api.data.duplicateValue": "Hodnota '{{value}}' pro jedinečné pole '{{field}}' již existuje.",
|
|
7601
|
+
"api.data.relationFilterFailed": "Hodnota {{value}} pro pole {{field}} neodpovídá definovanému filtru vztahů.",
|
|
7592
7602
|
"api.model.invalidStructure": "Model je neplatný. Některá pole jsou nesprávná.",
|
|
7593
7603
|
"api.validate.fieldArray": "Pole '{{0}}' musí být pole.",
|
|
7594
7604
|
"api.validate.invalidMimeType": "Neplatný typ souboru '{{type}}'. Povolené typy jsou: {{authorized}}.",
|
|
@@ -8988,6 +8998,7 @@ export const websiteTranslations = {
|
|
|
8988
8998
|
"suggested_prompt.recipes.prompt": "Коллекция кулинарных рецептов с ингредиентами, инструкциями и описанием приготовления. время.",
|
|
8989
8999
|
|
|
8990
9000
|
"api.data.duplicateValue": "Значение '{{value}}' уже существует для уникального поля '{{field}}'.",
|
|
9001
|
+
"api.data.relationFilterFailed": "Значение {{value}} для поля {{field}} не соответствует заданному фильтру связи.",
|
|
8991
9002
|
"api.model.invalidStructure": "Модель недействительна. Некоторые поля неверны.",
|
|
8992
9003
|
"api.validate.fieldArray": "Поле '{{0}}' должно быть массивом.",
|
|
8993
9004
|
"api.validate.invalidMimeType": "Недопустимый тип файла '{{type}}'. Допустимые типы: {{authorized}}.",
|
|
@@ -10396,6 +10407,7 @@ export const websiteTranslations = {
|
|
|
10396
10407
|
"suggested_prompt.recipes.prompt": "مجموعة من وصفات الطبخ، مع المكونات، والتعليمات، ومدة التحضير.",
|
|
10397
10408
|
|
|
10398
10409
|
"api.data.duplicateValue": "القيمة '{{value}}' موجودة بالفعل للحقل الفريد '{{field}}'.",
|
|
10410
|
+
"api.data.relationFilterFailed": "القيمة {{value}} للحقل {{field}} لا تتطابق مع مرشح العلاقة المحدد.",
|
|
10399
10411
|
"api.model.invalidStructure": "النموذج غير صالح. بعض الحقول غير صحيحة.",
|
|
10400
10412
|
"api.validate.fieldArray": "يجب أن يكون الحقل '{{0}}' مصفوفة.",
|
|
10401
10413
|
"api.validate.invalidMimeType": "نوع الملف '{{type}}' غير صالح. الأنواع المسموح بها هي: {{authorized}}.",
|
|
@@ -11823,6 +11835,7 @@ export const websiteTranslations = {
|
|
|
11823
11835
|
"suggested_prompt.recipes.prompt": "En samling matlagningsrecept, med ingredienser, instruktioner, och förberedelsetid.",
|
|
11824
11836
|
|
|
11825
11837
|
"api.data.duplicateValue": "Värdet '{{value}}' finns redan för det unika fältet '{{field}}'.",
|
|
11838
|
+
"api.data.relationFilterFailed": "Värdet {{value}} för fältet {{field}} matchar inte det definierade relationsfiltret.",
|
|
11826
11839
|
"api.model.invalidStructure": "Modellen är ogiltig. Vissa fält är felaktiga.",
|
|
11827
11840
|
"api.validate.fieldArray": "Fältet '{{0}}' måste vara en array.",
|
|
11828
11841
|
"api.validate.invalidMimeType": "Ogiltig filtyp '{{type}}'. Tillåtna typer är: {{authorized}}.",
|
|
@@ -13222,6 +13235,8 @@ export const websiteTranslations = {
|
|
|
13222
13235
|
"suggested_prompt.recipes.prompt": "Μια συλλογή συνταγών μαγειρικής, με υλικά, οδηγίες και χρόνο προετοιμασίας.",
|
|
13223
13236
|
|
|
13224
13237
|
"api.data.duplicateValue": "Η τιμή '{{value}}' υπάρχει ήδη για το μοναδικό πεδίο '{{field}}'.",
|
|
13238
|
+
"api.data.relationFilterFailed": "Η τιμή {{value}} για το πεδίο {{field}} δεν ταιριάζει με το καθορισμένο φίλτρο σχέσης.",
|
|
13239
|
+
|
|
13225
13240
|
"api.model.invalidStructure": "Το μοντέλο δεν είναι έγκυρο. Ορισμένα πεδία είναι λανθασμένα.",
|
|
13226
13241
|
"api.validate.fieldArray": "Το πεδίο '{{0}}' πρέπει να είναι ένας πίνακας.",
|
|
13227
13242
|
"api.validate.invalidMimeType": "Μη έγκυρος τύπος αρχείου '{{type}}'. Οι επιτρεπόμενοι τύποι είναι: {{authorized}}.",
|
|
@@ -14629,6 +14644,8 @@ export const websiteTranslations = {
|
|
|
14629
14644
|
"suggested_prompt.recipes.prompt": "مجموعهای از دستور پخت غذا، با مواد لازم، دستورالعمل و زمان آمادهسازی.",
|
|
14630
14645
|
|
|
14631
14646
|
"api.data.duplicateValue": "مقدار '{{value}}' از قبل برای فیلد منحصر به فرد '{{field}}' وجود دارد.",
|
|
14647
|
+
"api.data.relationFilterFailed": "مقدار {{value}} برای فیلد {{field}} با فیلتر رابطه تعریف شده مطابقت ندارد.",
|
|
14648
|
+
|
|
14632
14649
|
"api.model.invalidStructure": "مدل نامعتبر است. برخی از فیلدها نادرست هستند.",
|
|
14633
14650
|
"api.validate.fieldArray": "فیلد '{{0}}' باید یک آرایه باشد.",
|
|
14634
14651
|
"api.validate.invalidMimeType": "نوع فایل '{{type}}' نامعتبر است. انواع مجاز عبارتند از: {{authorized}}.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-primals-engine",
|
|
3
|
-
"version": "1.2.6
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
|
|
5
5
|
"main": "src/engine.js",
|
|
6
6
|
"type": "module",
|
package/src/filter.js
CHANGED
|
@@ -175,15 +175,45 @@ export const isConditionMet = (model, cond, formData, allModels, user) => {
|
|
|
175
175
|
|
|
176
176
|
if (!condition) return true;
|
|
177
177
|
|
|
178
|
+
// Cas 0: Condition est une valeur primitive (string, number, boolean)
|
|
179
|
+
// On la considère comme toujours vraie (comportement de searchData)
|
|
180
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
178
184
|
// Cas 1: Condition simple {field: value} → transformée en {field: {$eq: value}}
|
|
179
185
|
if (typeof condition === 'object' && !Array.isArray(condition)) {
|
|
180
186
|
const keys = Object.keys(condition);
|
|
187
|
+
|
|
188
|
+
// Cas spécial pour les conditions de type {field: value} (pas d'opérateur $)
|
|
181
189
|
if (keys.length === 1 && !keys[0].startsWith('$') &&
|
|
182
190
|
typeof condition[keys[0]] !== 'object') {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
const fieldName = keys[0];
|
|
192
|
+
const value = condition[fieldName];
|
|
193
|
+
|
|
194
|
+
// Si la valeur est null/undefined, on vérifie simplement l'existence
|
|
195
|
+
if (value === null || value === undefined) {
|
|
196
|
+
return formData[fieldName] === value;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Sinon on fait une comparaison d'égalité simple
|
|
200
|
+
return formData[fieldName] == value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Cas spécial pour les tableaux - vérifie si la valeur est incluse
|
|
204
|
+
if (keys.length === 1 && !keys[0].startsWith('$') &&
|
|
205
|
+
Array.isArray(condition[keys[0]])) {
|
|
206
|
+
const fieldName = keys[0];
|
|
207
|
+
const values = condition[fieldName];
|
|
208
|
+
const fieldValue = formData[fieldName];
|
|
209
|
+
|
|
210
|
+
// Si le champ est aussi un tableau, vérifie l'intersection
|
|
211
|
+
if (Array.isArray(fieldValue)) {
|
|
212
|
+
return fieldValue.some(v => values.includes(v));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Sinon vérifie si la valeur est dans le tableau
|
|
216
|
+
return values.includes(fieldValue);
|
|
187
217
|
}
|
|
188
218
|
}
|
|
189
219
|
|
|
@@ -218,4 +248,4 @@ export const isConditionMet = (model, cond, formData, allModels, user) => {
|
|
|
218
248
|
|
|
219
249
|
// Cas 4: Tous les autres cas (conditions normales avec opérateurs)
|
|
220
250
|
return evaluateSingleCondition(model, condition, formData, allModels, user);
|
|
221
|
-
};
|
|
251
|
+
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import NodeCache from "node-cache";
|
|
2
|
-
import i18n from "../../i18n.js";
|
|
3
|
-
import {allowedFields, maxStringLength} from "../../constants.js";
|
|
4
2
|
import path from "node:path";
|
|
5
|
-
|
|
3
|
+
import {Worker} from 'worker_threads';
|
|
6
4
|
export const modelsCache = new NodeCache( { stdTTL: 100, checkperiod: 120 } );
|
|
7
5
|
|
|
8
6
|
export const mongoDBWhitelist = [
|
package/src/modules/data/data.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Logger} from "../../gameObject.js";
|
|
2
2
|
import {BSON, ObjectId} from "mongodb";
|
|
3
|
-
import {promisify} from 'node:util';
|
|
3
|
+
import util, {promisify} from 'node:util';
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import {exec, execFile} from 'node:child_process';
|
|
6
6
|
import sanitizeHtml from 'sanitize-html';
|
|
@@ -67,7 +67,6 @@ import {
|
|
|
67
67
|
import NodeCache from "node-cache";
|
|
68
68
|
import AWS from 'aws-sdk';
|
|
69
69
|
import checkDiskSpace from "check-disk-space";
|
|
70
|
-
import {Worker} from 'worker_threads';
|
|
71
70
|
import {addFile, removeFile} from "../file.js";
|
|
72
71
|
import {downloadFromS3, getUserS3Config, listS3Backups, uploadToS3} from "../bucket.js";
|
|
73
72
|
import {
|
|
@@ -2042,22 +2041,41 @@ async function insertAndResolveRelations(doc, model, collection, me, idMap) {
|
|
|
2042
2041
|
|
|
2043
2042
|
for (const field of model.fields) {
|
|
2044
2043
|
if (field.type === 'relation' && field.relationFilter && docToProcess[field.name]) {
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2044
|
+
|
|
2045
|
+
const relatedIds = Array.isArray(docToProcess[field.name])
|
|
2046
|
+
? docToProcess[field.name]
|
|
2047
|
+
: [docToProcess[field.name]];
|
|
2048
|
+
|
|
2049
|
+
// Préparer un filtre global : match si _id dans relatedIds ET respecte relationFilter
|
|
2050
|
+
const validationQuery = {
|
|
2051
|
+
$and: [
|
|
2052
|
+
{ $in: ['$_id', relatedIds.map(id => ({ $toObjectId: id }))] },
|
|
2053
|
+
field.relationFilter
|
|
2054
|
+
]
|
|
2055
|
+
};
|
|
2056
|
+
|
|
2057
|
+
const relatedDocs = await searchData({
|
|
2058
|
+
filter: validationQuery,
|
|
2059
|
+
model: field.relation,
|
|
2060
|
+
limit: relatedIds.length
|
|
2061
|
+
}, me);
|
|
2062
|
+
|
|
2063
|
+
if ((relatedDocs?.count || 0) !== relatedIds.length) {
|
|
2064
|
+
const invalidIds = relatedIds.filter(id =>
|
|
2065
|
+
!relatedDocs.data.some(doc => doc._id.toString() === id.toString())
|
|
2066
|
+
);
|
|
2067
|
+
throw new Error(
|
|
2068
|
+
i18n.t(
|
|
2069
|
+
'api.data.relationFilterFailed',
|
|
2070
|
+
'Les valeurs {{values}} pour le champ {{field}} ne respectent pas le filtre de relation défini.',
|
|
2071
|
+
{ field: field.name, values: invalidIds.join(', ') }
|
|
2072
|
+
)
|
|
2073
|
+
);
|
|
2057
2074
|
}
|
|
2058
2075
|
}
|
|
2059
2076
|
}
|
|
2060
2077
|
|
|
2078
|
+
|
|
2061
2079
|
// Insertion en conservant éventuellement l'ID original
|
|
2062
2080
|
const result = docToProcess._id
|
|
2063
2081
|
? await collection.insertOne(docToProcess)
|
|
@@ -2297,28 +2315,37 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
|
|
|
2297
2315
|
}
|
|
2298
2316
|
|
|
2299
2317
|
for (const field of model.fields) {
|
|
2300
|
-
|
|
2301
|
-
|
|
2318
|
+
if (field.type === 'relation' && field.relationFilter && updateData[field.name]) {
|
|
2319
|
+
|
|
2302
2320
|
const relatedIds = Array.isArray(updateData[field.name])
|
|
2303
2321
|
? updateData[field.name]
|
|
2304
|
-
:
|
|
2305
|
-
|
|
2306
|
-
for (const id of relatedIds) {
|
|
2307
|
-
if (!id || !isObjectId(id)) continue; // Ignorer les valeurs null/invalides
|
|
2322
|
+
: [updateData[field.name]];
|
|
2308
2323
|
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2324
|
+
// Préparer un filtre global : match si _id dans relatedIds ET respecte relationFilter
|
|
2325
|
+
const validationQuery = {
|
|
2326
|
+
$and: [
|
|
2327
|
+
{ $in: ['$_id', relatedIds.map(id => ({ $toObjectId: id }))] },
|
|
2328
|
+
field.relationFilter
|
|
2329
|
+
]
|
|
2330
|
+
};
|
|
2315
2331
|
|
|
2316
|
-
|
|
2332
|
+
const relatedDocs = await searchData({
|
|
2333
|
+
filter: validationQuery,
|
|
2334
|
+
model: field.relation,
|
|
2335
|
+
limit: relatedIds.length
|
|
2336
|
+
}, user);
|
|
2317
2337
|
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2338
|
+
if ((relatedDocs?.count || 0) !== relatedIds.length) {
|
|
2339
|
+
const invalidIds = relatedIds.filter(id =>
|
|
2340
|
+
!relatedDocs.data.some(doc => doc._id.toString() === id.toString())
|
|
2341
|
+
);
|
|
2342
|
+
throw new Error(
|
|
2343
|
+
i18n.t(
|
|
2344
|
+
'api.data.relationFilterFailed',
|
|
2345
|
+
'Les valeurs {{values}} pour le champ {{field}} ne respectent pas le filtre de relation défini.',
|
|
2346
|
+
{ field: field.name, values: invalidIds.join(', ') }
|
|
2347
|
+
)
|
|
2348
|
+
);
|
|
2322
2349
|
}
|
|
2323
2350
|
}
|
|
2324
2351
|
}
|
package/src/modules/mongodb.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
|
|
2
|
-
import process from "process";
|
|
3
2
|
import {Logger} from "../gameObject.js";
|
|
4
3
|
import {MongoDatabase} from "../engine.js";
|
|
4
|
+
import {ObjectId} from "mongodb";
|
|
5
5
|
|
|
6
6
|
export let modelsCollection, datasCollection, filesCollection, packsCollection;
|
|
7
7
|
|
|
8
|
+
export {ObjectId};
|
|
9
|
+
|
|
8
10
|
let engine, logger;
|
|
9
11
|
export async function onInit(defaultEngine) {
|
|
10
12
|
engine = defaultEngine;
|