data-primals-engine 1.4.3 → 1.5.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 (77) hide show
  1. package/README.md +913 -867
  2. package/client/package-lock.json +49 -0
  3. package/client/package.json +1 -0
  4. package/client/src/AddWidgetTypeModal.jsx +47 -43
  5. package/client/src/App.jsx +3 -7
  6. package/client/src/App.scss +25 -3
  7. package/client/src/AssistantChat.jsx +363 -323
  8. package/client/src/AssistantChat.scss +30 -12
  9. package/client/src/Dashboard.jsx +480 -396
  10. package/client/src/Dashboard.scss +1 -1
  11. package/client/src/DashboardHtmlViewItem.jsx +147 -0
  12. package/client/src/DashboardView.jsx +104 -19
  13. package/client/src/DataEditor.jsx +12 -5
  14. package/client/src/DataLayout.jsx +805 -762
  15. package/client/src/DataLayout.scss +14 -0
  16. package/client/src/DataTable.jsx +63 -77
  17. package/client/src/Dialog.scss +1 -1
  18. package/client/src/Field.jsx +591 -322
  19. package/client/src/FlexDataRenderer.jsx +2 -0
  20. package/client/src/FlexTreeUtils.js +1 -1
  21. package/client/src/FlexViewCard.jsx +44 -0
  22. package/client/src/HistoryDialog.jsx +47 -14
  23. package/client/src/HtmlViewBuilderModal.jsx +91 -0
  24. package/client/src/HtmlViewBuilderModal.scss +18 -0
  25. package/client/src/HtmlViewCard.jsx +44 -0
  26. package/client/src/HtmlViewCard.scss +35 -0
  27. package/client/src/KPIDialog.jsx +11 -1
  28. package/client/src/KanbanCard.jsx +1 -2
  29. package/client/src/ModelCreator.jsx +6 -6
  30. package/client/src/ModelCreatorField.jsx +74 -31
  31. package/client/src/ModelList.jsx +93 -54
  32. package/client/src/Notification.jsx +136 -136
  33. package/client/src/Notification.scss +0 -18
  34. package/client/src/Pagination.jsx +5 -3
  35. package/client/src/RelationField.jsx +354 -258
  36. package/client/src/RelationSelectorWidget.jsx +173 -0
  37. package/client/src/constants.js +1 -1
  38. package/client/src/contexts/ModelContext.jsx +10 -1
  39. package/client/src/contexts/UIContext.jsx +72 -63
  40. package/client/src/filter.js +262 -212
  41. package/client/src/hooks/useTutorials.jsx +62 -65
  42. package/client/src/hooks/useValidation.js +75 -0
  43. package/client/src/translations.js +26 -24
  44. package/package.json +3 -1
  45. package/perf/README.md +147 -0
  46. package/perf/artillery-hooks.js +37 -0
  47. package/perf/perf-shot-hardwork.yml +84 -0
  48. package/perf/perf-shot-search.yml +45 -0
  49. package/perf/setup.yml +26 -0
  50. package/server.js +1 -1
  51. package/src/constants.js +3 -28
  52. package/src/core.js +15 -1
  53. package/src/data.js +1 -1
  54. package/src/defaultModels.js +63 -7
  55. package/src/email.js +5 -2
  56. package/src/engine.js +5 -3
  57. package/src/filter.js +5 -3
  58. package/src/i18n.js +710 -10
  59. package/src/modules/assistant/assistant.js +151 -19
  60. package/src/modules/bucket.js +14 -16
  61. package/src/modules/data/data.backup.js +11 -8
  62. package/src/modules/data/data.core.js +118 -92
  63. package/src/modules/data/data.history.js +531 -492
  64. package/src/modules/data/data.js +9 -56
  65. package/src/modules/data/data.operations.js +3282 -2999
  66. package/src/modules/data/data.relations.js +686 -686
  67. package/src/modules/data/data.routes.js +118 -24
  68. package/src/modules/data/data.scheduling.js +2 -1
  69. package/src/modules/data/data.validation.js +85 -3
  70. package/src/modules/file.js +247 -236
  71. package/src/modules/user.js +4 -1
  72. package/src/modules/workflow.js +9 -10
  73. package/src/openai.jobs.js +2 -0
  74. package/src/packs.js +5482 -5461
  75. package/src/providers.js +22 -7
  76. package/test/data.integration.test.js +136 -2
  77. package/test/import_export.integration.test.js +1 -1
