data-primals-engine 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,245 @@
1
+ import {Event} from "../../events.js";
2
+ import i18n from "../../i18n.js";
3
+ import {allowedFields, maxFileSize, maxModelNameLength, maxStringLength} from "../../constants.js";
4
+ import {getDefaultForType} from "../../data.js";
5
+
6
+ import {dataTypes} from "./data.operations.js";
7
+ import {Logger} from "../../gameObject.js";
8
+
9
+
10
+ let engine, logger;
11
+ export function onInit(defaultEngine) {
12
+ engine = defaultEngine;
13
+ logger = engine.getComponent(Logger);
14
+ }
15
+
16
+ export async function validateModelStructure(modelStructure) {
17
+ return await Event.Trigger("OnValidateModelStructure", "event", "system", modelStructure);
18
+ }
19
+
20
+ export const validateField = (field) => {
21
+
22
+ const allowedFieldTest = (fields) => {
23
+ // Check for unknown fields
24
+ const unknownFields = Object.keys(field).filter(f => ![...allowedFields, ...fields].includes(f));
25
+
26
+ if (unknownFields.length > 0) {
27
+ throw new Error(i18n.t('api.validate.unknowField', `Propriété(s) non reconnue(s): '{{0}}' pour le champ '{{1}}'`, [unknownFields.join(', '), field.name]));
28
+ }
29
+
30
+ const fieldInvalid = Object.keys(fields).find(f => JSON.stringify(field[f] || '').length > maxStringLength);
31
+ if (fieldInvalid) {
32
+ throw new Error(i18n.t('api.validate.invalidField', `Champ(s) non valide(s): '{{0}}'`, [fieldInvalid.name]));
33
+ }
34
+ }
35
+
36
+ // Check for required fields
37
+ if (!field.name || typeof field.name !== 'string') {
38
+ throw new Error(i18n.t('api.validate.requiredFieldString', "Le champ '{{0}}' est requis et doit être une chaîne de caractères.", ["name"]));
39
+ }
40
+ if (!field.type || typeof field.type !== 'string') {
41
+ throw new Error(i18n.t('api.validate.requiredFieldString', "Le champ '{{0}}' est requis et doit être une chaîne de caractères.", ["type"]));
42
+ }
43
+
44
+ // Check for specific field types
45
+ switch (field.type) {
46
+ case 'relation':
47
+ allowedFieldTest(['relation', 'multiple', 'relationFilter']);
48
+ if (!field.relation || typeof field.relation !== 'string' || field.relation.length > maxModelNameLength) {
49
+ throw new Error(i18n.t('api.validate.requiredFieldString', "Le champ '{{0}}' est requis et doit être une chaîne de caractères.", ["relation"]));
50
+ }
51
+ if (field.multiple !== undefined && typeof field.multiple !== 'boolean') {
52
+ throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["multiple"]));
53
+ }
54
+ if (field.relationFilter && typeof field.relationFilter !== 'object') {
55
+ throw new Error(i18n.t('api.validate.fieldObject', "L'attribut '{{0}}' doit être un objet.", ["relationFilter"]));
56
+ }
57
+ break;
58
+ case 'enum': {
59
+ allowedFieldTest(['items']);
60
+ if (!field.items || !Array.isArray(field.items) || field.items.length === 0) {
61
+ throw new Error(i18n.t('api.validate.fieldStringArray', "L'attribut '{{0}}' doit être un tableau de chaines de caractères.", ["items"]));
62
+ }
63
+ let id = field.items.findIndex(item => typeof item !== 'string');
64
+ if (id !== -1) {
65
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["items[" + id + "]"]));
66
+ }
67
+ break;
68
+ }
69
+ case 'number':
70
+ allowedFieldTest(['min', 'max', 'step', 'unit', 'delay', 'gauge', 'percent']);
71
+ if (field.min !== undefined && typeof field.min !== 'number') {
72
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["min"]));
73
+ }
74
+ if (field.max !== undefined && typeof field.max !== 'number') {
75
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["max"]));
76
+ }
77
+ if (field.max < field.min) {
78
+ throw new Error(i18n.t('api.validate.inferiorTo', "L'attribut '{{0}}' doit être inférieur à l'attribut '{{1}}'.", ["min", "max"]));
79
+ }
80
+ if (field.step !== undefined && typeof field.step !== 'number') {
81
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["step"]));
82
+ }
83
+ if (field.unit !== undefined && typeof field.unit !== 'string') {
84
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["unit"]));
85
+ }
86
+ if (field.delay !== undefined && typeof field.delay !== 'boolean') {
87
+ throw new Error(i18n.t('api.validate.fieldBoolean', "Le champ '{{0}}' doit être un booléen.", ["unit"]));
88
+ }
89
+ if (field.gauge !== undefined && typeof field.gauge !== 'boolean') {
90
+ throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["gauge"]));
91
+ }
92
+ if (field.percent !== undefined && typeof field.percent !== 'boolean') {
93
+ throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["percent"]));
94
+ }
95
+ break;
96
+ case 'string':
97
+ case 'string_t':
98
+ case 'richtext':
99
+ case 'richtext_t':
100
+ case 'url':
101
+ case 'email':
102
+ case 'phone':
103
+ case 'password':
104
+ case 'code':
105
+ if (field.type === 'code')
106
+ allowedFieldTest(['maxlength', 'language', 'conditionBuilder', 'targetModel']);
107
+ else if (['string_t', 'string'].includes(field.type))
108
+ allowedFieldTest(['maxlength', 'multiline']);
109
+ else
110
+ allowedFieldTest(['maxlength']);
111
+ if (field.maxlength !== undefined && typeof field.maxlength !== 'number') {
112
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxlength"]));
113
+ }
114
+ break;
115
+ case 'model':
116
+ case 'modelField':
117
+ allowedFieldTest([]);
118
+ break;
119
+ case 'object':
120
+ allowedFieldTest([]);
121
+ break;
122
+ case 'boolean':
123
+ allowedFieldTest([]);
124
+ break;
125
+ case 'date':
126
+ case 'datetime': {
127
+ allowedFieldTest(['min', 'max']);
128
+ if (field.min !== undefined && typeof field.min !== 'string') {
129
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["min"]));
130
+ }
131
+ if (field.max !== undefined && typeof field.max !== 'string') {
132
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["max"]));
133
+ }
134
+ const dtMin = field.min ? new Date(field.min) : null;
135
+ const dtMax = field.max ? new Date(field.max) : null;
136
+ if (dtMin && dtMax && dtMin > dtMax) {
137
+ throw new Error(i18n.t('api.validate.inferiorTo', "L'attribut '{{0}}' doit être inférieur à l'attribut '{{1}}'.", ["min", "max"]));
138
+ }
139
+ break;
140
+ }
141
+ case 'image':
142
+ case 'file': {
143
+ allowedFieldTest(['mimeTypes', 'maxSize']);
144
+ if (field.mimeTypes !== undefined && !Array.isArray(field.mimeTypes)) {
145
+ throw new Error(i18n.t('api.validate.fieldStringArray', "L'attribut '{{0}}' doit être un tableau de chaines de caractères.", ["mimeTypes"]));
146
+ }
147
+ let id;
148
+ if (field.mimeTypes !== undefined && (id = field.mimeTypes.findIndex(item => typeof item !== 'string')) !== -1) {
149
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["mimeTypes[" + id + "]"]));
150
+ }
151
+ if (field.maxSize !== undefined && typeof field.maxSize !== 'number') {
152
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxSize"]));
153
+ }
154
+ if (field.maxSize !== undefined && field.maxSize > maxFileSize) {
155
+ throw new Error(i18n.t('api.validate.fileSize', `L'attribut 'maxSize' ne doit pas dépasser {{0}} octets.`, [maxFileSize]));
156
+ }
157
+ break;
158
+ }
159
+ case 'color':
160
+ allowedFieldTest([]);
161
+ return true;
162
+ case 'cronSchedule':
163
+ allowedFieldTest(['cronMask']);
164
+ return true;
165
+ case 'calculated':
166
+ allowedFieldTest(['calculation']);
167
+ return true;
168
+ case 'array':
169
+ if (!field.itemsType || typeof field.itemsType !== 'string') {
170
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["itemsType"]));
171
+ }
172
+ if (!dataTypes[field.itemsType]) {
173
+ throw new Error(i18n.t('api.validate.invalidField', `Champ(s) non valide(s): '{{0}}'`, ["itemsType"]));
174
+ }
175
+ if (field.minItems !== undefined && typeof field.minItems !== 'number') {
176
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["minItems"]));
177
+ }
178
+ if (field.maxItems !== undefined && typeof field.maxItems !== 'number') {
179
+ throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["maxItems"]));
180
+ }
181
+ break;
182
+ default:
183
+ throw new Error(i18n.t('api.validate.unknowType', `Le type '{{0}}' n'est pas reconnu.`, [field.type]));
184
+ }
185
+
186
+ // Check for optional fields
187
+ if (field.required !== undefined && typeof field.required !== 'boolean') {
188
+ throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["required"]));
189
+ }
190
+ if (field.hint !== undefined && typeof field.hint !== 'string') {
191
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["hint"]));
192
+ }
193
+ if (field.default !== undefined && field.default !== null && typeof field.default !== typeof getDefaultForType(field) && typeof field.default !== 'function') {
194
+ throw new Error(i18n.t('api.validate.sameType', `L'attribut '{{0}}' doit être du même type que l'attribut '{{0}}' (${field.type}).`, ['default', 'type']));
195
+ }
196
+ if (field.validate !== undefined && typeof field.validate !== 'function') {
197
+ throw new Error(i18n.t('api.validate.fieldFunction', "L'attribut '{{0}}' doit être une fonction.", ['validate']));
198
+ }
199
+ if (field.unique !== undefined && typeof field.unique !== 'boolean') {
200
+ throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["unique"]));
201
+ }
202
+ if (field.placeholder !== undefined && typeof field.placeholder !== 'string') {
203
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["placeholder"]));
204
+ }
205
+ if (field.asMain !== undefined && typeof field.asMain !== 'boolean') {
206
+ throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["asMain"]));
207
+ }
208
+ if (field.unit !== undefined && typeof field.unit !== 'string') {
209
+ throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["unit"]));
210
+ }
211
+
212
+ return true;
213
+ };
214
+
215
+ /**
216
+ * Valide la structure et le contenu du document selon le modèle
217
+ */
218
+ export async function validateModelData(doc, model, isPatch = false) {
219
+ if (!isPatch) {
220
+ model.fields.forEach(field => {
221
+ const value = doc[field.name];
222
+ if (field.required) {
223
+ if (value === undefined && !('default' in field)) {
224
+ throw new Error(i18n.t('api.field.missingRequired', {field: field.name + " (" + model.name + ")"}));
225
+ }
226
+ if (value === '' || value === null) {
227
+ throw new Error(i18n.t('api.field.requiredCannotBeEmpty', {field: field.name}));
228
+ }
229
+ }
230
+ });
231
+ }
232
+
233
+ // 2. Validation des types de champs (toujours exécutée pour les champs fournis)
234
+ for (const [fieldName, value] of Object.entries(doc)) {
235
+ const fieldDef = model.fields.find(f => f.name === fieldName);
236
+ if (!fieldDef) continue; // On ignore les champs supplémentaires
237
+
238
+ const validator = dataTypes[fieldDef.type]?.validate;
239
+ const valid = validator && validator(value, fieldDef);
240
+ const realValidation = await Event.Trigger('OnDataValidate', "event", "system", value, fieldDef, doc);
241
+ if (!(valid || realValidation)) {
242
+ throw new Error(i18n.t('api.field.validationFailed', {field: fieldName, value}));
243
+ }
244
+ }
245
+ }
@@ -1 +1,29 @@
1
- export * from './data.js';
1
+ export * from './data.js';
2
+ export {validateModelData} from "./data.validation.js";
3
+ export {validateField} from "./data.validation.js";
4
+ export {validateModelStructure} from "./data.validation.js";
5
+ export {scheduleAlerts} from "./data.scheduling.js";
6
+ export {runStatefulAlertJob} from "./data.scheduling.js";
7
+ export {cancelAlerts} from "./data.scheduling.js";
8
+ export {installAllPacks} from "./data.operations.js";
9
+ export {installPack} from "./data.operations.js";
10
+ export {exportData} from "./data.operations.js";
11
+ export {importData} from "./data.operations.js";
12
+ export {searchData} from "./data.operations.js";
13
+ export {deleteData} from "./data.operations.js";
14
+ export {editData} from "./data.operations.js";
15
+ export {patchData} from "./data.operations.js";
16
+ export {pushDataUnsecure} from "./data.operations.js";
17
+ export {insertData} from "./data.operations.js";
18
+ export {getModels} from "./data.operations.js";
19
+ export {getModel} from "./data.operations.js";
20
+ export {deleteModels} from "./data.operations.js";
21
+ export {createModel} from "./data.operations.js";
22
+ export {editModel} from "./data.operations.js";
23
+ export {dataTypes} from "./data.operations.js";
24
+ export {dumpUserData} from "./data.backup.js";
25
+ export {loadFromDump} from "./data.backup.js";
26
+ export {validateRestoreRequest} from "./data.backup.js";
27
+ export {jobDumpUserData} from "./data.backup.js";
28
+ export {handleCustomEndpointRequest} from "./data.routes.js";
29
+ export {middlewareEndpointAuthenticator} from "./data.routes.js";
@@ -14,6 +14,7 @@ import {Logger} from "../gameObject.js";
14
14
  import rateLimit from "express-rate-limit";
