data-primals-engine 1.1.8 → 1.2.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/README.md +65 -22
- package/client/src/AssistantChat.scss +0 -1
- package/client/src/ChartConfigModal.jsx +0 -1
- package/client/src/ConditionBuilder.jsx +0 -2
- package/client/src/ContentView.jsx +0 -1
- package/client/src/CronBuilder.scss +0 -1
- package/client/src/CronPartBuilder.jsx +0 -1
- package/client/src/Dashboard.jsx +0 -1
- package/client/src/Dashboard.scss +0 -1
- package/client/src/DashboardChart.jsx +0 -1
- package/client/src/DashboardView.jsx +0 -1
- package/client/src/DataEditor.jsx +0 -4
- package/client/src/DisplayFlexNodeRenderer.jsx +0 -1
- package/client/src/DocumentationPageLayout.jsx +0 -2
- package/client/src/DocumentationPageLayout.scss +0 -1
- package/client/src/FlexNode.jsx +0 -1
- package/client/src/KanbanView.scss +0 -1
- package/client/src/RestoreDialog.jsx +0 -1
- package/client/src/TourSpotlight.jsx +0 -1
- package/client/src/hooks/useDragAndDrop.js +0 -1
- package/issues.txt +0 -0
- package/package.json +1 -1
- package/src/constants.js +6 -0
- package/src/data.js +0 -2
- package/src/migrate.js +0 -1
- package/src/modules/assistant.js +211 -133
- package/src/modules/data.js +88 -69
- package/src/modules/workflow.js +10 -12
- package/src/providers.js +1 -1
- package/src/workers/crypto-worker.js +0 -1
- package/test/data.integration.test.js +53 -70
- package/test/workflow.integration.test.js +1 -1
package/src/modules/data.js
CHANGED
|
@@ -10,7 +10,7 @@ import process from "node:process";
|
|
|
10
10
|
import {randomColor} from "randomcolor";
|
|
11
11
|
import cronstrue from 'cronstrue/i18n.js';
|
|
12
12
|
import { setTimeoutMiddleware } from '../middlewares/timeout.js';
|
|
13
|
-
|
|
13
|
+
import { mkdir } from 'node:fs/promises';
|
|
14
14
|
import {
|
|
15
15
|
anonymizeText,
|
|
16
16
|
encryptValue,
|
|
@@ -981,13 +981,10 @@ async function runStatefulAlertJob(alertId) {
|
|
|
981
981
|
// 3. Evaluate the trigger condition
|
|
982
982
|
const apiFilter = (alertDoc.triggerCondition);
|
|
983
983
|
const { count } = await searchData({
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
limit: 1
|
|
989
|
-
}
|
|
990
|
-
});
|
|
984
|
+
model: alertDoc.targetModel,
|
|
985
|
+
filter: apiFilter,
|
|
986
|
+
limit: 1
|
|
987
|
+
}, { username: alertDoc._user });
|
|
991
988
|
|
|
992
989
|
// 4. If condition is met, send notification and update state
|
|
993
990
|
if (count > 0) {
|
|
@@ -1233,17 +1230,14 @@ export async function handleCustomEndpointRequest(req, res) {
|
|
|
1233
1230
|
try {
|
|
1234
1231
|
// 1. Trouver l'endpoint correspondant dans la base de données
|
|
1235
1232
|
const endpointSearch = await searchData({
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
limit: 1
|
|
1245
|
-
}
|
|
1246
|
-
});
|
|
1233
|
+
model: 'endpoint',
|
|
1234
|
+
filter: {
|
|
1235
|
+
path: path,
|
|
1236
|
+
method: method,
|
|
1237
|
+
isActive: true
|
|
1238
|
+
},
|
|
1239
|
+
limit: 1
|
|
1240
|
+
}, user);
|
|
1247
1241
|
|
|
1248
1242
|
if (endpointSearch.count === 0) {
|
|
1249
1243
|
logger.warn(`[Endpoint] 404 - No active endpoint found for user '${user.username}', path '${path}', method '${method}'.`);
|
|
@@ -1382,6 +1376,9 @@ export async function onInit(defaultEngine) {
|
|
|
1382
1376
|
}
|
|
1383
1377
|
|
|
1384
1378
|
|
|
1379
|
+
// Create the uploads directories
|
|
1380
|
+
await mkdir(path.join("uploads", "tmp"),{ recursive: true});
|
|
1381
|
+
|
|
1385
1382
|
}else {
|
|
1386
1383
|
modelsCollection = getCollection("models");
|
|
1387
1384
|
datasCollection = getCollection("datas");
|
|
@@ -1396,7 +1393,7 @@ export async function onInit(defaultEngine) {
|
|
|
1396
1393
|
schedule.scheduleJob("0 0 * * *", async () => {
|
|
1397
1394
|
const dt = new Date();
|
|
1398
1395
|
dt.setTime(dt.getTime()-1000*3600*24*14);
|
|
1399
|
-
await deleteData("request",
|
|
1396
|
+
await deleteData("request", {"$lt": ["$timestamp",dt.toISOString()]}, null, false);
|
|
1400
1397
|
});
|
|
1401
1398
|
await scheduleWorkflowTriggers();
|
|
1402
1399
|
|
|
@@ -1527,14 +1524,11 @@ export async function onInit(defaultEngine) {
|
|
|
1527
1524
|
|
|
1528
1525
|
// On utilise la fonction de recherche interne de l'application
|
|
1529
1526
|
const searchResult = await searchData({
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
},
|
|
1536
|
-
user: user
|
|
1537
|
-
});
|
|
1527
|
+
model,
|
|
1528
|
+
filter: processedFilter,
|
|
1529
|
+
limit, // Optimisation : pas besoin de plus de résultats
|
|
1530
|
+
page: 1
|
|
1531
|
+
}, user);
|
|
1538
1532
|
|
|
1539
1533
|
// searchData devrait renvoyer un `count` total des documents correspondants
|
|
1540
1534
|
const isCompleted = searchResult.count >= limit;
|
|
@@ -1710,6 +1704,48 @@ export async function onInit(defaultEngine) {
|
|
|
1710
1704
|
});
|
|
1711
1705
|
|
|
1712
1706
|
|
|
1707
|
+
engine.post('/api/model/search', [middlewareAuthenticator, userInitiator], async (req, res) => {
|
|
1708
|
+
const { query } = req.fields;
|
|
1709
|
+
const user = req.me;
|
|
1710
|
+
|
|
1711
|
+
if (!query || typeof query !== 'string') {
|
|
1712
|
+
return res.status(400).json({ success: false, error: 'A search query string is required.' });
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
try {
|
|
1716
|
+
// Utilise une expression régulière pour une recherche insensible à la casse
|
|
1717
|
+
const searchRegex = new RegExp(query, 'i');
|
|
1718
|
+
|
|
1719
|
+
const results = await modelsCollection.find({
|
|
1720
|
+
// Cherche dans les modèles de l'utilisateur OU les modèles partagés
|
|
1721
|
+
$or: [
|
|
1722
|
+
{ _user: user.username },
|
|
1723
|
+
{ _user: { $exists: false } }
|
|
1724
|
+
],
|
|
1725
|
+
// Cherche dans le nom OU la description
|
|
1726
|
+
$and: [
|
|
1727
|
+
{ $or: [
|
|
1728
|
+
{ name: { $regex: searchRegex } },
|
|
1729
|
+
{ description: { $regex: searchRegex } }
|
|
1730
|
+
]}
|
|
1731
|
+
]
|
|
1732
|
+
}, {
|
|
1733
|
+
// On ne retourne que les informations utiles pour l'IA, pas tout le modèle
|
|
1734
|
+
projection: {
|
|
1735
|
+
name: 1,
|
|
1736
|
+
description: 1,
|
|
1737
|
+
_id: 0
|
|
1738
|
+
}
|
|
1739
|
+
}).limit(10).toArray(); // Limite à 10 résultats pour ne pas surcharger
|
|
1740
|
+
|
|
1741
|
+
res.json({ success: true, models: results });
|
|
1742
|
+
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
logger.error(`[Model Search] Error searching models for query "${query}":`, error);
|
|
1745
|
+
res.status(500).json({ success: false, error: 'An internal server error occurred.' });
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1713
1749
|
engine.post('/api/model/generate', [middlewareAuthenticator, userInitiator, assistantGlobalLimiter, generateLimiter, setTimeoutMiddleware(30000)], async (req, res) => {
|
|
1714
1750
|
// --- NOUVELLE LOGIQUE : Accepter le prompt ET un modèle existant ---
|
|
1715
1751
|
const { prompt, history = [], existingModel } = req.fields;
|
|
@@ -1820,7 +1856,7 @@ export async function onInit(defaultEngine) {
|
|
|
1820
1856
|
const { pack } = req.fields;
|
|
1821
1857
|
|
|
1822
1858
|
try {
|
|
1823
|
-
const {data, count} = await searchData({
|
|
1859
|
+
const {data, count} = await searchData({...req.query, model: req.fields.model || req.query.model, filter: req.fields.filter, pack}, req.me);
|
|
1824
1860
|
|
|
1825
1861
|
if( req.query.attachment ) {
|
|
1826
1862
|
res.attachment(req.query.attachment);
|
|
@@ -1845,7 +1881,7 @@ export async function onInit(defaultEngine) {
|
|
|
1845
1881
|
|
|
1846
1882
|
engine.delete('/api/data/:ids', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, setTimeoutMiddleware(15000)], async (req, res) => {
|
|
1847
1883
|
const ids = req.params.ids.split(',');
|
|
1848
|
-
const r = await deleteData(req.fields.model, ids,
|
|
1884
|
+
const r = await deleteData(req.fields.model, ids, req.me);
|
|
1849
1885
|
if( r.error) {
|
|
1850
1886
|
return res.status(r.statusCode || 400).json(r);
|
|
1851
1887
|
}else{
|
|
@@ -1854,7 +1890,7 @@ export async function onInit(defaultEngine) {
|
|
|
1854
1890
|
});
|
|
1855
1891
|
|
|
1856
1892
|
engine.delete('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, setTimeoutMiddleware(15000)], async (req, res) => {
|
|
1857
|
-
const r = await deleteData(req.fields.model,
|
|
1893
|
+
const r = await deleteData(req.fields.model, req.fields.filter, req.me);
|
|
1858
1894
|
if( r.error) {
|
|
1859
1895
|
return res.status(r.statusCode || 400).json(r);
|
|
1860
1896
|
}else{
|
|
@@ -2344,12 +2380,6 @@ export async function onInit(defaultEngine) {
|
|
|
2344
2380
|
* }
|
|
2345
2381
|
* Returns: Array of aggregated data points (e.g., [{ label: '...', value: ... }]) or an error.
|
|
2346
2382
|
*/
|
|
2347
|
-
// Dans server/src/modules/data.js, modifiez la route POST /api/charts/aggregate
|
|
2348
|
-
|
|
2349
|
-
// Dans server/src/modules/data.js, modifiez la route POST /api/charts/aggregate
|
|
2350
|
-
// C:/Dev/hackersonline-engine/server/src/modules/data.js
|
|
2351
|
-
|
|
2352
|
-
// ... (autres imports et code)
|
|
2353
2383
|
|
|
2354
2384
|
engine.post('/api/charts/aggregate', [throttle, middlewareAuthenticator, userInitiator, ...userMiddlewares, setTimeoutMiddleware(15000)], async (req, res) => {
|
|
2355
2385
|
// --- Récupérer groupByLabelField ---
|
|
@@ -3236,13 +3266,10 @@ async function processRelations(docToProcess, model, collection, me, idMap) {
|
|
|
3236
3266
|
batchFinds.push({
|
|
3237
3267
|
field: field.name,
|
|
3238
3268
|
promise: searchData({
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
model: field.relation
|
|
3244
|
-
}
|
|
3245
|
-
}),
|
|
3269
|
+
filter: value.$find,
|
|
3270
|
+
limit: field.multiple ? 0 : 1,
|
|
3271
|
+
model: field.relation
|
|
3272
|
+
}, me),
|
|
3246
3273
|
multiple: field.multiple
|
|
3247
3274
|
});
|
|
3248
3275
|
}
|
|
@@ -3655,7 +3682,7 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
|
|
|
3655
3682
|
}
|
|
3656
3683
|
|
|
3657
3684
|
// 2. Récupération des documents existants et de leur hash original
|
|
3658
|
-
const existingDocs = (await searchData({
|
|
3685
|
+
const existingDocs = (await searchData({model: modelName, filter}, user))?.data;
|
|
3659
3686
|
if (!existingDocs || existingDocs.length === 0) {
|
|
3660
3687
|
return {success: false, error: i18n.t("api.data.notFound")};
|
|
3661
3688
|
}
|
|
@@ -3856,7 +3883,8 @@ async function handleScheduledJobs(modelName, existingDocs, collection, updateDa
|
|
|
3856
3883
|
}
|
|
3857
3884
|
}
|
|
3858
3885
|
|
|
3859
|
-
|
|
3886
|
+
|
|
3887
|
+
export const deleteData = async (modelName, filter, user ={}, triggerWorkflow, waitForWorkflow = false) => {
|
|
3860
3888
|
|
|
3861
3889
|
try {
|
|
3862
3890
|
const collection = await getCollectionForUser(user);
|
|
@@ -3871,8 +3899,8 @@ export const deleteData = async (modelName, ids = [], filter, user ={}, triggerW
|
|
|
3871
3899
|
});
|
|
3872
3900
|
|
|
3873
3901
|
// Ajouter le filtre par IDs si fourni
|
|
3874
|
-
if (
|
|
3875
|
-
findFilter.push({"$in": ["$_id",
|
|
3902
|
+
if (Array.isArray(filter) && filter.length > 0) {
|
|
3903
|
+
findFilter.push({"$in": ["$_id", filter.map(m => new ObjectId(m))]});
|
|
3876
3904
|
}
|
|
3877
3905
|
|
|
3878
3906
|
// Ajouter le filtre par nom de modèle si fourni (utile si 'filter' est utilisé seul)
|
|
@@ -4071,7 +4099,7 @@ export const deleteData = async (modelName, ids = [], filter, user ={}, triggerW
|
|
|
4071
4099
|
|
|
4072
4100
|
// ... (le reste du fichier data.js)
|
|
4073
4101
|
|
|
4074
|
-
export const searchData = async (
|
|
4102
|
+
export const searchData = async (query, user) => {
|
|
4075
4103
|
const { page, limit, sort, model, ids, timeout, pack } = query; // Les filtres de la requête (attention aux injections MongoDB !)
|
|
4076
4104
|
|
|
4077
4105
|
if( user.username !== 'demo' && isLocalUser(user) && (
|
|
@@ -4610,8 +4638,10 @@ export const importData = async(options, files, user) => {
|
|
|
4610
4638
|
// let allProcessedData = [];
|
|
4611
4639
|
// let modelNameForImport = '';
|
|
4612
4640
|
// --- FIN DE LA MODIFICATION ---
|
|
4613
|
-
|
|
4614
|
-
|
|
4641
|
+
if( !file.name ){
|
|
4642
|
+
throw new Error("No file provided.");
|
|
4643
|
+
}
|
|
4644
|
+
if (file.type === 'application/json' || file.name.endsWith('.json')) {
|
|
4615
4645
|
fileProcessed = true;
|
|
4616
4646
|
const jsonData = await runImportExportWorker('parse-json', { fileContent: fileContent.toString() });
|
|
4617
4647
|
|
|
@@ -4752,7 +4782,7 @@ export const importData = async(options, files, user) => {
|
|
|
4752
4782
|
}
|
|
4753
4783
|
// --- FIN DE LA MODIFICATION PRINCIPALE ---
|
|
4754
4784
|
|
|
4755
|
-
} else if (file.
|
|
4785
|
+
} else if (file.type === 'text/csv' || file.name.endsWith('.csv')) {
|
|
4756
4786
|
// --- Logique CSV (inchangée, mais maintenant elle est séparée de la logique JSON) ---
|
|
4757
4787
|
fileProcessed = true;
|
|
4758
4788
|
const modelNameForImport = options.model;
|
|
@@ -4959,16 +4989,13 @@ export const exportData= async (options, user) =>{
|
|
|
4959
4989
|
|
|
4960
4990
|
// --- Fetch Data using searchData ---
|
|
4961
4991
|
const searchParams = {
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
depth: parsedDepth,
|
|
4967
|
-
limit: remainingLimit
|
|
4968
|
-
}
|
|
4992
|
+
model: modelName,
|
|
4993
|
+
filter: modelSpecificFilter,
|
|
4994
|
+
depth: parsedDepth,
|
|
4995
|
+
limit: remainingLimit
|
|
4969
4996
|
};
|
|
4970
4997
|
|
|
4971
|
-
const { data: resultData, count } = await searchData(searchParams);
|
|
4998
|
+
const { data: resultData, count } = await searchData(searchParams, user);
|
|
4972
4999
|
|
|
4973
5000
|
if (resultData && resultData.length > 0) {
|
|
4974
5001
|
exportResults[modelName] = resultData;
|
|
@@ -5073,7 +5100,6 @@ function isValidAggregationOperator(operator) {
|
|
|
5073
5100
|
|
|
5074
5101
|
return [...arithmeticOperators, ...comparisonOperators, ...stringOperators, ...conditionalOperators].includes(operator);
|
|
5075
5102
|
}
|
|
5076
|
-
// C:/Dev/hackersonline-engine/server/src/modules/data.js
|
|
5077
5103
|
|
|
5078
5104
|
/**
|
|
5079
5105
|
* Gère les traductions spécifiques à l'utilisateur et traite les données de manière récursive
|
|
@@ -5357,8 +5383,6 @@ const readKeyFromFile = (user) => {
|
|
|
5357
5383
|
return null;
|
|
5358
5384
|
};
|
|
5359
5385
|
|
|
5360
|
-
// C:/Dev/data-primals-engine/src/modules/data.js
|
|
5361
|
-
|
|
5362
5386
|
export const dumpUserData = async (user) => {
|
|
5363
5387
|
const s3Config = user.configS3;
|
|
5364
5388
|
const backupDir = getBackupDir();
|
|
@@ -5777,7 +5801,7 @@ export async function installPack(packId, user, lang) {
|
|
|
5777
5801
|
const finalSelector = { ...linkSelector };
|
|
5778
5802
|
delete finalSelector['_model']; // nécessaire
|
|
5779
5803
|
// CORRECTION 4: Appel corrigé à searchData
|
|
5780
|
-
const { data: targetDocs } = await searchData({
|
|
5804
|
+
const { data: targetDocs } = await searchData({model: targetModelName, filter: finalSelector }, user);
|
|
5781
5805
|
|
|
5782
5806
|
if (targetDocs && targetDocs.length > 0) {
|
|
5783
5807
|
targetIds = targetDocs.map(d => d._id); // Récupère un tableau d'ObjectIds
|
|
@@ -5832,11 +5856,6 @@ export const installAllPacks = async () => {
|
|
|
5832
5856
|
await packsCollection.insertMany(packs);
|
|
5833
5857
|
}
|
|
5834
5858
|
|
|
5835
|
-
// Dans C:/Dev/data-primals-engine/src/modules/data.js
|
|
5836
|
-
// Dans C:/Dev/data-primals-engine/src/modules/data.js
|
|
5837
|
-
|
|
5838
|
-
// ... (imports inchangés)
|
|
5839
|
-
|
|
5840
5859
|
export async function handleDemoInitialization(req, res) {
|
|
5841
5860
|
const user = req.me;
|
|
5842
5861
|
const body = req.fields;
|
package/src/modules/workflow.js
CHANGED
|
@@ -197,15 +197,15 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
197
197
|
await jail.set('db', createJailFunction({
|
|
198
198
|
create: (modelName, dataObject) => insertData(modelName, dataObject, {}, user),
|
|
199
199
|
find: async (modelName, filter) => {
|
|
200
|
-
const result = await searchData({
|
|
200
|
+
const result = await searchData({ model: modelName, filter }, user);
|
|
201
201
|
return new ivm.ExternalCopy(result.data).copyInto();
|
|
202
202
|
},
|
|
203
203
|
findOne: async (modelName, filter) => {
|
|
204
|
-
const result = await searchData({
|
|
204
|
+
const result = await searchData({ model: modelName, filter, limit: 1 }, user);
|
|
205
205
|
return new ivm.ExternalCopy(result.data?.[0] || null).copyInto();
|
|
206
206
|
},
|
|
207
207
|
update: (modelName, filter, updateObject) => patchData(modelName, filter, updateObject, {}, user),
|
|
208
|
-
delete: (modelName, filter) => deleteData(modelName,
|
|
208
|
+
delete: (modelName, filter) => deleteData(modelName, filter, user)
|
|
209
209
|
}));
|
|
210
210
|
|
|
211
211
|
const createLoggerFunction = (level) => {
|
|
@@ -228,11 +228,11 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
228
228
|
await jail.set('env', createJailFunction({
|
|
229
229
|
get: async (variableName) => {
|
|
230
230
|
if (!variableName) return null;
|
|
231
|
-
const result = await searchData({
|
|
231
|
+
const result = await searchData({ model: 'env', filter: { name: variableName }, limit: 1 }, user);
|
|
232
232
|
return new ivm.ExternalCopy(result.data?.[0]?.value || null).copyInto();
|
|
233
233
|
},
|
|
234
234
|
getAll: async () => {
|
|
235
|
-
const result = await searchData({
|
|
235
|
+
const result = await searchData({ model: 'env' }, user);
|
|
236
236
|
const envObject = result.data.reduce((acc, v) => {
|
|
237
237
|
acc[v.name] = v.value;
|
|
238
238
|
return acc;
|
|
@@ -708,7 +708,7 @@ async function handleDeleteDataAction(actionDef, contextData, user, dbCollection
|
|
|
708
708
|
// 5. Call the centralized deleteData function (à créer dans data.js)
|
|
709
709
|
// Cette fonction devra gérer la recherche préalable pour les workflows 'DataDeleted' et la suppression des fichiers.
|
|
710
710
|
const deleteResult = await deleteData(
|
|
711
|
-
targetModel,
|
|
711
|
+
targetModel,
|
|
712
712
|
selectorObject,
|
|
713
713
|
user
|
|
714
714
|
);
|
|
@@ -1115,7 +1115,7 @@ export async function triggerWorkflows(triggerData, user, eventType) {
|
|
|
1115
1115
|
|
|
1116
1116
|
// Exécuter la vérification dans la base de données
|
|
1117
1117
|
// Utilisation de countDocuments pour une vérification rapide
|
|
1118
|
-
const matchCount = await searchData({
|
|
1118
|
+
const matchCount = await searchData({ model: targetModelName, filter: finalFilter, limit: 1 }, user);
|
|
1119
1119
|
|
|
1120
1120
|
if (!matchCount.count) {
|
|
1121
1121
|
console.debug(`[Workflow Trigger] Trigger ${trigger._id}: dataFilter non satisfait par la donnée ${dataId}. WorkflowRun non créé.`);
|
|
@@ -1198,7 +1198,6 @@ export async function triggerWorkflows(triggerData, user, eventType) {
|
|
|
1198
1198
|
* @param {object} user - The user context for database access.
|
|
1199
1199
|
* @returns {Promise<void>}
|
|
1200
1200
|
*/
|
|
1201
|
-
// C:/Dev/hackersonline-engine/server/src/modules/workflow.js
|
|
1202
1201
|
|
|
1203
1202
|
export async function processWorkflowRun(workflowRunId, user) {
|
|
1204
1203
|
const dbCollection = await getCollectionForUser(user);
|
|
@@ -1280,7 +1279,7 @@ export async function processWorkflowRun(workflowRunId, user) {
|
|
|
1280
1279
|
try {
|
|
1281
1280
|
// --- 7. Évaluation des conditions de l'étape ---
|
|
1282
1281
|
if (currentStepDef.conditions && Object.keys(currentStepDef.conditions).length > 0) {
|
|
1283
|
-
const searchResult = await searchData({
|
|
1282
|
+
const searchResult = await searchData({ model: contextData.triggerDataModel, filter: currentStepDef.conditions, limit: 1}, user);
|
|
1284
1283
|
conditionsMet = searchResult && searchResult.count > 0;
|
|
1285
1284
|
logger.info(`[processWorkflowRun] Run ID: ${runId}, Step ID: ${currentStepId}: Conditions evaluated. Found ${searchResult ? searchResult.count : 0} match(es). Result: ${conditionsMet}`);
|
|
1286
1285
|
}
|
|
@@ -1478,9 +1477,8 @@ async function handleSendEmailAction(action, triggerData, user) {
|
|
|
1478
1477
|
|
|
1479
1478
|
// 1. Récupérer la configuration SMTP depuis le modèle 'env' de l'utilisateur
|
|
1480
1479
|
const envVars = await searchData({
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
});
|
|
1480
|
+
model: 'env', limit: 100 } // Limite raisonnable pour les variables d'env
|
|
1481
|
+
, user);
|
|
1484
1482
|
|
|
1485
1483
|
if (!envVars.data || envVars.data.length === 0) {
|
|
1486
1484
|
throw new Error("Aucune variable d'environnement (modèle 'env') trouvée pour la configuration SMTP.");
|
package/src/providers.js
CHANGED
|
@@ -91,7 +91,7 @@ export class UserProvider {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
async hasFeature(user, feature) {
|
|
94
|
-
return this.plans[user
|
|
94
|
+
return this.plans[user?.userPlan]?.features.some(f => f === feature);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Ajoutez ici d'autres méthodes nécessaires : findUserById, createUser, etc.
|
|
@@ -471,7 +471,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
471
471
|
const insertResult = await insertData(simpleModel.name, dataToDelete, {}, currentTestUser, false);
|
|
472
472
|
const docId = insertResult.insertedIds[0];
|
|
473
473
|
|
|
474
|
-
const deleteResult = await deleteData(simpleModel.name, [docId],
|
|
474
|
+
const deleteResult = await deleteData(simpleModel.name, [docId], currentTestUser);
|
|
475
475
|
|
|
476
476
|
expect(deleteResult.success).toBe(true);
|
|
477
477
|
expect(deleteResult.deletedCount).toBe(1);
|
|
@@ -493,7 +493,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
493
493
|
input: '$name',
|
|
494
494
|
regex: "^Item Filtrable"
|
|
495
495
|
} };
|
|
496
|
-
const deleteResult = await deleteData(simpleModel.name,
|
|
496
|
+
const deleteResult = await deleteData(simpleModel.name, filter, currentTestUser);
|
|
497
497
|
|
|
498
498
|
expect(deleteResult.success).toBe(true);
|
|
499
499
|
expect(deleteResult.deletedCount).toBe(2);
|
|
@@ -551,19 +551,16 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
551
551
|
it('devrait trouver un document par une condition sur une relation simple ($find)', async () => {
|
|
552
552
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
553
553
|
const searchParams = {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
autoExpand: true,
|
|
563
|
-
depth: 8
|
|
564
|
-
}
|
|
554
|
+
model: comprehensiveTestModelDefinition.name,
|
|
555
|
+
filter: {
|
|
556
|
+
relationSingle: {
|
|
557
|
+
"$find": { "$and": [{"$eq": ["$$this.relatedValue", 101]}] }
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
autoExpand: true,
|
|
561
|
+
depth: 8
|
|
565
562
|
};
|
|
566
|
-
const { data, count } = await searchData(searchParams);
|
|
563
|
+
const { data, count } = await searchData(searchParams, currentTestUser);
|
|
567
564
|
expect(count).toBe(1);
|
|
568
565
|
expect(data).toHaveLength(1);
|
|
569
566
|
expect(data[0]._id.toString()).toBe(docId1.toString());
|
|
@@ -575,18 +572,15 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
575
572
|
it('ne devrait pas trouver de document si la condition $find sur relation simple ne correspond pas', async () => {
|
|
576
573
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
577
574
|
const searchParams = {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
},
|
|
586
|
-
depth: 1
|
|
587
|
-
}
|
|
575
|
+
model: comprehensiveTestModelDefinition.name,
|
|
576
|
+
filter: {
|
|
577
|
+
relationSingle: {
|
|
578
|
+
"$find": { "$eq": ["$$this.relatedName", "NonExistentRelName"] }
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
depth: 1
|
|
588
582
|
};
|
|
589
|
-
const { data, count } = await searchData(searchParams);
|
|
583
|
+
const { data, count } = await searchData(searchParams, currentTestUser);
|
|
590
584
|
expect(count).toBe(0);
|
|
591
585
|
expect(data).toHaveLength(0);
|
|
592
586
|
});
|
|
@@ -594,20 +588,17 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
594
588
|
it('devrait trouver des documents par une condition sur une relation multiple ($find)', async () => {
|
|
595
589
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
596
590
|
const searchParams = {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
},
|
|
607
|
-
depth: 1
|
|
608
|
-
}
|
|
591
|
+
model: comprehensiveTestModelDefinition.name,
|
|
592
|
+
filter: {
|
|
593
|
+
$and: [{$ne:["$stringRequired", null]}, {
|
|
594
|
+
relationMultiple: { // Le champ qui est un tableau de relations
|
|
595
|
+
"$find": {"$eq": ["$$this.relatedValue", 102]} // Condition sur les documents liés
|
|
596
|
+
}
|
|
597
|
+
}]
|
|
598
|
+
},
|
|
599
|
+
depth: 1
|
|
609
600
|
};
|
|
610
|
-
const { data, count } = await searchData(searchParams);
|
|
601
|
+
const { data, count } = await searchData(searchParams, currentTestUser);
|
|
611
602
|
expect(count).toBe(1);
|
|
612
603
|
expect(data).toHaveLength(1);
|
|
613
604
|
expect(data[0]._id.toString()).toBe(docId1.toString());
|
|
@@ -620,19 +611,16 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
620
611
|
it('devrait trouver des documents en combinant un filtre normal et un filtre $find', async () => {
|
|
621
612
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
622
613
|
const searchParams = {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
},
|
|
632
|
-
depth: 1
|
|
633
|
-
}
|
|
614
|
+
model: comprehensiveTestModelDefinition.name,
|
|
615
|
+
filter: {
|
|
616
|
+
stringRequired: "SearchDoc1", // Filtre sur le modèle principal
|
|
617
|
+
relationSingle: {
|
|
618
|
+
"$find": {"$gt": ["$$this.relatedValue", 100] } // Filtre sur la relation
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
depth: 1
|
|
634
622
|
};
|
|
635
|
-
const { data, count } = await searchData(searchParams);
|
|
623
|
+
const { data, count } = await searchData(searchParams,currentTestUser);
|
|
636
624
|
expect(count).toBe(1);
|
|
637
625
|
expect(data).toHaveLength(1);
|
|
638
626
|
expect(data[0]._id.toString()).toBe(docId1.toString());
|
|
@@ -641,26 +629,21 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
641
629
|
it('devrait retourner un tableau vide si $find ne correspond à aucun document lié', async () => {
|
|
642
630
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
643
631
|
const searchParams = {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
},
|
|
652
|
-
depth: 1
|
|
653
|
-
}
|
|
632
|
+
model: comprehensiveTestModelDefinition.name,
|
|
633
|
+
filter: {
|
|
634
|
+
relationMultiple: {
|
|
635
|
+
"$find": { "$eq": ["$$this.relatedName", "NonExistentRelNameForMultiple"] }
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
depth: 1
|
|
654
639
|
};
|
|
655
|
-
const { data, count } = await searchData(searchParams);
|
|
640
|
+
const { data, count } = await searchData(searchParams,currentTestUser);
|
|
656
641
|
expect(count).toBe(0);
|
|
657
642
|
expect(data).toHaveLength(0);
|
|
658
643
|
});
|
|
659
644
|
|
|
660
645
|
});
|
|
661
646
|
|
|
662
|
-
// Dans C:/Dev/hackersonline-engine/test/data.integration.test.js
|
|
663
|
-
|
|
664
647
|
describe('installPack', () => {
|
|
665
648
|
let testPacksColInstance;
|
|
666
649
|
|
|
@@ -824,8 +807,8 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
824
807
|
// Cleanup before starting to ensure a clean state
|
|
825
808
|
await deleteModels({ name: productModel.name, _user: user.username });
|
|
826
809
|
await deleteModels({ name: orderModel.name, _user: user.username });
|
|
827
|
-
await deleteData(productModel.name,
|
|
828
|
-
await deleteData(orderModel.name,
|
|
810
|
+
await deleteData(productModel.name, {}, user);
|
|
811
|
+
await deleteData(orderModel.name, {}, user);
|
|
829
812
|
|
|
830
813
|
// Create models
|
|
831
814
|
await createModel({ ...productModel, _user: user.username });
|
|
@@ -843,8 +826,8 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
843
826
|
afterAll(async () => {
|
|
844
827
|
await deleteModels({ name: productModel.name, _user: user.username });
|
|
845
828
|
await deleteModels({ name: orderModel.name, _user: user.username });
|
|
846
|
-
await deleteData(productModel.name,
|
|
847
|
-
await deleteData(orderModel.name,
|
|
829
|
+
await deleteData(productModel.name, {}, user);
|
|
830
|
+
await deleteData(orderModel.name, {}, user);
|
|
848
831
|
});
|
|
849
832
|
|
|
850
833
|
it('should ALLOW inserting data with a valid relation', async () => {
|
|
@@ -852,7 +835,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
852
835
|
expect(result.success).toBe(true);
|
|
853
836
|
expect(result.insertedIds).toHaveLength(1);
|
|
854
837
|
// Cleanup the created order for test isolation
|
|
855
|
-
await deleteData(orderModel.name, result.insertedIds,
|
|
838
|
+
await deleteData(orderModel.name, result.insertedIds, user);
|
|
856
839
|
});
|
|
857
840
|
|
|
858
841
|
it('should REJECT inserting data with a relation that does not respect the filter', async () => {
|
|
@@ -873,7 +856,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
873
856
|
expect(result.modifiedCount).toBeGreaterThanOrEqual(0);
|
|
874
857
|
|
|
875
858
|
// Cleanup
|
|
876
|
-
await deleteData(orderModel.name, initialOrder.insertedIds,
|
|
859
|
+
await deleteData(orderModel.name, initialOrder.insertedIds, user);
|
|
877
860
|
});
|
|
878
861
|
|
|
879
862
|
it('should REJECT updating data with a relation that does not respect the filter', async () => {
|
|
@@ -885,7 +868,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
885
868
|
expect(result.success).toBe(false);
|
|
886
869
|
expect(result.error).toContain('produit');
|
|
887
870
|
// Cleanup
|
|
888
|
-
await deleteData(orderModel.name, initialOrder.insertedIds,
|
|
871
|
+
await deleteData(orderModel.name, initialOrder.insertedIds, user);
|
|
889
872
|
});
|
|
890
873
|
});
|
|
891
874
|
});
|
|
@@ -299,7 +299,7 @@ describe('Intégration des Workflows - triggerWorkflows', () => {
|
|
|
299
299
|
}, {}, mockUser, false);
|
|
300
300
|
|
|
301
301
|
// 2. Act: Supprimer la donnée
|
|
302
|
-
const deleteResult = await deleteData(targetDataModel.name, [projectId],
|
|
302
|
+
const deleteResult = await deleteData(targetDataModel.name, [projectId], mockUser, true, true);
|
|
303
303
|
expect(deleteResult.success).toBe(true);
|
|
304
304
|
|
|
305
305
|
// 3. Assert: Un `workflowRun` doit être créé
|