@@ -1,237 +1,248 @@
1
-
2
- // Nouvelle fonction pour ajouter un fichier privé
3
- import {maxPrivateFileSize, megabytes} from "../constants.js";
4
- import {isLocalUser} from "../data.js";
5
- import i18n from "../../src/i18n.js";
6
- import {getCollection} from "./mongodb.js";
7
- import {getFileExtension, isGUID, uuidv4} from "../core.js";
8
- import path from "node:path";
9
- import process from "node:process";
10
- import fs from "node:fs";
11
- import { checkServerCapacity} from "./data/index.js";
12
- import crypto from "node:crypto";
13
- import * as tar from "tar";
14
- import {promisify} from "node:util";
15
- import {calculateTotalUserStorageUsage, hasPermission} from "./user.js";
16
- import {Logger} from "../gameObject.js";
17
- import {deleteFromS3, getUserS3Config, uploadToS3} from "./bucket.js";
18
-
19
- const pbkdf2Async = promisify(crypto.pbkdf2);
20
-
21
- let engine, logger;
22
- const fsPromises = fs.promises;
23
-
24
- // Encryption settings
25
- const algorithm = 'aes-256-cbc'; // Algorithm to use
26
- const iterations = 100000; // Number of iterations for PBKDF2
27
- const keyLength = 32; // Key length for AES-256
28
- const ivLength = 16; // IV length for AES
29
-
30
- // Function to derive a key and IV from a passphrase and salt
31
- async function deriveKeyAndIV(password, salt) {
32
- const key = await pbkdf2Async(password, salt, iterations, keyLength, 'sha256');
33
- // L'IV n'est plus dérivé ici.
34
- return { key };
35
- }
36
-
37
-
38
- export const unzip = async (file) => {
39
- await tar.extract({ file: file, gzip: true, sync: true });
40
- }
41
-
42
- export const zip = async (filename) => {
43
- await tar.create({ gzip: true, sync: true, file: filename+'.gz' }, [filename])
44
- }
45
- export const addFile = async (file, user) => {
46
- if (!file) throw new Error("Le fichier est requis");
47
-
48
- if (file.size > maxPrivateFileSize) {
49
- throw new Error(`La taille du fichier dépasse la limite autorisée (${maxPrivateFileSize / megabytes} Mo).`);
50
- }
51
-
52
- if (user.username !== 'demo' && isLocalUser(user) && !await hasPermission(["API_ADMIN", "API_UPLOAD_FILE"], user)) {
53
- throw new Error(i18n.t("api.permission.uploadFile"));
54
- }
55
-
56
- const incomingDataSize = file.size;
57
-
58
- const userStorageLimit = await engine.userProvider.getUserStorageLimit(user);
59
- const currentStorageUsage = await calculateTotalUserStorageUsage(user);
60
-
61
- if (currentStorageUsage + incomingDataSize > userStorageLimit) {
62
- throw new Error(i18n.t("api.data.storageLimitExceeded", { limit: Math.round(userStorageLimit / megabytes) }));
63
- }
64
-
65
- const serverCapacity = await checkServerCapacity(incomingDataSize);
66
- if (!serverCapacity.isSufficient) {
67
- throw new Error(i18n.t("api.data.serverStorageFull", "Le serveur a atteint sa capacité de stockage maximale. Veuillez réessayer plus tard."));
68
- }
69
-
70
- // Générer un GUID pour le fichier
71
- const guid = uuidv4();
72
- const s3Config = await getUserS3Config(user);
73
- const extension = getFileExtension(file.name);
74
- const newFilename = `${guid}.${extension}`;
75
-
76
- const fileData = {
77
- guid: guid,
78
- filename: file.name,
79
- size: file.size,
80
- mimeType: file.type,
81
- createdAt: new Date(),
82
- user: user.username,
83
- mainUser: user._user
84
- };
85
-
86
- if (s3Config && s3Config.bucketName && s3Config.accessKeyId && s3Config.secretAccessKey) { // Correction: bucketName au lieu de bucket
87
- try {
88
- // Correction: Appel manquant à la fonction de téléversement
89
- await uploadToS3(s3Config, file.path, newFilename);
90
-
91
- fileData.storage = 's3';
92
- fileData.filename = newFilename; // Le nom sur S3
93
- logger.info(`Fichier ${newFilename} téléversé sur le bucket S3 ${s3Config.bucketName}.`);
94
- } catch (error) {
95
- logger.info(`Le téléversement S3 a échoué pour ${file.name}: ${error.message}`, 'error');
96
- throw new Error("Le téléversement S3 a échoué.");
97
- } finally {
98
- // Nettoyer le fichier temporaire uploadé par express-formidable
99
- await fsPromises.unlink(file.path).catch(e => logger.info(`Échec de la suppression du fichier temporaire ${file.path}: ${e.message}`, 'error'));
100
- }
101
- } else {
102
- // Sauvegarde locale
103
- const uploadDir = path.join(process.cwd(), "uploads", "private");
104
- if (!fs.existsSync(uploadDir)) {
105
- fs.mkdirSync(uploadDir, { recursive: true });
106
- }
107
- const newPath = path.join(uploadDir, newFilename);
108
-
109
- try {
110
- // express-formidable place déjà le fichier dans un répertoire temporaire. Nous n'avons qu'éplacer.
111
- await fsPromises.rename(file.path, newPath);
112
- fileData.storage = 'local';
113
- fileData.filename = newFilename; // Le nom dans le dossier uploads
114
- fileData.path = newPath; // Le chemin complet pour les fichiers locaux
115
- logger.info(`Fichier ${newFilename} sauvegardé localement dans ${newPath}.`);
116
- } catch (error) {
117
- logger.info(`Le déplacement du fichier local a échoué pour ${file.name}: ${error.message}`, 'error');
118
- // Essayer de nettoyer le fichier temporaire même si le renommage échoue
119
- await fsPromises.unlink(file.path).catch(e => logger.info(`Échec de la suppression du fichier temporaire ${file.path}: ${e.message}`, 'error'));
120
- throw new Error("Le stockage du fichier local a échoué.");
121
- }
122
- }
123
-
124
- const filesCollection = await getCollection("files");
125
- await filesCollection.insertOne(fileData);
126
-
127
- return guid;
128
- };
129
-
130
- /**
131
- * Récupère les métadonnées d'un fichier depuis la base de données.
132
- * @param {string} guid - Le GUID du fichier.
133
- * @returns {Promise<object|null>} L'objet de métadonnées du fichier ou null si non trouvé.
134
- */
135
- export const getFile = async (guid) => {
136
- const filesCollection = await getCollection("files");
137
- return await filesCollection.findOne({ guid });
138
- };
139
-
140
- export const removeFile = async (guid, user) => {
141
- if (!guid) return false;
142
- if (!isGUID(guid)) throw new Error("Le GUID du fichier n'est pas valide.");
143
-
144
- const fileData = await getFile(guid);
145
- if (!fileData) {
146
- logger.info(`Tentative de suppression d'un fichier inexistant avec le GUID : ${guid}`, 'warn');
147
- return;
148
- }
149
-
150
- if (fileData.storage === 's3') {
151
- const s3Config = await getUserS3Config(user);
152
- if (s3Config && s3Config.bucketName) { // Correction: bucketName au lieu de bucket
153
- try {
154
- await deleteFromS3(s3Config, fileData.filename);
155
- logger.info(`Fichier ${fileData.filename} supprimé du bucket S3 ${s3Config.bucketName}.`);
156
- } catch (error) {
157
- logger.info(`La suppression S3 a échoué pour ${fileData.filename}: ${error.message}`, 'error');
158
- throw new Error("La suppression S3 a échoué.");
159
- }
160
- } else {
161
- logger.info(`Configuration S3 non trouvée pour l'utilisateur, impossible de supprimer le fichier ${fileData.filename} de S3.`, 'error');
162
- throw new Error("Configuration S3 non trouvée, impossible de supprimer le fichier.");
163
- }
164
- } else if (fileData.storage === 'local') {
165
- try {
166
- if (fileData.path && fs.existsSync(fileData.path)) {
167
- await fsPromises.unlink(fileData.path);
168
- logger.info(`Fichier local ${fileData.path} supprimé.`);
169
- } else {
170
- logger.info(`Fichier local non trouvé au chemin ${fileData.path}, mais suppression de l'enregistrement en BDD.`, 'warn');
171
- }
172
- } catch (error) {
173
- logger.info(`La suppression du fichier local a échoué pour ${fileData.path}: ${error.message}`, 'error');
174
- throw new Error("La suppression du fichier local a échoué.");
175
- }
176
- }
177
-
178
- const filesCollection = await getCollection("files");
179
- await filesCollection.deleteOne({ guid });
180
- };
181
-
182
-
183
-
184
-
185
- // Function to encrypt the file content
186
- export async function encryptFile(filePath, password) {
187
- try {
188
- const salt = crypto.randomBytes(16);
189
- const iv = crypto.randomBytes(ivLength);
190
-
191
- // On ne dérive que la clé
192
- const { key } = await deriveKeyAndIV(password, salt);
193
-
194
- const fileData = await fs.promises.readFile(filePath);
195
- const cipher = crypto.createCipheriv(algorithm, key, iv);
196
- const encryptedData = Buffer.concat([cipher.update(fileData), cipher.final()]);
197
-
198
- // On it : [salt][iv][données chiffrées]
199
- await fs.promises.writeFile(filePath, Buffer.concat([salt, iv, encryptedData]));
200
-
201
- console.log('File encrypted successfully.', filePath);
202
- } catch (error) {
203
- console.error('Error during encryption:', error.message);
204
- }
205
- }
206
- // Function to decrypt the file content
207
- export async function decryptFile(filePath, password) {
208
- try {
209
- const fileData = await fs.promises.readFile(filePath);
210
-
211
- // Extraire le sel, l'IV et les données
212
- const salt = fileData.slice(0, 16);
213
- // NOUVEAU: Extraire l'IV qui suit le sel
214
- const iv = fileData.slice(16, 16 + ivLength);
215
- const encryptedData = fileData.slice(16 + ivLength);
216
-
217
- // On dérive la même clé en utilisant le sel extrait
218
- const { key } = await deriveKeyAndIV(password, salt);
219
-
220
- // On utilise l'IV extrait pour le déchiffrement
221
- const decipher = crypto.createDecipheriv(algorithm, key, iv);
222
- const decryptedData = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
223
-
224
- await fs.promises.writeFile(filePath, decryptedData);
225
-
226
- console.log('File decrypted successfully.');
227
- } catch (error) {
228
- console.error('Error during decryption:', error.message);
229
- // Relancer l'erreur peut être utile pour que l'appelant sache que ça a échoué
230
- throw new Error(`Decryption failed: ${error.message}`);
231
- }
232
- }
233
-
234
- export async function onInit(defaultEngine) {
235
- engine = defaultEngine;
236
- logger = engine.getComponent(Logger);
1
+
2
+ // Nouvelle fonction pour ajouter un fichier privé
3
+ import {maxPrivateFileSize, megabytes} from "../constants.js";
4
+ import {isLocalUser} from "../data.js";
5
+ import i18n from "../../src/i18n.js";
6
+ import {getCollection} from "./mongodb.js";
7
+ import {getFileExtension, isGUID, uuidv4} from "../core.js";
8
+ import path from "node:path";
9
+ import process from "node:process";
10
+ import fs from "node:fs";
11
+ import { checkServerCapacity} from "./data/index.js";
12
+ import crypto from "node:crypto";
13
+ import * as tar from "tar";
14
+ import {promisify} from "node:util";
15
+ import {calculateTotalUserStorageUsage, hasPermission} from "./user.js";
16
+ import {Logger} from "../gameObject.js";
17
+ import {deleteFromS3, getUserS3Config, uploadToS3} from "./bucket.js";
18
+ import {Config} from "../config.js";
19
+
20
+ const pbkdf2Async = promisify(crypto.pbkdf2);
21
+
22
+ let engine, logger;
23
+ const fsPromises = fs.promises;
24
+
25
+ // Encryption settings
26
+ const algorithm = 'aes-256-cbc'; // Algorithm to use
27
+ const iterations = 100000; // Number of iterations for PBKDF2
28
+ const keyLength = 32; // Key length for AES-256
29
+ const ivLength = 16; // IV length for AES
30
+
31
+ // Function to derive a key and IV from a passphrase and salt
32
+ async function deriveKeyAndIV(password, salt) {
33
+ const key = await pbkdf2Async(password, salt, iterations, keyLength, 'sha256');
34
+ // L'IV n'est plus dérivé ici.
35
+ return { key };
36
+ }
37
+
38
+
39
+ export const unzip = async (file) => {
40
+ await tar.extract({ file: file, gzip: true, sync: true });
41
+ }
42
+
43
+ export const zip = async (filename) => {
44
+ await tar.create({ gzip: true, sync: true, file: filename+'.gz' }, [filename])
45
+ }
46
+ export const addFile = async (file, user) => {
47
+ if (!file) throw new Error("Le fichier est requis");
48
+
49
+ // Rendre compatible avec formidable v2 (path, name, type) et v3 (filepath, originalFilename, mimetype)
50
+ const filePath = file.path || file.filepath;
51
+ const originalName = file.name || file.originalFilename;
52
+ const mimeType = file.type || file.mimetype;
53
+
54
+ if (!filePath || !originalName) {
55
+ throw new Error("Les informations du fichier (nom, chemin) sont incomplètes.");
56
+ }
57
+
58
+ const m = Config.Get('maxPrivateFileSize', maxPrivateFileSize);
59
+ if (file.size > m) {
60
+ throw new Error(`La taille du fichier dépasse la limite autorisée (${m / megabytes} Mo).`);
61
+ }
62
+
63
+ if (user.username !== 'demo' && isLocalUser(user) && !await hasPermission(["API_ADMIN", "API_UPLOAD_FILE"], user)) {
64
+ throw new Error(i18n.t("api.permission.uploadFile"));
65
+ }
66
+
67
+ const incomingDataSize = file.size;
68
+
69
+ const userStorageLimit = await engine.userProvider.getUserStorageLimit(user);
70
+ const currentStorageUsage = await calculateTotalUserStorageUsage(user);
71
+
72
+ if (currentStorageUsage + incomingDataSize > userStorageLimit) {
73
+ throw new Error(i18n.t("api.data.storageLimitExceeded", { limit: Math.round(userStorageLimit / megabytes) }));
74
+ }
75
+
76
+ const serverCapacity = await checkServerCapacity(incomingDataSize);
77
+ if (!serverCapacity.isSufficient) {
78
+ throw new Error(i18n.t("api.data.serverStorageFull", "Le serveur a atteint sa capacité de stockage maximale. Veuillez réessayer plus tard."));
79
+ }
80
+
81
+ // Générer un GUID pour le fichier
82
+ const guid = uuidv4();
83
+ const s3Config = await getUserS3Config(user);
84
+ const extension = getFileExtension(originalName);
85
+ const newFilename = `${guid}.${extension}`;
86
+
87
+ const fileData = {
88
+ guid: guid,
89
+ name: originalName, // Nom original du fichier
90
+ filename: newFilename, // Nom du fichier stocké (GUID + extension)
91
+ size: file.size,
92
+ mimeType: mimeType,
93
+ createdAt: new Date(),
94
+ user: user.username,
95
+ mainUser: user._user
96
+ };
97
+
98
+ if (s3Config && s3Config.bucketName && s3Config.accessKeyId && s3Config.secretAccessKey) {
99
+ try {
100
+ await uploadToS3(s3Config, filePath, newFilename);
101
+
102
+ fileData.storage = 's3';
103
+ logger.info(`Fichier ${newFilename} téléversé sur le bucket S3 ${s3Config.bucketName}.`);
104
+ } catch (error) {
105
+ logger.error(`Le téléversement S3 a échoué pour ${originalName}: ${error.message}`, error);
106
+ throw new Error("Le téléversement S3 a échoué.");
107
+ } finally {
108
+ // Nettoyer le fichier temporaire uploadé par express-formidable
109
+ await fsPromises.unlink(filePath).catch(e => logger.warn(`Échec de la suppression du fichier temporaire ${filePath}: ${e.message}`));
110
+ }
111
+ } else {
112
+ // Sauvegarde locale
113
+ const uploadDir = path.join(process.cwd(), "uploads", "private");
114
+ if (!fs.existsSync(uploadDir)) {
115
+ fs.mkdirSync(uploadDir, { recursive: true });
116
+ }
117
+ const newPath = path.join(uploadDir, newFilename);
118
+
119
+ try {
120
+ // Utiliser copyFile puis unlink au lieu de rename pour éviter les erreurs cross-device (EXDEV)
121
+ // qui peuvent survenir dans des environnements conteneurisés comme GitHub Actions.
122
+ await fsPromises.copyFile(filePath, newPath);
123
+ await fsPromises.unlink(filePath);
124
+ fileData.storage = 'local';
125
+ fileData.path = newPath; // Le chemin complet pour les fichiers locaux
126
+ logger.info(`Fichier ${newFilename} sauvegardé localement dans ${newPath}.`);
127
+ } catch (error) {
128
+ logger.error(`Le stockage du fichier local a échoué pour ${originalName}: ${error.message}`, error);
129
+ // Essayer de nettoyer le fichier temporaire même si le renommage échoue
130
+ await fsPromises.unlink(filePath).catch(e => logger.warn(`Échec de la suppression du fichier temporaire ${filePath}: ${e.message}`));
131
+ throw new Error("Le stockage du fichier local a échoué.");
132
+ }
133
+ }
134
+
135
+ const filesCollection = await getCollection("files");
136
+ await filesCollection.insertOne(fileData);
137
+
138
+ return guid;
139
+ };
140
+
141
+ /**
142
+ * Récupère les métadonnées d'un fichier depuis la base de données.
143
+ * @param {string} guid - Le GUID du fichier.
144
+ * @returns {Promise<object|null>} L'objet de métadonnées du fichier ou null si non trouvé.
145
+ */
146
+ export const getFile = async (guid) => {
147
+ const filesCollection = await getCollection("files");
148
+ return await filesCollection.findOne({ guid });
149
+ };
150
+
151
+ export const removeFile = async (guid, user) => {
152
+ if (!guid) return false;
153
+ if (!isGUID(guid)) throw new Error("Le GUID du fichier n'est pas valide.");
154
+
155
+ const fileData = await getFile(guid);
156
+ if (!fileData) {
157
+ logger.info(`Tentative de suppression d'un fichier inexistant avec le GUID : ${guid}`, 'warn');
158
+ return;
159
+ }
160
+
161
+ if (fileData.storage === 's3') {
162
+ const s3Config = await getUserS3Config(user);
163
+ if (s3Config && s3Config.bucketName) { // Correction: bucketName au lieu de bucket
164
+ try {
165
+ await deleteFromS3(s3Config, fileData.filename);
166
+ logger.info(`Fichier ${fileData.filename} supprimé du bucket S3 ${s3Config.bucketName}.`);
167
+ } catch (error) {
168
+ logger.info(`La suppression S3 a échoué pour ${fileData.filename}: ${error.message}`, 'error');
169
+ throw new Error("La suppression S3 a échoué.");
170
+ }
171
+ } else {
172
+ logger.info(`Configuration S3 non trouvée pour l'utilisateur, impossible de supprimer le fichier ${fileData.filename} de S3.`, 'error');
173
+ throw new Error("Configuration S3 non trouvée, impossible de supprimer le fichier.");
174
+ }
175
+ } else if (fileData.storage === 'local') {
176
+ try {
177
+ if (fileData.path && fs.existsSync(fileData.path)) {
178
+ await fsPromises.unlink(fileData.path);
179
+ logger.info(`Fichier local ${fileData.path} supprimé.`);
180
+ } else {
181
+ logger.info(`Fichier local non trouvé au chemin ${fileData.path}, mais suppression de l'enregistrement en BDD.`, 'warn');
182
+ }
183
+ } catch (error) {
184
+ logger.info(`La suppression du fichier local a échoué pour ${fileData.path}: ${error.message}`, 'error');
185
+ throw new Error("La suppression du fichier local a échoué.");
186
+ }
187
+ }
188
+
189
+ const filesCollection = await getCollection("files");
190
+ await filesCollection.deleteOne({ guid });
191
+ };
192
+
193
+
194
+
195
+
196
+ // Function to encrypt the file content
197
+ export async function encryptFile(filePath, password) {
198
+ try {
199
+ const salt = crypto.randomBytes(16);
200
+ const iv = crypto.randomBytes(ivLength);
201
+
202
+ // On ne dérive que la clé
203
+ const { key } = await deriveKeyAndIV(password, salt);
204
+
205
+ const fileData = await fs.promises.readFile(filePath);
206
+ const cipher = crypto.createCipheriv(algorithm, key, iv);
207
+ const encryptedData = Buffer.concat([cipher.update(fileData), cipher.final()]);
208
+
209
+ // On it : [salt][iv][données chiffrées]
210
+ await fs.promises.writeFile(filePath, Buffer.concat([salt, iv, encryptedData]));
211
+
212
+ console.log('File encrypted successfully.', filePath);
213
+ } catch (error) {
214
+ console.error('Error during encryption:', error.message);
215
+ }
216
+ }
217
+ // Function to decrypt the file content
218
+ export async function decryptFile(filePath, password) {
219
+ try {
220
+ const fileData = await fs.promises.readFile(filePath);
221
+
222
+ // Extraire le sel, l'IV et les données
223
+ const salt = fileData.slice(0, 16);
224
+ // NOUVEAU: Extraire l'IV qui suit le sel
225
+ const iv = fileData.slice(16, 16 + ivLength);
226
+ const encryptedData = fileData.slice(16 + ivLength);
227
+
228
+ // On dérive la même clé en utilisant le sel extrait
229
+ const { key } = await deriveKeyAndIV(password, salt);
230
+
231
+ // On utilise l'IV extrait pour le déchiffrement
232
+ const decipher = crypto.createDecipheriv(algorithm, key, iv);
233
+ const decryptedData = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
234
+
235
+ await fs.promises.writeFile(filePath, decryptedData);
236
+
237
+ console.log('File decrypted successfully.');
238
+ } catch (error) {
239
+ console.error('Error during decryption:', error.message);
240
+ // Relancer l'erreur peut être utile pour que l'appelant sache que ça a échoué
241
+ throw new Error(`Decryption failed: ${error.message}`);
242
+ }
243
+ }
244
+
245
+ export async function onInit(defaultEngine) {
246
+ engine = defaultEngine;
247
+ logger = engine.getComponent(Logger);
237
248
  }
@@ -15,6 +15,7 @@ import rateLimit from "express-rate-limit";
15
15
  import ivm from "isolated-vm";
16
16
  import {emailDefaultConfig} from "../constants.js";
17
17
  import {safeAssignObject} from "../core.js";
18
+ import {Config} from "../config.js";
18
19
 
19
20
  export const userInitiator = async (req, res, next) => {
20
21
 
@@ -278,6 +279,8 @@ export async function getEnv(user){
278
279
 
279
280
  export async function getSmtpConfig(user) {
280
281
 
282
+ const cfg = Config.Get('emailDefaultConfig', emailDefaultConfig);
283
+
281
284
  // 1. Récupérer la configuration SMTP depuis le modèle 'env' de l'utilisateur
282
285
  const envVars = await searchData({
283
286
  model: 'env',
@@ -289,7 +292,7 @@ export async function getSmtpConfig(user) {
289
292
  return acc;
290
293
  }, {});
291
294
  if( !smtpConfig.port )
292
- smtpConfig.port = emailDefaultConfig.port;
295
+ smtpConfig.port = cfg.port;
293
296
 
294
297
  return smtpConfig;
295
298
  }
@@ -6,12 +6,9 @@ import crypto from "node:crypto";
6
6
  import ivm from 'isolated-vm';
7
7
 
8
8
  import {Logger} from "../gameObject.js";
9
- import {deleteData, getModel, insertData, patchData, scheduleAlerts, searchData} from "./data/index.js";
10
- import {emailDefaultConfig, maxExecutionsByStep, maxWorkflowSteps, port} from "../constants.js";
11
- import {ChatOpenAI} from "@langchain/openai";
12
- import {ChatGoogleGenerativeAI} from "@langchain/google-genai";
9
+ import {deleteData, getModel, insertData, patchData, searchData} from "./data/index.js";
10
+ import { maxExecutionsByStep, maxWorkflowSteps, port} from "../constants.js";
13
11
  import {ChatPromptTemplate} from "@langchain/core/prompts";
14
- import { ChatDeepSeek } from "@langchain/deepseek";
15
12
  import i18n from "../../src/i18n.js";
16
13
  import {sendEmail} from "../email.js";
17
14
 
@@ -21,9 +18,9 @@ import { services } from '../services/index.js';
21
18
  import {getEnv, getSmtpConfig} from "./user.js";
22
19
  import {getHost} from "../constants.js";
23
20
  import {providers} from "./assistant/constants.js";
24
- import {ChatAnthropic} from "@langchain/anthropic";
25
21
  import {getAIProvider} from "./assistant/assistant.js";
26
- import {escapeRegex, safeAssignObject} from "../core.js";
22
+ import { safeAssignObject} from "../core.js";
23
+ import {Config} from "../config.js";
27
24
 
28
25
  let logger = null;
29
26
  export async function onInit(defaultEngine) {
@@ -1449,14 +1446,16 @@ export async function processWorkflowRun(workflowRunId, user) {
1449
1446
  }
1450
1447
 
1451
1448
  let stepCount = 0;
1449
+ const mw = Config.Get('maxWorkflowSteps', maxWorkflowSteps);
1452
1450
  while (currentStepId) {
1453
- if (stepCount++ >= maxWorkflowSteps) {
1451
+ if (stepCount++ >= mw) {
1454
1452
  return await logError(`Maximum workflow step executions exceeded (${maxWorkflowSteps} max).`);
1455
1453
  }
1456
1454
 
1457
1455
  const execCount = (stepExecutionsCount[currentStepId] || 0) + 1;
1458
- if (execCount > maxExecutionsByStep) {
1459
- return await logError(`Maximum executions (${maxExecutionsByStep}) exceeded for step ${currentStepId}.`);
1456
+ const m = Config.Get('maxExecutionsByStep', maxExecutionsByStep);
1457
+ if (execCount > m) {
1458
+ return await logError(`Maximum executions (${m}) exceeded for step ${currentStepId}.`);
1460
1459
  }
1461
1460
  stepExecutionsCount[currentStepId] = execCount;
1462
1461
  logger.info(`[processWorkflowRun] Run ID: ${runId}, Current Step ID: ${currentStepId}`);
@@ -28,6 +28,7 @@ export const openaiJobModel = async (lang, txt, history, existingModels = []) =>
28
28
  {
29
29
  "models": [{
30
30
  "name": "book",
31
+ "icon": "FaBook",
31
32
  "description": "*description détaillée en plusieurs phrases de l'utilité du modèle, et ses cas d'usage, ici : Modèle de référencement de livres multi-support. Peut être utilisé pour des bibliothèques personnelles, virtuelles ou municipales.*",
32
33
  "fields": [
33
34
  {"name": "title", "type": "string", "required": true, "asMain": true, "hint": "Titre du livre", color: '#FF89CC'},
@@ -40,6 +41,7 @@ export const openaiJobModel = async (lang, txt, history, existingModels = []) =>
40
41
  {
41
42
  name: "library",
42
43
  "description": "",
44
+ "icon": "FaBookOpenReader",
43
45
  fields: [
44
46
  {
45
47
  "name": "name",