data-primals-engine 1.1.8 → 1.2.1

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.
Files changed (44) hide show
  1. package/README.md +72 -25
  2. package/client/src/App.scss +11 -6
  3. package/client/src/AssistantChat.scss +0 -1
  4. package/client/src/ChartConfigModal.jsx +0 -1
  5. package/client/src/ConditionBuilder.jsx +0 -2
  6. package/client/src/ConditionBuilder2.jsx +47 -6
  7. package/client/src/ContentView.jsx +0 -1
  8. package/client/src/CronBuilder.scss +0 -1
  9. package/client/src/CronPartBuilder.jsx +0 -1
  10. package/client/src/Dashboard.jsx +0 -1
  11. package/client/src/Dashboard.scss +0 -1
  12. package/client/src/DashboardChart.jsx +0 -1
  13. package/client/src/DashboardView.jsx +0 -1
  14. package/client/src/DataEditor.jsx +0 -4
  15. package/client/src/DisplayFlexNodeRenderer.jsx +0 -1
  16. package/client/src/DocumentationPageLayout.jsx +0 -2
  17. package/client/src/DocumentationPageLayout.scss +0 -1
  18. package/client/src/Field.jsx +1 -1
  19. package/client/src/FlexNode.jsx +0 -1
  20. package/client/src/KanbanView.scss +0 -1
  21. package/client/src/ModelCreator.jsx +4 -4
  22. package/client/src/ModelCreator.scss +10 -0
  23. package/client/src/ModelCreatorField.jsx +34 -34
  24. package/client/src/RTE.jsx +2 -1
  25. package/client/src/RestoreDialog.jsx +0 -1
  26. package/client/src/TourSpotlight.jsx +0 -1
  27. package/client/src/filter.js +2 -0
  28. package/client/src/hooks/useDragAndDrop.js +0 -1
  29. package/issues.txt +0 -0
  30. package/package.json +3 -2
  31. package/src/constants.js +6 -0
  32. package/src/core.js +6 -0
  33. package/src/data.js +0 -2
  34. package/src/defaultModels.js +33 -0
  35. package/src/migrate.js +0 -1
  36. package/src/modules/assistant.js +211 -133
  37. package/src/modules/data.js +88 -69
  38. package/src/modules/user.js +111 -56
  39. package/src/modules/workflow.js +10 -12
  40. package/src/providers.js +1 -1
  41. package/src/workers/crypto-worker.js +0 -1
  42. package/test/data.integration.test.js +53 -70
  43. package/test/user.test.js +203 -0
  44. package/test/workflow.integration.test.js +1 -1
