data-primals-engine 1.0.7 → 1.0.9
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/.github/workflows/node.js.yml +59 -0
- package/.github/workflows/npm-publish.yml +23 -0
- package/Dockerfile +15 -0
- package/DockerfileTest +15 -0
- package/package.json +4 -7
- package/server.js +11 -2
- package/src/data.js +15 -5
- package/src/email.js +2 -0
- package/src/engine.js +42 -40
- package/src/gameObject.js +5 -7
- package/src/migrate.js +1 -1
- package/src/modules/data.js +87 -61
- package/src/modules/mongodb.js +1 -8
- package/src/modules/workflow.js +190 -58
- package/src/packs.js +26 -10
- package/src/setenv.js +39 -0
- package/test/data.backup.integration.test.js +141 -0
- package/test/data.integration.test.js +796 -0
- package/test/globalSetup.js +15 -0
- package/test/globalTeardown.js +8 -0
- package/test/import_export.integration.test.js +200 -0
- package/test/workflow.integration.test.js +314 -0
- package/test/workflow.robustness.test.js +195 -0
- package/vitest.config.js +16 -0
package/src/modules/data.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
encryptValue,
|
|
17
17
|
getDefaultForType,
|
|
18
18
|
getFieldValueHash,
|
|
19
|
-
getUserId,
|
|
19
|
+
getUserId, isDemoUser,
|
|
20
20
|
isLocalUser
|
|
21
21
|
} from "../data.js";
|
|
22
22
|
import {
|
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
import {assistantGlobalLimiter} from "./assistant.js";
|
|
89
89
|
import {getAllPacks} from "../packs.js";
|
|
90
90
|
import {throttleMiddleware} from "../middlewares/throttle.js";
|
|
91
|
+
import {Config} from "../config.js";
|
|
91
92
|
|
|
92
93
|
// Obtenir le chemin du répertoire courant de manière fiable avec ES Modules
|
|
93
94
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -100,7 +101,7 @@ const sseConnections = new Map();
|
|
|
100
101
|
|
|
101
102
|
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
102
103
|
|
|
103
|
-
const
|
|
104
|
+
const getBackupDir = () => process.env.BACKUP_DIR || './backups'; // Répertoire de stockage des sauvegardes
|
|
104
105
|
const execAsync = promisify(exec);
|
|
105
106
|
|
|
106
107
|
let importJobs = {};
|
|
@@ -147,7 +148,7 @@ export const jobDumpUserData = async () => {
|
|
|
147
148
|
|
|
148
149
|
users.forEach((user) =>
|
|
149
150
|
{
|
|
150
|
-
if(
|
|
151
|
+
if( isDemoUser(user) && Config.Get("useDemoAccounts"))
|
|
151
152
|
return;
|
|
152
153
|
try {
|
|
153
154
|
dumpUserData(user).catch(e => {
|
|
@@ -383,7 +384,7 @@ export const dataTypes = {
|
|
|
383
384
|
}
|
|
384
385
|
},
|
|
385
386
|
boolean: {
|
|
386
|
-
validate: (value) => typeof value === 'boolean',
|
|
387
|
+
validate: (value) => value === null || typeof value === 'boolean',
|
|
387
388
|
anonymize: () => {
|
|
388
389
|
return !!getRandom(0, 1);
|
|
389
390
|
}
|
|
@@ -1459,7 +1460,7 @@ export async function onInit(defaultEngine) {
|
|
|
1459
1460
|
|
|
1460
1461
|
engine.put('/api/model/:id', [middlewareAuthenticator, userInitiator, setTimeoutMiddleware(15000)], async (req, res) => {
|
|
1461
1462
|
|
|
1462
|
-
if(
|
|
1463
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_EDIT_MODEL"], req.me)){
|
|
1463
1464
|
return res.status(403).json({success: false, error: i18n.t('api.permission.editModel', 'Cannot edit models from the API')})
|
|
1464
1465
|
}
|
|
1465
1466
|
|
|
@@ -1698,7 +1699,7 @@ export async function onInit(defaultEngine) {
|
|
|
1698
1699
|
|
|
1699
1700
|
engine.get('/api/models', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
|
|
1700
1701
|
|
|
1701
|
-
if(
|
|
1702
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_GET_MODELS"], req.me)){
|
|
1702
1703
|
return res.status(403).json({success: false, error: i18n.t('api.permission.getModels')})
|
|
1703
1704
|
}
|
|
1704
1705
|
|
|
@@ -1726,7 +1727,7 @@ export async function onInit(defaultEngine) {
|
|
|
1726
1727
|
return res.status(400).json({error: "Le paramètre 'name' est requis."});
|
|
1727
1728
|
}
|
|
1728
1729
|
|
|
1729
|
-
if(
|
|
1730
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_GET_MODEL"], req.me) && !await hasPermission("API_GET_MODEL_"+modelName, req.me)){
|
|
1730
1731
|
return res.json({success: false, error: i18n.t('api.permission.getModel')})
|
|
1731
1732
|
}
|
|
1732
1733
|
|
|
@@ -1738,7 +1739,7 @@ export async function onInit(defaultEngine) {
|
|
|
1738
1739
|
});
|
|
1739
1740
|
engine.post('/api/model', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter], async (req, res) => {
|
|
1740
1741
|
|
|
1741
|
-
if(
|
|
1742
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_ADD_MODEL"], req.me) ){
|
|
1742
1743
|
return res.status(403).json({success: false, error: i18n.t('api.permission.addModel')})
|
|
1743
1744
|
}
|
|
1744
1745
|
try {
|
|
@@ -1810,7 +1811,7 @@ export async function onInit(defaultEngine) {
|
|
|
1810
1811
|
})));
|
|
1811
1812
|
}
|
|
1812
1813
|
const install = !!req.fields.install;
|
|
1813
|
-
if( install &&
|
|
1814
|
+
if( install && (isDemoUser(req.me) && Config.Get("useDemoAccounts")) ){
|
|
1814
1815
|
|
|
1815
1816
|
await datasCollection.deleteMany({ _user: req.me.username});
|
|
1816
1817
|
await modelsCollection.deleteMany({ _user: req.me.username});
|
|
@@ -1846,7 +1847,7 @@ export async function onInit(defaultEngine) {
|
|
|
1846
1847
|
return res.status(400).json({error: "Le paramètre 'name' est requis."});
|
|
1847
1848
|
}
|
|
1848
1849
|
|
|
1849
|
-
if(
|
|
1850
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && (
|
|
1850
1851
|
!await hasPermission(["API_ADMIN","API_DELETE_MODEL","API_DELETE_MODEL_"+modelName], req.me) ||
|
|
1851
1852
|
await hasPermission(["API_DELETE_MODEL_NOT_"+modelName], req.me))){
|
|
1852
1853
|
return res.status(403).json({success: false, error: i18n.t( "api.permission.deleteModel", { model: modelName})})
|
|
@@ -1910,7 +1911,7 @@ export async function onInit(defaultEngine) {
|
|
|
1910
1911
|
return res.status(404).json({ error: i18n.t('api.model.notFound', { model: modelId }) });
|
|
1911
1912
|
}
|
|
1912
1913
|
|
|
1913
|
-
if(
|
|
1914
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && (
|
|
1914
1915
|
!await hasPermission(["API_ADMIN", "API_EDIT_MODEL", "API_EDIT_MODEL_"+model.name], req.me) ||
|
|
1915
1916
|
await hasPermission(["API_EDIT_MODEL_NOT_"+model.name], req.me))){
|
|
1916
1917
|
return res.status(403).json({success: false, error: i18n.t('api.permission.editModel')})
|
|
@@ -2323,7 +2324,7 @@ export async function onInit(defaultEngine) {
|
|
|
2323
2324
|
|
|
2324
2325
|
|
|
2325
2326
|
engine.post('/api/data/removeFromPack', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
|
|
2326
|
-
if(
|
|
2327
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_CREATE_PACK"], req.me)){
|
|
2327
2328
|
return res.status(403).json({success: false, error: i18n.t('api.permission.createPack')})
|
|
2328
2329
|
}
|
|
2329
2330
|
const { itemIds } = req.fields;
|
|
@@ -2459,7 +2460,7 @@ export async function onInit(defaultEngine) {
|
|
|
2459
2460
|
const { packName, itemIds } = req.fields;
|
|
2460
2461
|
const user = req.me;
|
|
2461
2462
|
|
|
2462
|
-
if(
|
|
2463
|
+
if( !(isDemoUser(req.me) && Config.Get("useDemoAccounts")) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_CREATE_PACK"], req.me)){
|
|
2463
2464
|
return res.status(403).json({success: false, error: i18n.t('api.permission.createPack')})
|
|
2464
2465
|
}
|
|
2465
2466
|
// --- Validation ---
|
|
@@ -2591,14 +2592,18 @@ export async function onInit(defaultEngine) {
|
|
|
2591
2592
|
}
|
|
2592
2593
|
|
|
2593
2594
|
export const createModel = async (data) => {
|
|
2594
|
-
return await
|
|
2595
|
+
return await getCollection('models').insertOne(data);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
export const deleteModels = async (filter) => {
|
|
2599
|
+
return await getCollection('models').deleteMany(filter ? filter : {_user: { $exists: false }});
|
|
2595
2600
|
}
|
|
2596
2601
|
|
|
2597
2602
|
export const getModel = async (modelName, user) => {
|
|
2598
2603
|
const modelInCache = modelsCache.get(user.username+"@@"+modelName);
|
|
2599
2604
|
if(modelInCache)
|
|
2600
2605
|
return modelInCache;
|
|
2601
|
-
const model = await
|
|
2606
|
+
const model = await getCollection('models').findOne({name: modelName, $and: [{_user: {$exists: true}}, {$or: [{_user: user._user}, {_user: user.username}]}]});
|
|
2602
2607
|
if (!model) {
|
|
2603
2608
|
throw new Error(i18n.t('api.model.notFound', {model: modelName}));
|
|
2604
2609
|
}
|
|
@@ -2606,7 +2611,7 @@ export const getModel = async (modelName, user) => {
|
|
|
2606
2611
|
return model;
|
|
2607
2612
|
}
|
|
2608
2613
|
export const getModels = async () => {
|
|
2609
|
-
return await
|
|
2614
|
+
return await getCollection('models').find({'$or': [{_user: { $exists: false}}]}).toArray();
|
|
2610
2615
|
}
|
|
2611
2616
|
|
|
2612
2617
|
|
|
@@ -2722,7 +2727,7 @@ export async function checkServerCapacity(incomingDataSize = 0) {
|
|
|
2722
2727
|
export const insertData = async (modelName, data, files, user, triggerWorkflow = true, waitForWorkflow = true) => {
|
|
2723
2728
|
|
|
2724
2729
|
// --- Vérification des permissions (inchangée) ---
|
|
2725
|
-
if (
|
|
2730
|
+
if (!(isDemoUser(user) && Config.Get("useDemoAccounts")) && isLocalUser(user) && (
|
|
2726
2731
|
!await hasPermission(["API_ADMIN", "API_ADD_DATA", "API_ADD_DATA_" + modelName], user) ||
|
|
2727
2732
|
await hasPermission(["API_ADD_DATA_NOT_" + modelName], user))) {
|
|
2728
2733
|
// Renvoyer une structure d'erreur cohérente
|
|
@@ -2998,10 +3003,14 @@ async function processRelations(docToProcess, model, collection, me, idMap) {
|
|
|
2998
3003
|
// Phase 3: Traitement des résultats
|
|
2999
3004
|
findResults.forEach((result, index) => {
|
|
3000
3005
|
const { field, multiple } = batchFinds[index];
|
|
3001
|
-
if (result.data?.length) {
|
|
3006
|
+
if (result.data?.length > 0) {
|
|
3007
|
+
// Cas où des documents sont trouvés
|
|
3002
3008
|
docToProcess[field] = multiple
|
|
3003
3009
|
? result.data.map(r => r._id.toString())
|
|
3004
3010
|
: result.data[0]._id.toString();
|
|
3011
|
+
} else {
|
|
3012
|
+
// Cas où AUCUN document n'est trouvé : il faut nettoyer le champ !
|
|
3013
|
+
docToProcess[field] = multiple ? [] : null;
|
|
3005
3014
|
}
|
|
3006
3015
|
});
|
|
3007
3016
|
|
|
@@ -3161,28 +3170,29 @@ async function applyFieldFilters(docToProcess, model) {
|
|
|
3161
3170
|
/**
|
|
3162
3171
|
* Valide la structure et le contenu du document selon le modèle
|
|
3163
3172
|
*/
|
|
3164
|
-
function validateModelData(doc, model) {
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3173
|
+
function validateModelData(doc, model, isPatch = false) {
|
|
3174
|
+
if (!isPatch) {
|
|
3175
|
+
model.fields.forEach(field => {
|
|
3176
|
+
const value = doc[field.name];
|
|
3177
|
+
if (field.required) {
|
|
3178
|
+
if (value === undefined && !('default' in field)) {
|
|
3179
|
+
throw new Error(i18n.t('api.field.missingRequired', { field: field.name + " (" + model.name + ")" }));
|
|
3180
|
+
}
|
|
3181
|
+
if (value === '' || value === null) {
|
|
3182
|
+
throw new Error(i18n.t('api.field.requiredCannotBeEmpty', { field: field.name }));
|
|
3183
|
+
}
|
|
3174
3184
|
}
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3177
3187
|
|
|
3178
|
-
// 2. Validation des types de champs
|
|
3188
|
+
// 2. Validation des types de champs (toujours exécutée pour les champs fournis)
|
|
3179
3189
|
for (const [fieldName, value] of Object.entries(doc)) {
|
|
3180
3190
|
const fieldDef = model.fields.find(f => f.name === fieldName);
|
|
3181
3191
|
if (!fieldDef) continue; // On ignore les champs supplémentaires
|
|
3182
3192
|
|
|
3183
3193
|
const validator = dataTypes[fieldDef.type]?.validate;
|
|
3184
3194
|
if (validator && !validator(value, fieldDef)) {
|
|
3185
|
-
throw new Error(i18n.t('api.field.validationFailed', { field: fieldName, value}));
|
|
3195
|
+
throw new Error(i18n.t('api.field.validationFailed', { field: fieldName, value }));
|
|
3186
3196
|
}
|
|
3187
3197
|
}
|
|
3188
3198
|
}
|
|
@@ -3358,9 +3368,11 @@ export const editData = async (modelName, filter, data, files, user, triggerWork
|
|
|
3358
3368
|
return await internalEditOrPatchData(modelName, filter, data, files, user, false, triggerWorkflow, waitForWorkflow);
|
|
3359
3369
|
};
|
|
3360
3370
|
|
|
3371
|
+
// Dans src/modules/data.js
|
|
3372
|
+
|
|
3361
3373
|
const internalEditOrPatchData = async (modelName, filter, data, files, user, isPatch, triggerWorkflow = true, waitForWorkflow = false) => {
|
|
3362
3374
|
try {
|
|
3363
|
-
// Vérification des permissions
|
|
3375
|
+
// 1. Vérification des permissions
|
|
3364
3376
|
if (user.username !== 'demo' && isLocalUser(user) && (
|
|
3365
3377
|
!await hasPermission(["API_ADMIN", "API_EDIT_DATA", "API_EDIT_DATA_" + modelName], user) ||
|
|
3366
3378
|
await hasPermission(["API_EDIT_DATA_NOT_" + modelName], user))) {
|
|
@@ -3373,22 +3385,20 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
|
|
|
3373
3385
|
throw new Error(i18n.t("api.model.notFound", {model: modelName}));
|
|
3374
3386
|
}
|
|
3375
3387
|
|
|
3376
|
-
// Récupération des documents existants
|
|
3388
|
+
// 2. Récupération des documents existants et de leur hash original
|
|
3377
3389
|
const existingDocs = (await searchData({user, query: {model: modelName, filter}}))?.data;
|
|
3378
3390
|
if (!existingDocs || existingDocs.length === 0) {
|
|
3379
3391
|
return {success: false, error: i18n.t("api.data.notFound")};
|
|
3380
3392
|
}
|
|
3381
|
-
|
|
3382
3393
|
const ids = existingDocs.map(d => new ObjectId(d._id));
|
|
3394
|
+
const originalHash = existingDocs[0]._hash; // Sauvegarde du hash avant modification
|
|
3383
3395
|
|
|
3384
|
-
// Préparation des données de mise à jour
|
|
3396
|
+
// 3. Préparation des données de mise à jour (inchangé)
|
|
3385
3397
|
const updateData = {...data};
|
|
3386
3398
|
delete updateData._model;
|
|
3387
3399
|
delete updateData._user;
|
|
3388
3400
|
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
// Traitement des fichiers
|
|
3401
|
+
// Traitement des fichiers (inchangé)
|
|
3392
3402
|
const fileFields = model.fields.filter(f => f.type === 'file' || (f.type === 'array' && f.itemsType === 'file'));
|
|
3393
3403
|
for (const field of fileFields) {
|
|
3394
3404
|
if (files?.[field.name+'[0]']) {
|
|
@@ -3402,12 +3412,15 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
|
|
|
3402
3412
|
}
|
|
3403
3413
|
}
|
|
3404
3414
|
|
|
3405
|
-
// Validation
|
|
3415
|
+
// 4. Validation adaptée pour patch ou edit (inchangé)
|
|
3406
3416
|
if (!isPatch) {
|
|
3407
|
-
|
|
3417
|
+
const dataToValidate = { ...existingDocs[0], ...updateData };
|
|
3418
|
+
validateModelData(dataToValidate, model, false);
|
|
3419
|
+
} else {
|
|
3420
|
+
validateModelData(updateData, model, true);
|
|
3408
3421
|
}
|
|
3409
3422
|
|
|
3410
|
-
// Vérification des champs uniques
|
|
3423
|
+
// 5. Vérification des champs uniques (inchangé)
|
|
3411
3424
|
const uniqueFields = model.fields.filter(f => f.unique);
|
|
3412
3425
|
for (const field of uniqueFields) {
|
|
3413
3426
|
if (updateData[field.name] !== undefined) {
|
|
@@ -3415,31 +3428,27 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
|
|
|
3415
3428
|
_user: user._user || user.username,
|
|
3416
3429
|
_model: modelName,
|
|
3417
3430
|
[field.name]: updateData[field.name],
|
|
3418
|
-
_id: {$nin: ids}
|
|
3431
|
+
_id: {$nin: ids}
|
|
3419
3432
|
});
|
|
3420
|
-
|
|
3421
3433
|
if (existing) {
|
|
3422
|
-
throw new Error(i18n.t("api.data.duplicateValue", {
|
|
3423
|
-
field: field.name,
|
|
3424
|
-
value: updateData[field.name]
|
|
3425
|
-
}));
|
|
3434
|
+
throw new Error(i18n.t("api.data.duplicateValue", { field: field.name, value: updateData[field.name] }));
|
|
3426
3435
|
}
|
|
3427
3436
|
}
|
|
3428
3437
|
}
|
|
3438
|
+
|
|
3439
|
+
// 6. Traitement des relations (inchangé)
|
|
3429
3440
|
const relationFields = model.fields.filter(f => f.type === 'relation');
|
|
3430
3441
|
for (const field of relationFields) {
|
|
3431
3442
|
if (updateData[field.name] !== undefined) {
|
|
3432
3443
|
const relationValue = updateData[field.name];
|
|
3433
3444
|
if (relationValue !== null && typeof relationValue === 'object') {
|
|
3434
|
-
// Ajouter l'option preserveIds: true pour conserver les IDs
|
|
3435
3445
|
const insertedIds = await pushDataUnsecure(relationValue, field.relation, user, { preserveIds: true });
|
|
3436
3446
|
updateData[field.name] = field.multiple ? insertedIds || [] : insertedIds?.[0] || null;
|
|
3437
3447
|
}
|
|
3438
3448
|
}
|
|
3439
3449
|
}
|
|
3440
3450
|
|
|
3441
|
-
|
|
3442
|
-
// Gestion des mots de passe et autres filtres
|
|
3451
|
+
// 7. Application des filtres de champ (ex: hashage de mot de passe) (inchangé)
|
|
3443
3452
|
for (const field of model.fields) {
|
|
3444
3453
|
if (updateData[field.name] !== undefined && dataTypes[field.type]?.filter) {
|
|
3445
3454
|
updateData[field.name] = await dataTypes[field.type].filter(
|
|
@@ -3449,23 +3458,31 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
|
|
|
3449
3458
|
}
|
|
3450
3459
|
}
|
|
3451
3460
|
|
|
3452
|
-
|
|
3461
|
+
// 8. Calcul du nouveau hash et préparation des données finales
|
|
3462
|
+
const finalStateForHash = { ...existingDocs[0], ...updateData };
|
|
3463
|
+
const newHash = getFieldValueHash(model, finalStateForHash);
|
|
3453
3464
|
|
|
3454
|
-
// On ne met à jour que les champs fournis + le nouveau hash
|
|
3455
3465
|
const finalDataForSet = {
|
|
3456
3466
|
...updateData,
|
|
3457
|
-
_hash:
|
|
3467
|
+
_hash: newHash
|
|
3458
3468
|
};
|
|
3459
3469
|
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3470
|
+
// 9. *** CORRECTION LOGIQUE ***
|
|
3471
|
+
// On ne vérifie l'unicité que si le hash a réellement changé.
|
|
3472
|
+
if (newHash !== originalHash) {
|
|
3473
|
+
const hashCheck = await checkHash(user, model, newHash, existingDocs[0]._id.toString());
|
|
3474
|
+
if (hashCheck) {
|
|
3475
|
+
// Le nouvel état du document créerait un doublon.
|
|
3476
|
+
throw new Error(i18n.t("api.data.notUniqueData"));
|
|
3477
|
+
}
|
|
3463
3478
|
}
|
|
3464
3479
|
|
|
3480
|
+
// 10. Exécution de la mise à jour (inchangé)
|
|
3465
3481
|
const bulkOps = [{ updateMany: { filter: {_id: {$in: ids}}, update: {$set: finalDataForSet} } }];
|
|
3466
3482
|
const bulkResult = await collection.bulkWrite(bulkOps);
|
|
3467
3483
|
const modifiedCount = bulkResult.modifiedCount || 0;
|
|
3468
3484
|
|
|
3485
|
+
// 11. Tâches post-mise à jour (schedules, workflows) (inchangé)
|
|
3469
3486
|
if (["workflowTrigger", "alert"].includes(modelName)) {
|
|
3470
3487
|
await handleScheduledJobs(modelName, existingDocs, collection, finalDataForSet);
|
|
3471
3488
|
}
|
|
@@ -4231,8 +4248,10 @@ export const searchData = async ({user, query}) => {
|
|
|
4231
4248
|
pipelines.push({$project: {_user: 0}});
|
|
4232
4249
|
pipelines.push({$project: {_model: 0}});
|
|
4233
4250
|
}
|
|
4234
|
-
console.log(util.inspect(pipelines, false, 29, true))
|
|
4235
4251
|
|
|
4252
|
+
console.log(util.inspect(pipelines, false, 29, true));
|
|
4253
|
+
|
|
4254
|
+
// 4. Exécuter la pipeline
|
|
4236
4255
|
const ts = parseInt(timeout, 10)/2.0 || searchRequestTimeout;
|
|
4237
4256
|
const count = await collection.aggregate([...pipelines, { $count: "count" }]).maxTimeMS(ts).toArray();
|
|
4238
4257
|
let prom = collection.aggregate(pipelines).maxTimeMS(ts);
|
|
@@ -4249,7 +4268,7 @@ export const searchData = async ({user, query}) => {
|
|
|
4249
4268
|
|
|
4250
4269
|
export const importData = async(options, files, user) => {
|
|
4251
4270
|
|
|
4252
|
-
if(
|
|
4271
|
+
if( !(isDemoUser(user) && Config.Get("useDemoAccounts")) && isLocalUser(user) && !await hasPermission(["API_ADMIN", "API_IMPORT_DATA"], user)){
|
|
4253
4272
|
return ({ success: false, error: "API_IMPORT_DATA permission needed." });
|
|
4254
4273
|
}
|
|
4255
4274
|
|
|
@@ -4602,7 +4621,7 @@ export const exportData= async (options, user) =>{
|
|
|
4602
4621
|
// Example using hasPermission:
|
|
4603
4622
|
// if (user.username !== 'demo' && isLocalUser(user) && !await hasPermission(["API_ADMIN", "API_SEARCH_DATA", "API_SEARCH_DATA_"+modelName], user)) {
|
|
4604
4623
|
// Example using checkPermission (if it exists and works similarly):
|
|
4605
|
-
if (isLocalUser(user) &&
|
|
4624
|
+
if (isLocalUser(user) && !(isDemoUser(user) && Config.Get("useDemoAccounts")) && !(await hasPermission('API_EXPORT_DATA', user))) { // Adapt this line based on your actual permission function
|
|
4606
4625
|
console.warn(`User ${userId} lacks permission to search/export model ${modelName}`);
|
|
4607
4626
|
errors.push(i18n.t('api.permission.searchData', 'Cannot search data from the API') + ` (${modelName})`);
|
|
4608
4627
|
continue; // Skip this model
|
|
@@ -4925,6 +4944,7 @@ export const loadFromDump = async (user, options = {}) => {
|
|
|
4925
4944
|
// ...
|
|
4926
4945
|
let backupFilePath; // Assurez-vous que cette variable est bien définie avec le chemin du fichier .tar.gz
|
|
4927
4946
|
// Exemple simplifié :
|
|
4947
|
+
const backupDir = getBackupDir();
|
|
4928
4948
|
const backupFilenameRegex = new RegExp(`^backup_${user.username}_(\\d+)\\.tar\\.gz$`);
|
|
4929
4949
|
const backupFiles = fs.readdirSync(backupDir).filter(filename => backupFilenameRegex.test(filename));
|
|
4930
4950
|
if (backupFiles.length === 0) throw new Error(`Aucun fichier de sauvegarde local trouvé pour l'utilisateur ${user.username}.`);
|
|
@@ -5001,6 +5021,7 @@ export const loadFromDump = async (user, options = {}) => {
|
|
|
5001
5021
|
|
|
5002
5022
|
// Fonction pour générer une clé aléatoire et la stocker dans un fichier
|
|
5003
5023
|
const generateAndStoreKey = (user) => {
|
|
5024
|
+
const backupDir = getBackupDir();
|
|
5004
5025
|
const keyFile = path.join(backupDir, getObjectHash({id:getUserId(user)})+'_encryption.key');
|
|
5005
5026
|
const key = crypto.randomBytes(16).toString('hex');
|
|
5006
5027
|
fs.writeFileSync(keyFile, key, { mode: 0o600 }); // Permissions strictes
|
|
@@ -5009,6 +5030,7 @@ const generateAndStoreKey = (user) => {
|
|
|
5009
5030
|
|
|
5010
5031
|
// Fonction pour lire la clé depuis le fichier
|
|
5011
5032
|
const readKeyFromFile = (user) => {
|
|
5033
|
+
const backupDir = getBackupDir();
|
|
5012
5034
|
const keyFile = path.join(backupDir, getObjectHash({id:getUserId(user)})+'_encryption.key');
|
|
5013
5035
|
if (fs.existsSync(keyFile)) {
|
|
5014
5036
|
return fs.readFileSync(keyFile, 'utf8');
|
|
@@ -5020,6 +5042,7 @@ export const dumpUserData = async (user) => {
|
|
|
5020
5042
|
// Déterminer la clé de chiffrement
|
|
5021
5043
|
// Pour cet exemple, on simule la config S3. Remplace par la vraie récupération.
|
|
5022
5044
|
const s3Config = user.configS3; // Supposons que l'objet 'user' passé contient déjà 'configS3'
|
|
5045
|
+
const backupDir = getBackupDir();
|
|
5023
5046
|
|
|
5024
5047
|
let encryptedKey = readKeyFromFile(user);
|
|
5025
5048
|
if (!encryptedKey) {
|
|
@@ -5065,7 +5088,7 @@ export const dumpUserData = async (user) => {
|
|
|
5065
5088
|
let col;
|
|
5066
5089
|
for (const collection of collections) {
|
|
5067
5090
|
|
|
5068
|
-
const colls = [
|
|
5091
|
+
const colls = [getUserCollectionName(user), 'models'];
|
|
5069
5092
|
if( colls.includes(collection.name) ){
|
|
5070
5093
|
|
|
5071
5094
|
// Exécuter mongodump avec les filtres appropriés
|
|
@@ -5151,6 +5174,7 @@ async function manageBackupRotation(user, backupFrequency, s3Config = null) { //
|
|
|
5151
5174
|
|
|
5152
5175
|
} else {
|
|
5153
5176
|
logger.info(`Gestion de la rotation des sauvegardes locales pour ${userId}.`);
|
|
5177
|
+
const backupDir = getBackupDir();
|
|
5154
5178
|
const localFiles = fs.readdirSync(backupDir);
|
|
5155
5179
|
filesToManage = localFiles
|
|
5156
5180
|
.filter(f => !fs.lstatSync(path.join(backupDir, f)).isDirectory() && f.startsWith(`backup_${userId}_`) && f.endsWith('.tar.gz'))
|
|
@@ -5384,6 +5408,8 @@ export async function installPack(logger, packId, user, lang) {
|
|
|
5384
5408
|
if (documents.length === 0) continue;
|
|
5385
5409
|
|
|
5386
5410
|
const docsToInsert = [];
|
|
5411
|
+
console.log(modelName, user);
|
|
5412
|
+
|
|
5387
5413
|
const modelDefForHash = await getModel(modelName, user);
|
|
5388
5414
|
|
|
5389
5415
|
for (const docSource of documents) {
|
package/src/modules/mongodb.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import process from "process";
|
|
3
3
|
import {MongoClient as InternalMongoClient} from "mongodb";
|
|
4
4
|
import {Logger} from "../gameObject.js";
|
|
5
|
-
import {MongoDatabase} from "../engine.js";
|
|
5
|
+
import {MongoClient, MongoDatabase} from "../engine.js";
|
|
6
6
|
import * as tls from "node:tls";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
|
|
@@ -23,13 +23,6 @@ export async function onInit(defaultEngine) {
|
|
|
23
23
|
} catch (e) {
|
|
24
24
|
|
|
25
25
|
}
|
|
26
|
-
// Create a SecureContext object
|
|
27
|
-
// Connection URL
|
|
28
|
-
const dbUrl = process.env.MONGO_DB_URL || 'mongodb://localhost:27017';
|
|
29
|
-
const MongoClient = new InternalMongoClient(dbUrl, {
|
|
30
|
-
tls: false, maxPoolSize: 20
|
|
31
|
-
});
|
|
32
|
-
await MongoClient.connect();
|
|
33
26
|
|
|
34
27
|
modelsCollection = MongoDatabase.collection("models");
|
|
35
28
|
datasCollection = MongoDatabase.collection("datas");
|