data-primals-engine 1.6.0 → 1.6.2-rc1

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/src/i18n.js CHANGED
@@ -5,6 +5,10 @@ import LanguageDetector from "i18next-browser-languagedetector";
5
5
  export const translations = {
6
6
  fr: {
7
7
  translation: {
8
+
9
+ "model_audience": "Audiences",
10
+ "model_deal": "Opportunités",
11
+
8
12
  "relationField.select.title": "Sélectionner {{modelName}}",
9
13
  "btns.validate": "Valider",
10
14
  "btns.create": "Créer",
@@ -1318,6 +1322,8 @@ export const translations = {
1318
1322
  },
1319
1323
  en: {
1320
1324
  translation: {
1325
+ "model_audience": "Audiences",
1326
+ "model_deal": "Opportunities",
1321
1327
  "relationField.select.title": "Select {{modelName}}",
1322
1328
  "btns.validate": "Validate",
1323
1329
  "btns.create": "Create",
@@ -2627,6 +2633,8 @@ export const translations = {
2627
2633
  },
2628
2634
  es: {
2629
2635
  translation: {
2636
+ "model_audience": "Audiencias",
2637
+ "model_deal": "Oportunidades",
2630
2638
  "relationField.select.title": "Seleccionar {{modelName}}",
2631
2639
  "btns.validate": "Validar",
2632
2640
  "btns.create": "Crear",
@@ -3973,6 +3981,10 @@ export const translations = {
3973
3981
  },
3974
3982
  pt: {
3975
3983
  translation: {
3984
+
3985
+ "model_audience": "Públicos",
3986
+ "model_campaign": "Campanhas de marketing",
3987
+ "model_deal": "Oportunidades",
3976
3988
  "relationField.select.title": "Selecionar {{modelName}}",
3977
3989
  "btns.validate": "Validar",
3978
3990
  "btns.create": "Criar",
@@ -4125,6 +4137,8 @@ export const translations = {
4125
4137
  },
4126
4138
  de: {
4127
4139
  translation: {
4140
+ "model_audience": "Zielgruppen",
4141
+ "model_deal": "Möglichkeiten",
4128
4142
  "relationField.select.title": "{{modelName}} auswählen",
4129
4143
  "btns.validate": "Validieren",
4130
4144
  "btns.create": "Erstellen",
@@ -5424,7 +5438,6 @@ export const translations = {
5424
5438
  "field_endpoint_isPublic_hint": "Se selezionato, questo endpoint sarà accessibile senza autenticazione. Lo script verrà eseguito con i permessi del proprietario dell'endpoint.",
5425
5439
  "field_endpoint_code": "Codice da eseguire",
5426
5440
  "field_endpoint_code_hint": "Lo script da eseguire. Deve restituire un valore o un oggetto che sarà la risposta JSON.",
5427
- "model_env": "Variabili d'Ambiente (env)",
5428
5441
  "datatable.advancedFilter.title": "Filtro avanzato (MongoDB)",
5429
5442
  "datatable.advancedFilter.desc": "Crea i tuoi filtri avanzati da zero, utilizzando gli operatori di aggregazione di MongoDB disponibili qui.",
5430
5443
  "editData": "Modifica in {{0}}",
@@ -5696,6 +5709,10 @@ export const translations = {
5696
5709
  "model_channel": "Canali di notifica",
5697
5710
  "model_message": "Messaggi",
5698
5711
  "model_kpi": "Indicatori KPI",
5712
+ "model_env": "Variabili d'Ambiente (env)",
5713
+ "model_audience": "Pubblici",
5714
+ "model_deal": "Opportunità",
5715
+
5699
5716
  "field_permission_name": "Nome",
5700
5717
  "field_permission_description": "Descrizione",
5701
5718
 
@@ -6628,6 +6645,9 @@ export const translations = {
6628
6645
  },
6629
6646
  cs: {
6630
6647
  translation: {
6648
+ "model_audience": "Publika",
6649
+ "model_campaign": "Marketingové kampaně",
6650
+ "model_deal": "Příležitosti",
6631
6651
  "relationField.select.title": "Vybrat {{modelName}}",
6632
6652
  "btns.validate": "Ověřit",
6633
6653
  "btns.create": "Vytvořit",
@@ -7553,6 +7573,8 @@ export const translations = {
7553
7573
  },
7554
7574
  ru: {
7555
7575
  translation: {
7576
+ "model_audience": "Аудитории",
7577
+ "model_deal": "Возможности",
7556
7578
  "relationField.select.title": "Выбрать {{modelName}}",
7557
7579
  "btns.validate": "Проверить",
7558
7580
  "btns.create": "Создать",
@@ -8832,6 +8854,8 @@ export const translations = {
8832
8854
  },
8833
8855
  ar: {
8834
8856
  translation: {
8857
+ "model_audience": "الجماهير",
8858
+ "model_deal": "الفرص",
8835
8859
  "relationField.select.title": "اختر {{modelName}}",
8836
8860
  "btns.validate": "تحقق",
8837
8861
  "btns.create": "إنشاء",
@@ -8901,7 +8925,7 @@ export const translations = {
8901
8925
  "field_endpoint_code": "الكود المنفذ",
8902
8926
  "field_endpoint_code_hint": "السكريبت الذي سيتم تنفيذه. يجب أن يعيد قيمة أو كائنًا سيكون استجابة JSON.",
8903
8927
  "model_env": "متغيرات البيئة (env)",
8904
-
8928
+
8905
8929
  "datatable.advancedFilter.title": "مرشح متقدم (MongoDB)",
8906
8930
  "datatable.advancedFilter.desc": "أنشئ مرشحاتك المتقدمة من الصفر، باستخدام عوامل تجميع MongoDB المتوفرة هنا",
8907
8931
  "editData": "تعديل في {{0}}",
@@ -10156,6 +10180,8 @@ export const translations = {
10156
10180
  },
10157
10181
  sv: {
10158
10182
  translation: {
10183
+ "model_audience": "Målgrupper",
10184
+ "model_deal": "Möjligheter",
10159
10185
  "relationField.select.title": "Välj {{modelName}}",
10160
10186
  "btns.validate": "Validera",
10161
10187
  "btns.create": "Skapa",
@@ -11316,7 +11342,7 @@ export const translations = {
11316
11342
  "no": "Nej",
11317
11343
  "true": "Sant",
11318
11344
  "false": "Falskt",
11319
-
11345
+
11320
11346
 
11321
11347
  "assistant.type": "Skriv ditt meddelande...",
11322
11348
  "assistant.welcome": "Hej! Hur kan jag hjälpa dig med din data?",
@@ -11383,6 +11409,10 @@ export const translations = {
11383
11409
  },
11384
11410
  el: {
11385
11411
  translation: {
11412
+ "model_audience": "Κοινά",
11413
+ "model_deal": "Ευκαιρίες",
11414
+ "model_endpoint": "Τελικά σημεία",
11415
+ "model_env": "Μεταβλητές Περιβάλλοντος",
11386
11416
  "relationField.select.title": "Επιλογή {{modelName}}",
11387
11417
  "btns.validate": "Επικύρωση",
11388
11418
  "btns.create": "Δημιουργία",
@@ -12586,6 +12616,8 @@ export const translations = {
12586
12616
  },
12587
12617
  fa: {
12588
12618
  translation: {
12619
+ "model_audience": "مخاطبان",
12620
+ "model_deal": "فرصت‌ها",
12589
12621
  "relationField.select.title": "{{modelName}} را انتخاب کنید",
12590
12622
  "btns.validate": "اعتبارسنجی",
12591
12623
  "btns.create": "ایجاد",
package/src/index.js CHANGED
@@ -7,6 +7,7 @@ export { Config } from './config.js';
7
7
  export { event_on, event_trigger, event_off } from './core.js';
8
8
  export { Event } from './events.js';
9
9
 
10
+ export { hasPermission, middlewareAuthenticator, getEnv, getSmtpConfig } from "./modules/user.js"
10
11
  export { sendSseToUser } from './modules/data/data.routes.js';
11
12
 
12
13
  export { UserProvider } from './providers.js';
@@ -459,6 +459,12 @@ export const dataTypes = {
459
459
  }
460
460
  };
461
461
 
462
+ const userStorageCache = new NodeCache({
463
+ stdTTL: 600, // 10 minutes
464
+ checkperiod: 120,
465
+ useClones: false
466
+ });
467
+
462
468
 
463
469
 
464
470
  let engine, logger;
@@ -466,6 +472,29 @@ export function onInit(defaultEngine) {
466
472
  engine = defaultEngine;
467
473
  logger = engine.getComponent(Logger);
468
474
 
475
+ // Précalculer périodiquement l'utilisation du stockage pour chaque utilisateur
476
+ const updateUserStorageUsage = async () => {
477
+ logger.debug('[Storage] Starting periodic user storage calculation...');
478
+ try {
479
+ const usersCollection = getCollection('users');
480
+ const users = await usersCollection.find({}, { projection: { username: 1 } }).toArray();
481
+
482
+ for (const user of users) {
483
+ try {
484
+ const usage = await calculateTotalUserStorageUsage(user);
485
+ userStorageCache.set(user.username, usage);
486
+ } catch (userError) {
487
+ logger.error(`[Storage] Failed to calculate storage for user ${user.username}:`, userError);
488
+ }
489
+ }
490
+ logger.debug(`[Storage] User storage calculation finished for ${users.length} users.`);
491
+ } catch (e) {
492
+ logger.error('[Storage] Error during periodic user storage calculation:', e);
493
+ }
494
+ };
495
+
496
+ setInterval(updateUserStorageUsage, 300000); // Toutes les 5 minutes
497
+ updateUserStorageUsage(); // Lancer une première fois au démarrage
469
498
  // Nettoyer périodiquement les relations obsolètes
470
499
  setInterval(() => {
471
500
  // Nettoyer les relations pour les clés qui n'existent plus
@@ -990,8 +1019,11 @@ async function checkLimits(datas, model, collection, me) {
990
1019
  const userStorageLimit = await engine.userProvider.getUserStorageLimit(me);
991
1020
 
992
1021
  // Vérification des limites utilisateur
993
- const currentStorageUsage = await calculateTotalUserStorageUsage(me);
994
- if (currentStorageUsage + incomingDataSize > userStorageLimit) {
1022
+ let currentStorageUsage = userStorageCache.get(me.username);
1023
+ if (currentStorageUsage === undefined) {
1024
+ currentStorageUsage = await calculateTotalUserStorageUsage(me); // Fallback si le cache est vide
1025
+ }
1026
+ if ((currentStorageUsage || 0) + incomingDataSize > userStorageLimit) {
995
1027
  throw new Error(i18n.t("api.data.storageLimitExceeded", {
996
1028
  limit: Math.round(userStorageLimit / megabytes)
997
1029
  }));
@@ -1074,7 +1106,7 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
1074
1106
  const updatePushData = {};
1075
1107
 
1076
1108
  for (const key in data) {
1077
- if (key.startsWith('_')) continue;
1109
+ if (key.startsWith('_') && !['_env', '_pack'].includes(key)) continue;
1078
1110
 
1079
1111
  if (isPlainObject(data[key]) && data[key].$push !== undefined) {
1080
1112
  // C'est une opération $push
@@ -1251,6 +1283,43 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
1251
1283
  }
1252
1284
  }
1253
1285
 
1286
+ // 8. Vérification des limites de stockage avant la mise à jour
1287
+ const originalDocsSize = calculateDataSize(existingDocs);
1288
+ const finalStateForSize = existingDocs.map(doc => {
1289
+ const updatedDoc = { ...doc, ...updateSetData };
1290
+ for (const fieldName in updatePushData) {
1291
+ let itemsToAdd;
1292
+ const pushValue = updatePushData[fieldName];
1293
+ if (isPlainObject(pushValue) && pushValue.$each) {
1294
+ itemsToAdd = pushValue.$each;
1295
+ } else if (Array.isArray(pushValue)) {
1296
+ itemsToAdd = pushValue;
1297
+ } else {
1298
+ itemsToAdd = [pushValue];
1299
+ }
1300
+ if (!Array.isArray(updatedDoc[fieldName])) {
1301
+ updatedDoc[fieldName] = [];
1302
+ }
1303
+ updatedDoc[fieldName].push(...itemsToAdd);
1304
+ }
1305
+ return updatedDoc;
1306
+ });
1307
+
1308
+ const finalDocsSize = calculateDataSize(finalStateForSize);
1309
+ const sizeDelta = finalDocsSize - originalDocsSize;
1310
+
1311
+ if (sizeDelta > 0) {
1312
+ const userStorageLimit = await engine.userProvider.getUserStorageLimit(user);
1313
+ const currentStorageUsage = await calculateTotalUserStorageUsage(user);
1314
+ if (currentStorageUsage + sizeDelta > userStorageLimit) {
1315
+ throw new Error(i18n.t("api.data.storageLimitExceeded", { limit: Math.round(userStorageLimit / megabytes) }));
1316
+ }
1317
+ const serverCapacity = await checkServerCapacity(sizeDelta);
1318
+ if (!serverCapacity.isSufficient) {
1319
+ throw new Error(i18n.t("api.data.serverStorageFull"));
1320
+ }
1321
+ }
1322
+
1254
1323
  // 8. Calcul du nouveau hash en simulant l'état final
1255
1324
  const finalStateForHash = { ...existingDocs[0], ...updateSetData };
1256
1325
  for (const fieldName in updatePushData) {
@@ -1694,7 +1763,8 @@ const generateCacheKey = (query, user) => {
1694
1763
  filter: query.filter,
1695
1764
  depth: query.depth,
1696
1765
  autoExpand: query.autoExpand,
1697
- pack: query.pack
1766
+ pack: query.pack,
1767
+ env: query.env
1698
1768
  },
1699
1769
  user: user.username
1700
1770
  };
@@ -1710,7 +1780,7 @@ const searchCache = new NodeCache({
1710
1780
  });
1711
1781
 
1712
1782
  export const searchData = async (query, user) => {
1713
- const {page, limit, sort, model, pipelinesPosition, pipelines: customPipelines = [], ids, timeout, pack} = query;
1783
+ const {page, limit, sort, model, pipelinesPosition, pipelines: customPipelines = [], ids, timeout, pack, env} = query;
1714
1784
 
1715
1785
  if (user && user.username !== 'demo' && isLocalUser(user) && (
1716
1786
  !await hasPermission(["API_ADMIN", "API_SEARCH_DATA", "API_SEARCH_DATA_" + model], user) ||
@@ -2162,8 +2232,18 @@ export const searchData = async (query, user) => {
2162
2232
  }
2163
2233
  }
2164
2234
 
2235
+ let envMatchStage;
2236
+ if (env === 'production') {
2237
+ envMatchStage = {$match: {$or: [{'_env': 'production'}, {'_env': {$exists: false}}]}};
2238
+ } else if (env) {
2239
+ envMatchStage = {$match: {'_env': env}};
2240
+ } else {
2241
+ envMatchStage = {$match: {'_env': {$exists: false}}};
2242
+ }
2243
+
2165
2244
  return pipelines.concat(
2166
2245
  [
2246
+ envMatchStage,
2167
2247
  {$match: {'_pack': pack ? pack : {$exists: false}}},
2168
2248
  {$match: {$expr: dataNoRelation}}
2169
2249
  ],
@@ -3128,6 +3208,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3128
3208
  delete docForInsert._temp_pack_id;
3129
3209
 
3130
3210
  if (user) docForInsert._user = username;
3211
+
3131
3212
  docForInsert._model = modelName;
3132
3213
  docForInsert._hash = getFieldValueHash(modelDefForHash, docForInsert);
3133
3214
 
@@ -3136,6 +3217,8 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3136
3217
  _hash: docForInsert._hash,
3137
3218
  _model: modelName
3138
3219
  };
3220
+
3221
+ if (docForInsert._env) existingQuery._env = docForInsert._env;
3139
3222
  if (user) existingQuery._user = username;
3140
3223
 
3141
3224
  const existingDoc = await collection.findOne(existingQuery, {projection: {_id: 1}});
@@ -3159,6 +3242,9 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3159
3242
  const result = await collection.insertMany(finalDocsToInsert, {ordered: false});
3160
3243
  summary.datas.inserted += result.insertedCount;
3161
3244
 
3245
+ // Invalider le cache pour le modèle qui vient d'être modifié
3246
+ invalidateModelCache(modelName);
3247
+
3162
3248
  docsToInsert.forEach((doc, index) => {
3163
3249
  if (result.insertedIds[index]) {
3164
3250
  tempIdToNewIdMap[doc._temp_pack_id_for_mapping] = result.insertedIds[index];
@@ -108,7 +108,7 @@ export async function onInit(defaultEngine) {
108
108
  * @returns {Promise<Set<string>>} Un Set contenant les noms de toutes les permissions actives.
109
109
  * @private
110
110
  */
111
- export async function getUserActivePermissions(user) {
111
+ export async function getUserActivePermissions(user, env = null) {
112
112
  const datasCollection = await getCollectionForUser(user);
113
113
  const now = new Date();
114
114
  const activePermissions = new Set();
@@ -139,12 +139,17 @@ export async function getUserActivePermissions(user) {
139
139
  // --- ÉTAPE 2: Appliquer les exceptions de permission ---
140
140
  const exceptions = await datasCollection.aggregate([
141
141
  {
142
- $match: {
142
+ $match: { // Filtre de base pour les exceptions de l'utilisateur
143
143
  _model: "userPermission",
144
- user: user._id, // Pas besoin de convertir en ObjectId si c'est déjà une string
145
- $or: [
146
- { expiresAt: { $exists: false } },
147
- { expiresAt: { $gt: now } }
144
+ user: user._id,
145
+ $and: [ // Filtre sur l'environnement et l'expiration
146
+ {
147
+ $or: [ // La permission est soit globale, soit spécifique à l'environnement demandé
148
+ { env: { $exists: false } },
149
+ { env: env }
150
+ ]
151
+ },
152
+ { $or: [{ expiresAt: { $exists: false } }, { expiresAt: { $gt: now } }] }
148
153
  ]
149
154
  }
150
155
  },
@@ -193,7 +198,7 @@ export async function getUserActivePermissions(user) {
193
198
  * @param {object} user - L'objet utilisateur authentifié.
194
199
  * @returns {Promise<boolean>} - True si l'utilisateur a la permission, sinon false.
195
200
  */
196
- export async function hasPermission(permissionNames, user) {
201
+ export async function hasPermission(permissionNames, user, env = null) {
197
202
  // Garde la compatibilité pour les utilisateurs non-locaux (ex: système)
198
203
  if (!isLocalUser(user)) {
199
204
  const userRoles = new Set(user.roles || []);
@@ -209,10 +214,10 @@ export async function hasPermission(permissionNames, user) {
209
214
  }
210
215
 
211
216
  // 1. Obtenir l'ensemble final et à jour des permissions de l'utilisateur
212
- const activePermissions = await getUserActivePermissions(user);
217
+ const activePermissions = await getUserActivePermissions(user, env);
213
218
 
214
219
  // 2. Vérifier si au moins une des permissions requises est dans l'ensemble des permissions actives
215
- return requiredPermissions.some(pName => activePermissions.has(pName));
220
+ return requiredPermissions.some(pName => activePermissions.has(pName)) || false;
216
221
 
217
222
  } catch (e) {
218
223
  logger.error("Erreur lors de la vérification des permissions :", e);
@@ -1380,10 +1380,7 @@ export async function triggerWorkflows(triggerData, user, eventType) {
1380
1380
  }
1381
1381
  }
1382
1382
 
1383
- return new Promise((resolve) => setTimeout(async () => {
1384
- await trigger(triggerData, user, eventType);
1385
- resolve();
1386
- }, 0));
1383
+ return trigger(triggerData, user, eventType);
1387
1384
  }
1388
1385
  /**
1389
1386
  * Processes a workflowRun instance step-by-step.