data-primals-engine 1.6.5 → 1.7.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/src/migrate.js CHANGED
@@ -24,7 +24,7 @@ engine.start(port, async () => {
24
24
  const target = process.argv[3]; // Le nom du fichier de migration cible
25
25
 
26
26
  try {
27
- const db = MongoDatabase;
27
+ const db = MongoDatabase();
28
28
  const { executedNames, allMigrationFiles } = await getMigrationStatus(db);
29
29
 
30
30
  switch (command) {
@@ -8,7 +8,7 @@ import {Config} from "../../config.js";
8
8
  import { Event } from "../../events.js";
9
9
  import rateLimit from "express-rate-limit";
10
10
  import {generateLimiter} from "../user.js";
11
- import {assistantConfig, maxAIReflectiveSteps} from "../../constants.js";
11
+ import {maxAIReflectiveSteps} from "../../constants.js";
12
12
  import i18n from "../../i18n.js";
13
13
  import {parseSafeJSON} from "../../core.js";
14
14
 
@@ -80,16 +80,15 @@ REGLE FONDATRICE : suis les règles et ne dévie pas du chemin.
80
80
  STYLE UTILISE : apporte l'information au plus rapide, sans détours, ni sollicitation à l'utilisateur, ou à des tiers.
81
81
 
82
82
  FORMAT DE RÉPONSE OBLIGATOIRE :
83
- Ta réponse DOIT être un objet JSON unique OU un tableau d'objets JSON. Chaque objet doit contenir exactement 2 champs :
83
+ Ta réponse DOIT être un tableau d'objets JSON (Même pour une seule action). Chaque objet doit contenir exactement 2 champs :
84
84
  1. "action" (string)
85
85
  2. "params" (object)
86
86
 
87
- - Pour une action simple, retourne un objet : { "action": "...", "params": {...} }
88
- - Pour enchaîner plusieurs actions, retourne un tableau :
89
- [
90
- { "action": "search_models", "params": {"query": "..."} },
91
- { "action": "displayMessage", "params": { "message": "Je cherche les modèles..." } }
92
- ]
87
+ Exemple :
88
+ [
89
+ { "action": "search_models", "params": {"query": "..."} },
90
+ { "action": "displayMessage", "params": { "message": "Je cherche les modèles..." } }
91
+ ]
93
92
 
94
93
  Tu as accès aux outils et actions suivants.
95
94
 
@@ -529,7 +528,7 @@ export async function handleChatRequest(params, user, sendEvent = null) {
529
528
  }
530
529
 
531
530
  const toolResults = [];
532
- let finalActionResult = null;
531
+ const finalActionResults = [];
533
532
 
534
533
  for (const command of commands) {
535
534
  logger.debug(`[Assistant] Action décidée par l'IA: ${command.action}`, command);
@@ -554,27 +553,31 @@ export async function handleChatRequest(params, user, sendEvent = null) {
554
553
  const res = await Event.Trigger('OnChatAction', 'event', 'user', action, parsedParams, command, llmOptions, user, params);
555
554
 
556
555
  if (res) {
557
- // On a trouvé une action finale. On la stocke et on arrête de chercher.
558
- finalActionResult = res;
559
- break; // Sort de la boucle des commandes
556
+ if (res.isToolResult) {
557
+ toolResults.push({ action, result: res.toolResult || res.displayMessage });
558
+ } else {
559
+ // On a trouvé une action finale. On la stocke.
560
+ finalActionResults.push(res);
561
+ }
560
562
  } else {
561
563
  // Si l'action n'est reconnue par aucune des logiques ci-dessus
562
564
  logger.warn(`[Assistant] Action non reconnue reçue de l'IA: ${action}`);
563
- finalActionResult = {
565
+ finalActionResults.push({
564
566
  success: true,
565
567
  displayMessage: i18n.t('assistant.unknownAction', "Désolé, je ne comprends pas la commande '{{action}}'.", { action })
566
- };
567
- break; // Sortir de la boucle des commandes
568
+ });
568
569
  }
569
570
  }
570
571
 
571
- // Si une action finale a été exécutée, on retourne son résultat.
572
- if (finalActionResult) {
572
+ // Si une ou plusieurs actions finales ont été exécutées, on retourne le résultat.
573
+ if (finalActionResults.length > 0) {
574
+ // Rétrocompatibilité : si un seul résultat, on renvoie l'objet directement. Sinon, on renvoie un tableau 'results'.
575
+ const response = finalActionResults.length === 1 ? finalActionResults[0] : { success: true, results: finalActionResults };
573
576
  if (sendEvent) {
574
- sendEvent('final_result', finalActionResult);
577
+ sendEvent('final_result', response);
575
578
  return;
576
579
  }
577
- return finalActionResult;
580
+ return response;
578
581
  }
579
582
 
580
583
  // Si on a uniquement des résultats d'outils, on les ajoute à l'historique et on continue la boucle.
@@ -673,7 +676,14 @@ async function handleFinalChatAction(action, params, parsedResponse, llmOptions,
673
676
 
674
677
  // Actions nécessitant une confirmation de l'utilisateur
675
678
  if (['post', 'update', 'delete'].includes(action)) {
676
- return { success: true, displayMessage: i18n.t('assistant.confirmActionPrompt', "Veuillez confirmer l'action suivante :"), confirmationRequest: parsedResponse };
679
+ return {
680
+ success: true,
681
+ displayMessage: i18n.t('assistant.confirmActionPrompt', "Veuillez confirmer l'action suivante :"),
682
+ confirmationRequest: parsedResponse,
683
+ model: params.model,
684
+ filter: params.filter,
685
+ data: params.data
686
+ };
677
687
  }
678
688
 
679
689
  // Action finale pour afficher un message
@@ -8,7 +8,7 @@ import {downloadFromS3, getUserS3Config, listS3Backups, uploadToS3} from "../buc
8
8
  import fs from "node:fs";
9
9
  import {modelsCache, runCryptoWorkerTask} from "./data.core.js";
10
10
  import * as tar from "tar";
11
- import {getCollection, getUserCollectionName, modelsCollection} from "../mongodb.js";
11
+ import {getCollection, getDatabase, getUserCollectionName, modelsCollection} from "../mongodb.js";
12
12
  import {removeFile} from "../file.js";
13
13
  import {cancelAlerts, scheduleAlerts} from "./data.scheduling.js";
14
14
  import {dbName} from "../../constants.js";
@@ -136,7 +136,7 @@ export const loadFromDump = async (user, options = {}) => {
136
136
  await tar.extract({file: backupFilePath, gzip: true, C: tmpRestoreDir, sync: true});
137
137
 
138
138
  // ... (Cleaning logic: deleteMany, removeFile, cancelAlerts) ...
139
- const datasCollection = getCollection("datas");
139
+ const datasCollection = getCollection(Config.Get('dataCollection', 'datas'));
140
140
  if (modelsOnly) {
141
141
  await modelsCollection.deleteMany({_user: user.username});
142
142
  } else {
@@ -169,7 +169,7 @@ export const loadFromDump = async (user, options = {}) => {
169
169
  args.push('--nsInclude', `${d}.models`);
170
170
  } else {
171
171
  // mongorestore accepte plusieurs fois l'option --nsInclude
172
- args.push('--nsInclude', `${d}.datas`);
172
+ args.push('--nsInclude', `${d}.${Config.Get('dataCollection', 'datas')}`);
173
173
  args.push('--nsInclude', `${d}.models`);
174
174
  }
175
175
  // Le répertoire source est le dernier argument
@@ -245,7 +245,7 @@ export const dumpUserData = async (user) => {
245
245
 
246
246
 
247
247
  const d = Config.Get('dbName', dbName);
248
- const collections = await MongoDatabase.listCollections().toArray();
248
+ const collections = await getDatabase().listCollections().toArray();
249
249
  for (const collection of collections) {
250
250
  const collsToBackup = [await getUserCollectionName(user), 'models'];
251
251
  if (collsToBackup.includes(collection.name)) {
@@ -2,7 +2,7 @@ import {Logger} from "../../gameObject.js";
2
2
  import {mkdir} from 'node:fs/promises';
3
3
  import {onInit as historyInit} from "./data.history.js";
4
4
  import {isDemoUser, isLocalUser} from "../../data.js";
5
- import {install, maxAlertsPerUser, maxRequestData, storageSafetyMargin} from "../../constants.js";
5
+ import {install, storageSafetyMargin} from "../../constants.js";
6
6
  import {createCollection, getCollection} from "../mongodb.js";
7
7
  import path from "node:path";
8
8
  import {isGUID, sequential} from "../../core.js";
@@ -10,7 +10,6 @@ import {Event} from "../../events.js";
10
10
  import fs from "node:fs";
11
11
  import schedule from "node-schedule";
12
12
  import {middleware} from "../../middlewares/middleware-mongodb.js";
13
- import i18n from "../../i18n.js";
14
13
  import checkDiskSpace from "check-disk-space";
15
14
  import {removeFile} from "../file.js";
16
15
  import {hasPermission} from "../user.js";
@@ -18,11 +17,12 @@ import { onInit as userInit } from '../user.js';
18
17
  import {registerRoutes} from "./data.routes.js";
19
18
  import {mongoDBWhitelist} from "./data.core.js";
20
19
  import {onInit as relationsInit} from "./data.relations.js";
21
- import {validateField, onInit as validationInit} from "./data.validation.js";
20
+ import { onInit as validationInit} from "./data.validation.js";
22
21
  import {cancelAlerts, scheduleAlerts, onInit as scheduleInit} from "./data.scheduling.js";
23
22
  import {deleteData, installPack, onInit as operationsInit} from "./data.operations.js";
24
23
  import {jobDumpUserData, onInit as backupInit} from "./data.backup.js";
25
24
  import {Config} from "../../config.js";
25
+ import {profiles} from "../../profiles.js";
26
26
 
27
27
  let engine;
28
28
  let logger;
@@ -57,7 +57,7 @@ export async function onInit(defaultEngine) {
57
57
 
58
58
  const i = Config.Get('install', install);
59
59
  if( i ) {
60
- datasCollection = await createCollection("datas");
60
+ datasCollection = await createCollection(Config.Get('dataCollection', 'datas'));
61
61
  historyCollection = await createCollection("history");
62
62
  filesCollection = await createCollection("files");
63
63
  packsCollection = await createCollection("packs");
@@ -127,7 +127,7 @@ export async function onInit(defaultEngine) {
127
127
 
128
128
  }else {
129
129
  modelsCollection = getCollection("models");
130
- datasCollection = getCollection("datas");
130
+ datasCollection = getCollection(Config.Get('dataCollection','datas'));
131
131
  filesCollection = getCollection("files");
132
132
  packsCollection = getCollection("packs");
133
133
  historyCollection = getCollection("history");
@@ -256,7 +256,7 @@ export async function handleDemoInitialization(req, res) {
256
256
 
257
257
  try {
258
258
  // 1. Nettoyage de l'environnement (inchangé)
259
- const datasCollection = getCollection("datas");
259
+ const datasCollection = getCollection(Config.Get('dataCollection',"datas"));
260
260
  const modelsCollection = getCollection("models");
261
261
  const filesCollection = getCollection("files");
262
262
 
@@ -24,7 +24,7 @@ import {
24
24
  optionsSanitizer,
25
25
  searchRequestTimeout
26
26
  } from "../../constants.js";
27
- import {anonymizeText, getFieldValueHash, getUserId, isDemoUser, isLocalUser} from "../../data.js";
27
+ import {anonymizeText, encryptValue, getFieldValueHash, getUserId, isDemoUser, isLocalUser} from "../../data.js";
28
28
  import sanitizeHtml from "sanitize-html";
29
29
  import {importJobs, modelsCache, runCryptoWorkerTask, runImportExportWorker} from "./data.core.js";
30
30
  import {
@@ -71,7 +71,7 @@ const IMPORT_CHUNK_DELAY_MS = 1000; // Délai en millisecondes entre le traiteme
71
71
 
72
72
  export const dataTypes = {
73
73
  object: {
74
- validate: (value, field) => {
74
+ validate: (value, field) => {
75
75
  return value === null || isPlainObject(value);
76
76
  }
77
77
  },
@@ -112,7 +112,13 @@ export const dataTypes = {
112
112
  const ml = Math.min(Math.max(field.maxlength, 0), m);
113
113
  return value === null || typeof value === 'string' && (!ml || value.length <= ml)
114
114
  },
115
- anonymize: anonymizeText
115
+ anonymize: anonymizeText,
116
+ filter: async (value, field) => {
117
+ if (field.encrypted && value) {
118
+ return encryptValue(value);
119
+ }
120
+ return value;
121
+ }
116
122
  },
117
123
  code: {
118
124
  validate: (value, field) => {
@@ -142,8 +148,12 @@ export const dataTypes = {
142
148
  const ml = Math.min(Math.max(field.maxlength, 0), m);
143
149
  return value === null || typeof value === 'string' && (!ml || value.length <= ml)
144
150
  },
145
- filter: async (value) => {
146
- return sanitizeHtml(value, optionsSanitizer);
151
+ filter: async (value, field) => {
152
+ const sanitized = sanitizeHtml(value, optionsSanitizer);
153
+ if (field.encrypted && sanitized) {
154
+ return encryptValue(sanitized);
155
+ }
156
+ return sanitized;
147
157
  },
148
158
  anonymize: anonymizeText
149
159
  },
@@ -152,7 +162,7 @@ export const dataTypes = {
152
162
  if (value === null)
153
163
  return true;
154
164
  const m = Config.Get('maxStringLength', maxStringLength);
155
- const ml = Math.min(Math.max(field.maxlength, 0), maxStringLength);
165
+ const ml = Math.min(Math.max(field.maxlength, 0), m);
156
166
  // La valeur peut être une chaîne de caractères...
157
167
  if (typeof value === 'string') {
158
168
  return !ml || value.length <= ml;
@@ -850,7 +860,7 @@ export const insertData = async (modelName, data, files, user, triggerWorkflow =
850
860
  return {success: true, data: firstInsertedDoc, insertedIds: insertedIds.map(id => id.toString())};
851
861
 
852
862
  } catch (error) { // Attrape les erreurs de permission ou de pushDataUnsecure
853
- logger.error(`[insertData] Main error during insertion process for model ${modelName}: ${error.message}`, error.stack);
863
+ logger?.error(`[insertData] Main error during insertion process for model ${modelName}: ${error.message}`, error.stack);
854
864
  // Renvoyer une structure d'erreur cohérente
855
865
  return {
856
866
  success: false,
@@ -1474,7 +1484,7 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
1474
1484
  };
1475
1485
 
1476
1486
  } catch (error) {
1477
- logger.error("Erreur lors de la mise à jour de la ressource :", error);
1487
+ logger?.error("Erreur lors de la mise à jour de la ressource :", error);
1478
1488
  return {success: false, error: error.message};
1479
1489
  }
1480
1490
  };
@@ -1716,9 +1726,10 @@ export const deleteData = async (modelName, filter, user = {}, triggerWorkflow,
1716
1726
  } else {
1717
1727
  logger.info(`[deleteData] No documents to delete for user ${user?.username} after permission checks or matching criteria.`);
1718
1728
  }
1719
-
1720
1729
  const res = { success: true, deletedCount };
1721
1730
 
1731
+ if (!modelNameToInvalidate)
1732
+ return res;
1722
1733
  // --- CORRECTION ---
1723
1734
  // The event payload must match what the history listener expects: { modelName, user, before }.
1724
1735
  // 'documentsToDelete' contains the full documents before they were deleted.
@@ -1845,7 +1856,6 @@ export const searchData = async (query, user) => {
1845
1856
  // Vérifier si les données sont en cache
1846
1857
  const cachedData = searchCache.get(cacheKey);
1847
1858
  if (cachedData) {
1848
- console.log('Cache hit for key:', cacheKey);
1849
1859
  return cachedData;
1850
1860
  }
1851
1861
 
@@ -3090,7 +3100,6 @@ export const exportData = async (options, user) => {
3090
3100
  * Installs pack models and data for a user or globally.
3091
3101
  * Can accept either a pack ID (to install from database) or a direct pack JSON object.
3092
3102
  *
3093
- * @param {object} logger - Logger instance
3094
3103
  * @param {string|object} packIdentifier - Either pack ID (string) or pack JSON object
3095
3104
  * @param {object|null} user - User object (if installing for user) or null (for global install)
3096
3105
  * @param {string} [lang='en'] - Language code for localized data
@@ -3099,7 +3108,6 @@ export const exportData = async (options, user) => {
3099
3108
  export async function installPack(packIdentifier, user = null, lang = 'en', options = {}) {
3100
3109
  let pack;
3101
3110
  const packsCollection = getCollection('packs');
3102
-
3103
3111
  // Determine if we're working with an ID or direct pack object
3104
3112
  if (typeof packIdentifier === 'string') {
3105
3113
  let p;
@@ -3139,12 +3147,19 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3139
3147
  throw new Error('Invalid pack identifier - must be either pack ID string or pack object');
3140
3148
  }
3141
3149
 
3150
+ // Vérifier si le pack est déjà installé pour cet utilisateur
3151
+ const existingPack = await packsCollection.findOne({ name: pack.name, _user: user?.username });
3152
+ if (existingPack) {
3153
+ logger?.warn(`Pack '${pack.name}' is already installed for user '${user?.username}'. Skipping installation.`);
3154
+ return { success: true, summary: {}, errors: [], modifiedCount: 0 };
3155
+ }
3156
+
3142
3157
  const username = user ? user.username : null;
3143
3158
  const logPrefix = username
3144
3159
  ? `Installing pack '${pack.name}' for user '${username}'`
3145
3160
  : `Installing pack '${pack.name}' globally`;
3146
3161
 
3147
- logger.info(`--- ${logPrefix} ---`);
3162
+ logger?.info(`--- ${logPrefix} ---`);
3148
3163
 
3149
3164
  const summary = {
3150
3165
  models: {installed: [], skipped: [], failed: []},
@@ -3159,9 +3174,8 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3159
3174
  if (Array.isArray(pack.models)) {
3160
3175
  // For user installs, check existing models
3161
3176
  const existingModels = user
3162
- ? await modelsCollection.find({_user: username}).toArray()
3177
+ ? await modelsCollection.find({_user: user.username}).toArray()
3163
3178
  : await modelsCollection.find({_user: {$exists: false}}).toArray();
3164
-
3165
3179
  const existingModelNames = existingModels.map(m => m.name);
3166
3180
 
3167
3181
  for (let i = 0; i < pack.models.length; i++) {
@@ -3170,8 +3184,9 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3170
3184
  const modelName = typeof modelOrName === 'string' ? modelOrName : modelOrName?.name;
3171
3185
  if (!modelName) throw new Error('Model definition in pack is missing a name.');
3172
3186
 
3173
- if (existingModelNames.includes(modelName)) {
3174
- logger.debug(`[Model Install] Skipping '${modelName}': already exists`);
3187
+ const r = typeof user?.username === 'string' && await modelsCollection.findOne({name: modelName, _user: user.username || /.*/});
3188
+ if (r ) {
3189
+ logger?.debug(`[Model Install] Skipping '${modelName}': already exists`);
3175
3190
  summary.models.skipped.push(modelName);
3176
3191
  continue;
3177
3192
  }
@@ -3198,7 +3213,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3198
3213
  await modelsCollection.insertOne(preparedModel);
3199
3214
  summary.models.installed.push(modelName);
3200
3215
  } catch (e) {
3201
- logger.error(e);
3216
+ logger?.error(e);
3202
3217
  const modelName = typeof modelOrName === 'string' ? modelOrName : modelOrName?.name || 'unknown';
3203
3218
  errors.push(`Failed to install model '${modelName}': ${e.message}`);
3204
3219
  summary.models.failed.push(modelName);
@@ -3209,8 +3224,8 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3209
3224
  // --- PHASE 2: DATA INSTALLATION ---
3210
3225
  const dataToInstall = {...pack.data?.all, ...pack.data?.[lang]};
3211
3226
  if (!dataToInstall || Object.keys(dataToInstall).length === 0) {
3212
- logger.warn(`Pack '${pack.name}' has no data to install.`);
3213
- return {success: false, summary, errors, modifiedCount: 0};
3227
+ logger?.warn(`Pack '${pack.name}' has no data to install.`);
3228
+ return {success: errors.length === 0, summary, errors, modifiedCount: 0};
3214
3229
  }
3215
3230
 
3216
3231
  // Process link references (same as original)
@@ -3243,49 +3258,49 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3243
3258
  const documents = dataToInstall[modelName];
3244
3259
  if (documents.length === 0) continue;
3245
3260
 
3246
- const docsToInsert = [];
3247
- const modelDefForHash = await getModel(modelName, user);
3261
+ try {
3262
+ const docsToInsert = [];
3263
+ const modelDefForHash = await getModel(modelName, user);
3248
3264
 
3249
- for (const docSource of documents) {
3250
- let docForInsert = {...docSource};
3265
+ for (const docSource of documents) {
3266
+ let docForInsert = {...docSource};
3251
3267
 
3252
- // Clear $link fields for first pass
3253
- for (const key in docForInsert) {
3254
- if (isPlainObject(docForInsert[key]) && docForInsert[key].$link) {
3255
- docForInsert[key] = null;
3268
+ // Clear $link fields for first pass
3269
+ for (const key in docForInsert) {
3270
+ if (isPlainObject(docForInsert[key]) && docForInsert[key].$link) {
3271
+ docForInsert[key] = null;
3272
+ }
3256
3273
  }
3257
- }
3258
3274
 
3259
- const tempId = docForInsert._temp_pack_id;
3260
- delete docForInsert._id;
3261
- delete docForInsert._temp_pack_id;
3275
+ const tempId = docForInsert._temp_pack_id;
3276
+ delete docForInsert._id;
3277
+ delete docForInsert._temp_pack_id;
3262
3278
 
3263
- if (user) docForInsert._user = username;
3264
-
3265
- docForInsert._model = modelName;
3266
- docForInsert._hash = getFieldValueHash(modelDefForHash, docForInsert);
3279
+ if (user) docForInsert._user = username;
3267
3280
 
3268
- // Check for existing document
3269
- const existingQuery = {
3270
- _hash: docForInsert._hash,
3271
- _model: modelName
3272
- };
3281
+ docForInsert._model = modelName;
3282
+ docForInsert._hash = getFieldValueHash(modelDefForHash, docForInsert);
3273
3283
 
3274
- if (docForInsert._env) existingQuery._env = docForInsert._env;
3275
- if (user) existingQuery._user = username;
3284
+ // Check for existing document
3285
+ const existingQuery = {
3286
+ _hash: docForInsert._hash,
3287
+ _model: modelName
3288
+ };
3276
3289
 
3277
- const existingDoc = await collection.findOne(existingQuery, {projection: {_id: 1}});
3278
- if (existingDoc) {
3279
- tempIdToNewIdMap[tempId] = existingDoc._id;
3280
- summary.datas.skipped++;
3281
- } else {
3282
- docForInsert._temp_pack_id_for_mapping = tempId;
3283
- docsToInsert.push(docForInsert);
3290
+ if (docForInsert._env) existingQuery._env = docForInsert._env;
3291
+ if (user) existingQuery._user = username;
3292
+
3293
+ const existingDoc = await collection.findOne(existingQuery, {projection: {_id: 1}});
3294
+ if (existingDoc) {
3295
+ tempIdToNewIdMap[tempId] = existingDoc._id;
3296
+ summary.datas.skipped++;
3297
+ } else {
3298
+ docForInsert._temp_pack_id_for_mapping = tempId;
3299
+ docsToInsert.push(docForInsert);
3300
+ }
3284
3301
  }
3285
- }
3286
3302
 
3287
- if (docsToInsert.length > 0) {
3288
- try {
3303
+ if (docsToInsert.length > 0) {
3289
3304
  const finalDocsToInsert = docsToInsert.map(d => {
3290
3305
  const doc = {...d};
3291
3306
  delete doc._temp_pack_id_for_mapping;
@@ -3303,22 +3318,22 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3303
3318
  tempIdToNewIdMap[doc._temp_pack_id_for_mapping] = result.insertedIds[index];
3304
3319
  }
3305
3320
  });
3306
- } catch (e) {
3307
- summary.datas.failed += docsToInsert.length;
3308
- errors.push(`Error inserting batch for ${modelName}: ${e.message}`);
3309
- logger.error(`[Pack Install] Error on insertMany for model ${modelName}:`, e);
3310
3321
  }
3322
+ } catch (e) {
3323
+ summary.datas.failed += documents.length;
3324
+ errors.push(`Error processing model ${modelName}: ${e.message}`);
3325
+ logger?.error(`[Pack Install] Error processing model ${modelName}:`, e);
3311
3326
  }
3312
3327
  }
3313
3328
 
3314
3329
  // --- PASS 2: REFERENCE LINKING ---
3315
- logger.info(`[Pack Install] Starting Pass 2: Linking ${linkQueue.length} references`);
3330
+ logger?.info(`[Pack Install] Starting Pass 2: Linking ${linkQueue.length} references`);
3316
3331
  for (const linkOp of linkQueue) {
3317
3332
  const {sourceTempId, sourceModelName, fieldName, linkSelector} = linkOp;
3318
3333
  const sourceId = tempIdToNewIdMap[sourceTempId];
3319
3334
 
3320
3335
  if (!sourceId) {
3321
- logger.warn(`[LINK FAILED] Could not find newly inserted document for temp ID ${sourceTempId}. Skipping link.`);
3336
+ logger?.warn(`[LINK FAILED] Could not find newly inserted document for temp ID ${sourceTempId}. Skipping link.`);
3322
3337
  continue;
3323
3338
  }
3324
3339
 
@@ -3330,7 +3345,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3330
3345
  const fieldDef = sourceModelDef.fields.find(f => f.name === fieldName);
3331
3346
 
3332
3347
  if (!fieldDef) {
3333
- logger.warn(`[LINK FAILED] Field '${fieldName}' not found in source model '${sourceModelName}'`);
3348
+ logger?.warn(`[LINK FAILED] Field '${fieldName}' not found in source model '${sourceModelName}'`);
3334
3349
  errors.push(`[LINK FAILED] Field '${fieldName}' not found in source model '${sourceModelName}'`);
3335
3350
  summary.datas.failed++;
3336
3351
  continue;
@@ -3344,7 +3359,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3344
3359
 
3345
3360
  if (!targetDocs || targetDocs.length === 0) {
3346
3361
  const errorMsg = `[LINK FAILED] No target found for ${JSON.stringify(linkSelector)}`;
3347
- logger.warn(errorMsg);
3362
+ logger?.warn(errorMsg);
3348
3363
  errors.push(errorMsg);
3349
3364
  summary.datas.failed++;
3350
3365
  continue;
@@ -3363,7 +3378,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3363
3378
 
3364
3379
  } catch (e) {
3365
3380
  const errorMsg = `[LINK CRITICAL] Error linking ${sourceModelName}.${fieldName}: ${e.message}`;
3366
- logger.error(errorMsg, e.stack);
3381
+ logger?.error(errorMsg, e.stack);
3367
3382
  errors.push(errorMsg);
3368
3383
  summary.datas.failed++;
3369
3384
  }
@@ -3372,7 +3387,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3372
3387
  if (options.installForUser && user?.username) {
3373
3388
  if (pack.name)
3374
3389
  await packsCollection.deleteOne({name: pack.name, _user: user.username});
3375
- logger.info(`--- Creating pack '${pack.name}' for user... ---`);
3390
+ logger?.info(`--- Creating pack '${pack.name}' for user... ---`);
3376
3391
  const packToCreate = {...pack, _id: undefined, private: true, _user: user.username};
3377
3392
  await packsCollection.insertOne(packToCreate);
3378
3393
  }
@@ -3383,7 +3398,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', opti
3383
3398
  }
3384
3399
 
3385
3400
  const modifiedCount = summary.datas.inserted + summary.datas.updated;
3386
- logger.info(`--- ${logPrefix} completed ---`);
3401
+ logger?.info(`--- ${logPrefix} completed ---`);
3387
3402
  return {
3388
3403
  success: errors.length === 0,
3389
3404
  summary,
@@ -581,7 +581,8 @@ export const checkHash = async (me, model, hash, excludeId = null) => {
581
581
  const query = {
582
582
  _model: model.name,
583
583
  _hash: hash,
584
- ...(excludeId && {_id: {$ne: new ObjectId(excludeId)}})
584
+ ...(excludeId && {_id: {$ne: new ObjectId(excludeId)}}),
585
+ _user: me.username
585
586
  };
586
587
 
587
588
  console.log("Query being executed:", JSON.stringify(query, null, 2));
@@ -21,7 +21,7 @@ export function onInit(defaultEngine) {
21
21
 
22
22
  export const cancelAlerts = async (user) => {
23
23
 
24
- const datasCollection = getCollection('datas'); // Alerts are in the global collection
24
+ const datasCollection = getCollection(Config.Get('dataCollection', 'datas')); // Alerts are in the global collection
25
25
 
26
26
  // 1. Fetch the latest state of the alert
27
27
  const alertDocs = await datasCollection.find({_user: user.username, _model: 'alert'}).toArray();
@@ -3,31 +3,36 @@ import {Logger} from "../gameObject.js";
3
3
  import {MongoDatabase} from "../engine.js";
4
4
  import {ObjectId} from "mongodb";
5
5
  import {isLocalUser} from "../data.js";
6
+ import {Event} from "../events.js";
7
+ import {Config} from "../config.js";
6
8
 
7
9
  export let modelsCollection, datasCollection, filesCollection, packsCollection;
8
10
 
9
11
  export {ObjectId};
10
12
 
11
- let engine, logger;
13
+ let engine, logger, currentDb
14
+
12
15
  let colls= [];
13
16
  export async function onInit(defaultEngine) {
14
17
  engine = defaultEngine;
15
18
  logger = engine.getComponent(Logger);
16
19
 
20
+ currentDb = MongoDatabase();
17
21
  modelsCollection = getCollection("models");
18
- datasCollection = getCollection("datas");
22
+ datasCollection = getCollection(Config.Get('dataCollection', 'datas'));
19
23
  filesCollection = getCollection("files");
20
24
  packsCollection = getCollection("packs");
21
25
 
22
- colls = await MongoDatabase.listCollections().toArray();
26
+ colls = await currentDb.listCollections().toArray();
23
27
 
28
+ await Event.Trigger("OnDatabaseLoaded", "system", "calls", engine)
24
29
  logger.info("MongoDB collections loaded.");
25
30
  }
26
31
 
27
32
  export const getCollections= async (forceRefresh)=>{
28
33
  if( !forceRefresh )
29
34
  return colls;
30
- colls = await MongoDatabase.listCollections().toArray();
35
+ colls = await currentDb.listCollections().toArray();
31
36
  }
32
37
 
33
38
  export const createCollection = async (coll)=>{
@@ -35,7 +40,7 @@ export const createCollection = async (coll)=>{
35
40
  if( found){
36
41
  return getCollection(coll);
37
42
  }
38
- return await MongoDatabase.createCollection(coll);
43
+ return await currentDb.createCollection(coll);
39
44
  }
40
45
 
41
46
  export const isObjectId = (id) => {
@@ -44,15 +49,18 @@ export const isObjectId = (id) => {
44
49
 
45
50
 
46
51
  export const getCollection = (str) => {
47
- return MongoDatabase.collection(str);
52
+ return currentDb.collection(str);
48
53
  }
49
54
 
50
-
55
+ export const getDatabase = () => {
56
+ return currentDb;
57
+ }
51
58
 
52
59
  // New function to determine the collection name for a user
53
60
  export const getUserCollectionName = async (user) => {
54
61
  const feat = await engine.userProvider.hasFeature(user, 'indexes');
55
- return feat ? (isLocalUser(user) ? `datas_${user._user}` :`datas_${user.username}` ) : 'datas';
62
+ const dataCollectionName = Config.Get('dataCollection', 'datas');
63
+ return feat ? (isLocalUser(user) ? `${dataCollectionName}_${user._user}` :`${dataCollectionName}_${user.username}` ) : dataCollectionName;
56
64
  };
57
65
 
58
66
 
@@ -1,5 +1,4 @@
1
1
  import i18n from "../i18n.js";
2
- import {MongoDatabase} from "../engine.js";
3
2
  import {
4
3
  createCollection,
5
4
  getCollection,