15
15
  import ivm from "isolated-vm";
16
16
  import {emailDefaultConfig} from "../constants.js";
17
+ import {safeAssignObject} from "../core.js";
17
18
 
18
19
  export const userInitiator = async (req, res, next) => {
19
20
 
@@ -269,7 +270,7 @@ export async function calculateTotalUserStorageUsage(user) {
269
270
  export async function getEnv(user){
270
271
  const result = await searchData({ model: 'env' }, user);
271
272
  const envObject = result.data.reduce((acc, v) => {
272
- acc[v.name] = v.value;
273
+ safeAssignObject(acc, v.name, v.value);
273
274
  return acc;
274
275
  }, {});
275
276
  return envObject;
@@ -23,7 +23,7 @@ import {getHost} from "../constants.js";
23
23
  import {providers} from "./assistant/constants.js";
24
24
  import {ChatAnthropic} from "@langchain/anthropic";
25
25
  import {getAIProvider} from "./assistant/assistant.js";
26
- import {escapeRegex} from "../core.js";
26
+ import {escapeRegex, safeAssignObject} from "../core.js";
27
27
 
28
28
  let logger = null;
29
29
  export async function onInit(defaultEngine) {
@@ -1143,7 +1143,8 @@ export async function substituteVariables(template, contextData, user) {
1143
1143
  const newObj = {};
1144
1144
  for (const key in template) {
1145
1145
  if (Object.prototype.hasOwnProperty.call(template, key)) {
1146
- newObj[key] = await substituteVariables(template[key], contextData, user);
1146
+ const val = await substituteVariables(template[key], contextData, user);
1147
+ safeAssignObject(newObj, key, val);
1147
1148
  }
1148
1149
  }
1149
1150
  return newObj;
@@ -1,6 +1,7 @@
1
1
  import Stripe from 'stripe';
2
2
  import {editData, insertData, searchData} from '../modules/data/index.js';
3
3
  import {getEnv} from "../modules/user.js";
4
+ import {safeAssignObject} from "../core.js";
4
5
 
5
6
  const stripeInstances = {};
6
7
 
@@ -15,10 +16,10 @@ async function getStripeClient(user) {
15
16
  if (!stripeInstances[userId]) {
16
17
  const env = await getEnv(user);
17
18
  if (env.STRIPE_SECRET_KEY) {
18
- stripeInstances[userId] = {
19
+ safeAssignObject(stripeInstances, userId, {
19
20
  client: new Stripe(env.STRIPE_SECRET_KEY),
20
21
  webhookSecret: env.STRIPE_WEBHOOK_SECRET
21
- };
22
+ });
22
23
  }
23
24
  }
24
25
  return stripeInstances[userId] || null;