@@ -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
- user: { username: alertDoc._user },
985
- query: {
986
- model: alertDoc.targetModel,
987
- filter: apiFilter,
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
- user,
1237
- query: {
1238
- model: 'endpoint',
1239
- filter: {
1240
- path: path,
1241
- method: method,
1242
- isActive: true
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", [], {"$lt": ["$timestamp",dt.toISOString()]}, null, false);
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
- query: {
1531
- model,
1532
- filter: processedFilter,
1533
- limit, // Optimisation : pas besoin de plus de résultats
1534
- page: 1
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({query: {...req.query, model: req.fields.model || req.query.model, filter: req.fields.filter, pack}, user: req.me });
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, {}, req.me);
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, [], req.fields.filter, req.me);
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
- user: me,
3240
- query: {
3241
- filter: value.$find,
3242
- limit: field.multiple ? 0 : 1,
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({user, query: {model: modelName, filter}}))?.data;
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
- export const deleteData = async (modelName, ids = [], filter, user ={}, triggerWorkflow, waitForWorkflow = false) => {
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 (ids && ids.length > 0) {
3875
- findFilter.push({"$in": ["$_id", ids.map(m => new ObjectId(m))]});
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 ({user, query}) => {
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
- if (file.mimetype === 'application/json' || file.originalFilename.endsWith('.json')) {
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.mimetype === 'text/csv' || file.originalFilename.endsWith('.csv')) {
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
- user: user,
4963
- query: {
4964
- model: modelName,
4965
- filter: modelSpecificFilter,
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({ user, query: { model: targetModelName, filter: finalSelector } });
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;
@@ -88,73 +88,128 @@ export async function onInit(defaultEngine) {
88
88
  engine = defaultEngine;
89
89
  logger = engine.getComponent(Logger);
90
90
  }
91
+ /**
92
+ * Calcule et retourne l'ensemble des permissions actives pour un utilisateur.
93
+ * Cette fonction interne est la pierre angulaire de la nouvelle logique de permission.
94
+ * 1. Elle récupère toutes les permissions de base issues des rôles de l'utilisateur.
95
+ * 2. Elle applique ensuite les "exceptions" (ajouts ou retraits de permissions) qui sont valides (non expirées).
96
+ * @param {object} user - L'objet utilisateur pour lequel calculer les permissions.
97
+ * @returns {Promise<Set<string>>} Un Set contenant les noms de toutes les permissions actives.
98
+ * @private
99
+ */
100
+ export async function getUserActivePermissions(user) {
101
+ const datasCollection = await getCollectionForUser(user);
102
+ const now = new Date();
103
+ const activePermissions = new Set();
91
104
 
92
- export async function hasPermission(permissionNames, user) {
93
- if( !isLocalUser(user)){
94
- return user.roles?.some(f => permissionNames.includes(f));
95
- }
96
- try {
97
- // Si on a une string on le transforme en tableau.
98
- const permissionNamesArray = Array.isArray(permissionNames) ? permissionNames : [permissionNames];
99
- const collection = await getCollectionForUser(user);
105
+ // --- ÉTAPE 1: Récupérer les permissions de base des rôles ---
106
+ if (user.roles && user.roles.length > 0) {
107
+ const roleIds = user.roles.map(id => new ObjectId(id));
100
108
 
101
- const job = [
109
+ const rolePermissions = await datasCollection.aggregate([
110
+ { $match: { _id: { $in: roleIds }, _model: "role" } },
111
+ { $unwind: "$permissions" },
112
+ { $addFields: { "permissionId": { "$toObjectId": "$permissions" } } },
102
113
  {
103
114
  $lookup: {
104
- from: 'datas',
105
- let: { rolesIds: (user.roles ||[]).map(m => new ObjectId(m)) },
106
- pipeline: [
107
- {
108
- $match: {
109
- $expr: {
110
- $and: [
111
- { $in: ['$_id', '$$rolesIds'] }
112
- ]
113
- }
114
- }
115
- },
116
- {
117
- $lookup: {
118
- from: 'datas',
119
- let: { rolePermissions: {
120
- "$map": {
121
- "input": "$permissions",
122
- "in": { "$toObjectId": "$$this" }
123
- }
124
- } },
125
- pipeline: [
126
- {
127
- $match: {
128
- $expr: {
129
- $and: [
130
- { $in: ['$_id', '$$rolePermissions'] }
131
- ]
132
- }
133
- }
134
- },
135
- { $limit: 1 }
136
- ],
137
- as: 'permissions'
138
- }
139
- },
140
- { $unwind: { path: '$permissions', preserveNullAndEmptyArrays: true } }
141
- ],
142
- as: 'roles'
115
+ from: datasCollection.collectionName, // Utiliser la même collection
116
+ localField: "permissionId",
117
+ foreignField: "_id",
118
+ as: "permissionDoc"
143
119
  }
144
120
  },
145
- { $unwind: { path: '$roles', preserveNullAndEmptyArrays: true } },
146
- { $match: { 'roles.permissions.name': { $in: permissionNamesArray } } }, // Match if permissions.name in array
147
- { $limit: 1 },
148
- { $project: { _id: 0, hasPermission: { $cond: [{ $in: ['$roles.permissions.name', permissionNamesArray] }, true, false] } } } //check the value
149
- ];
150
- const result = await collection.aggregate(job).toArray();
151
- return result.length === 1 && result[0].hasPermission;
121
+ { $unwind: "$permissionDoc" },
122
+ { $group: { _id: "$permissionDoc.name" } }
123
+ ]).toArray();
124
+
125
+ rolePermissions.forEach(p => p._id && activePermissions.add(p._id));
126
+ }
127
+
128
+ // --- ÉTAPE 2: Appliquer les exceptions de permission ---
129
+ const exceptions = await datasCollection.aggregate([
130
+ {
131
+ $match: {
132
+ _model: "userPermission",
133
+ user: user._id, // Pas besoin de convertir en ObjectId si c'est déjà une string
134
+ $or: [
135
+ { expiresAt: { $exists: false } },
136
+ { expiresAt: { $gt: now } }
137
+ ]
138
+ }
139
+ },
140
+ {
141
+ $lookup: {
142
+ from: datasCollection.collectionName,
143
+ let: { permissionId: "$permission" },
144
+ pipeline: [
145
+ {
146
+ $match: {
147
+ $expr: {
148
+ $eq: [
149
+ "$_id",
150
+ { $toObjectId: "$$permissionId" } // Conversion ici
151
+ ]
152
+ }
153
+ }
154
+ },
155
+ { $project: { name: 1 } }
156
+ ],
157
+ as: 'permissionDoc'
158
+ }
159
+ },
160
+ { $unwind: '$permissionDoc' }
161
+ ]).toArray();
162
+
163
+ // Appliquer les exceptions
164
+ for (const exception of exceptions) {
165
+ const permissionName = exception.permissionDoc?.name;
166
+ if (!permissionName) continue;
167
+
168
+ if (exception.isGranted) {
169
+ activePermissions.add(permissionName);
170
+ } else {
171
+ activePermissions.delete(permissionName);
172
+ }
173
+ }
174
+
175
+ return activePermissions;
176
+ }
177
+
178
+ /**
179
+ * Vérifie si un utilisateur possède au moins une des permissions spécifiées.
180
+ * Cette fonction utilise la nouvelle logique basée sur les rôles et les exceptions de permission.
181
+ * @param {string|string[]} permissionNames - Le nom de la permission ou un tableau de noms.
182
+ * @param {object} user - L'objet utilisateur authentifié.
183
+ * @returns {Promise<boolean>} - True si l'utilisateur a la permission, sinon false.
184
+ */
185
+ export async function hasPermission(permissionNames, user) {
186
+ // Garde la compatibilité pour les utilisateurs non-locaux (ex: système)
187
+ if (!isLocalUser(user)) {
188
+ const userRoles = new Set(user.roles || []);
189
+ const requiredPermissions = Array.isArray(permissionNames) ? permissionNames : [permissionNames];
190
+ return requiredPermissions.some(p => userRoles.has(p));
191
+ }
192
+
193
+ try {
194
+ const requiredPermissions = Array.isArray(permissionNames) ? permissionNames : [permissionNames];
195
+ // Si aucune permission n'est requise, on autorise
196
+ if (requiredPermissions.length === 0) {
197
+ return true;
198
+ }
199
+
200
+ // 1. Obtenir l'ensemble final et à jour des permissions de l'utilisateur
201
+ const activePermissions = await getUserActivePermissions(user);
202
+
203
+ // 2. Vérifier si au moins une des permissions requises est dans l'ensemble des permissions actives
204
+ return requiredPermissions.some(pName => activePermissions.has(pName));
205
+
152
206
  } catch (e) {
153
- logger.error(e);
207
+ logger.error("Erreur lors de la vérification des permissions :", e);
154
208
  return false;
155
209
  }
156
210
  }
157
211
 
212
+
158
213
  /**
159
214
  * Calcule l'utilisation totale de l'espace de stockage pour un utilisateur en octets.
160
215
  * Cela inclut la taille des documents dans sa collection de données et la taille de ses fichiers uploadés.
@@ -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({ user, query: { model: modelName, filter } });
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({ user, query: { model: modelName, filter, limit: 1 } });
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, null, filter, user)
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({ user, query: { model: 'env', filter: { name: variableName }, limit: 1 } });
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({ user, query: { model: 'env' } });
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({ user, query: { model: targetModelName, filter: finalFilter, limit: 1 } });
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({ user, query: { model: contextData.triggerDataModel, filter: currentStepDef.conditions, limit: 1 } });
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
- user,
1482
- query: { model: 'env', limit: 100 } // Limite raisonnable pour les variables d'env
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.userPlan]?.features.some(f => f === feature);
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.
@@ -1,4 +1,3 @@
1
- // C:/Dev/hackersonline-engine/server/src/workers/crypto-worker.js
2
1
  import { parentPort } from 'worker_threads';
3
2
  import crypto from 'node:crypto';
4
3
  import fs from 'node:fs/promises';