data-primals-engine 1.7.1 → 1.7.3

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 (99) hide show
  1. package/README.md +9 -9
  2. package/client/package-lock.json +8430 -8121
  3. package/client/package.json +7 -4
  4. package/client/src/APIInfo.jsx +1 -1
  5. package/client/src/App.jsx +2 -1
  6. package/client/src/App.scss +1635 -1626
  7. package/client/src/AssistantChat.jsx +2 -3
  8. package/client/src/CalendarView.jsx +1 -0
  9. package/client/src/ContentView.jsx +3 -3
  10. package/client/src/Dashboard.jsx +5 -2
  11. package/client/src/DashboardChart.jsx +1 -0
  12. package/client/src/DashboardFlexViewItem.jsx +1 -0
  13. package/client/src/DashboardHtmlViewItem.jsx +1 -0
  14. package/client/src/DashboardView.jsx +2 -0
  15. package/client/src/DataEditor.jsx +0 -1
  16. package/client/src/DataImporter.jsx +489 -468
  17. package/client/src/DataLayout.jsx +25 -23
  18. package/client/src/DataTable.jsx +6 -5
  19. package/client/src/Dialog.jsx +92 -90
  20. package/client/src/Dialog.scss +122 -116
  21. package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
  22. package/client/src/DocumentationPageLayout.scss +1 -1
  23. package/client/src/FlexBuilderControls.jsx +1 -0
  24. package/client/src/FlexBuilderPreview.jsx +1 -1
  25. package/client/src/FlexNode.jsx +1 -1
  26. package/client/src/HistoryDialog.jsx +3 -2
  27. package/client/src/KPIWidget.jsx +1 -1
  28. package/client/src/KanbanView.jsx +1 -0
  29. package/client/src/ModelCreator.jsx +4 -0
  30. package/client/src/ModelList.jsx +1 -0
  31. package/client/src/PackGallery.jsx +5 -4
  32. package/client/src/RTETrans.jsx +1 -1
  33. package/client/src/RelationField.jsx +2 -0
  34. package/client/src/RelationSelectorWidget.jsx +2 -0
  35. package/client/src/RelationValue.jsx +1 -0
  36. package/client/src/RestoreDialog.jsx +1 -0
  37. package/client/src/ViewSwitcher.jsx +1 -1
  38. package/client/src/WorkflowEditor.jsx +3 -0
  39. package/client/src/contexts/CommandContext.jsx +2 -1
  40. package/client/src/contexts/ModelContext.jsx +3 -3
  41. package/client/src/hooks/data.js +1 -0
  42. package/client/src/hooks/useValidation.js +1 -0
  43. package/client/src/translations.js +24 -24
  44. package/client/vite.config.js +31 -30
  45. package/doc/AI-assistance.md +87 -63
  46. package/doc/Concepts.md +122 -0
  47. package/doc/Custom-Endpoints.md +31 -0
  48. package/doc/Event-system.md +13 -14
  49. package/doc/Home.md +33 -0
  50. package/doc/Modules.md +83 -0
  51. package/doc/Packs-gallery.md +8 -23
  52. package/doc/Workflows.md +32 -0
  53. package/doc/automation-workflows.md +141 -102
  54. package/doc/dashboards-kpis-charts.md +47 -49
  55. package/doc/data-management.md +126 -120
  56. package/doc/data-models.md +68 -75
  57. package/doc/roles-permissions.md +144 -43
  58. package/doc/sharding-replication.md +158 -0
  59. package/doc/users.md +54 -30
  60. package/package.json +27 -17
  61. package/server.js +37 -37
  62. package/src/ai.jobs.js +135 -0
  63. package/src/constants.js +560 -545
  64. package/src/data.js +521 -518
  65. package/src/email.js +157 -154
  66. package/src/engine.js +167 -49
  67. package/src/gameObject.js +6 -0
  68. package/src/i18n.js +0 -1
  69. package/src/modules/assistant/assistant.js +782 -763
  70. package/src/modules/assistant/constants.js +23 -16
  71. package/src/modules/assistant/providers.js +77 -37
  72. package/src/modules/auth-google/index.js +53 -50
  73. package/src/modules/bucket.js +346 -335
  74. package/src/modules/data/data.backup.js +400 -376
  75. package/src/modules/data/data.cluster.js +559 -0
  76. package/src/modules/data/data.core.js +11 -8
  77. package/src/modules/data/data.js +311 -311
  78. package/src/modules/data/data.operations.js +3666 -3555
  79. package/src/modules/data/data.relations.js +1 -0
  80. package/src/modules/data/data.replication.js +125 -0
  81. package/src/modules/data/data.routes.js +2206 -1879
  82. package/src/modules/data/data.scheduling.js +2 -1
  83. package/src/modules/file.js +248 -247
  84. package/src/modules/mongodb.js +76 -73
  85. package/src/modules/user.js +18 -2
  86. package/src/modules/worker-script-runner.js +97 -0
  87. package/src/modules/workflow.js +177 -52
  88. package/src/packs.js +5701 -5701
  89. package/src/providers.js +298 -297
  90. package/src/sso.js +91 -25
  91. package/test/assistant.test.js +207 -206
  92. package/test/cluster.test.js +221 -0
  93. package/test/core.test.js +0 -2
  94. package/test/data.integration.test.js +1425 -1416
  95. package/test/import_export.integration.test.js +210 -210
  96. package/test/replication.test.js +163 -0
  97. package/test/workflow.actions.integration.test.js +487 -475
  98. package/test/workflow.integration.test.js +332 -329
  99. package/doc/core-concepts.md +0 -33
package/src/providers.js CHANGED
@@ -1,298 +1,299 @@
1
- import {compare, hash} from "bcrypt";
2
- import {maxTotalPrivateFilesSize} from "./constants.js";
3
- import {getCollection, ObjectId} from "./modules/mongodb.js";
4
- import {Logger} from "./gameObject.js";
5
- import {Config} from "./config.js";
6
-
7
- /**
8
- * @class UserProvider
9
- * Définit le contrat pour toutes les implémentations de fournisseurs d'utilisateurs.
10
- * C'est une classe de base abstraite, elle n'est pas destinée à être utilisée directement.
11
- */
12
- export class UserProvider {
13
-
14
- plans = {};
15
-
16
- constructor(engine) {
17
- if (this.constructor === UserProvider) {
18
- throw new Error("Abstract classes can't be instantiated.");
19
- }
20
- this.engine = engine;
21
- this.plans = this.getUserPlans();
22
- }
23
-
24
- /**
25
- * Trouve un utilisateur par son ID unique. Nécessaire pour la désérialisation de Passport.
26
- * @param {string} id - L'ID de l'utilisateur.
27
- * @returns {Promise<object|null>} L'objet utilisateur ou null.
28
- */
29
- async findUserById(id) {
30
- throw new Error("Method 'findUserById()' must be implemented.");
31
- }
32
-
33
-
34
- /**
35
- * Met à jour un nouvel utilisateur.
36
- * @param user
37
- * @returns {Promise<void>}
38
- */
39
- async updateUser(user, data) {
40
-
41
- }
42
-
43
- /**
44
- * Trouve un utilisateur par son nom d'utilisateur.
45
- * @param {string} username - Le nom d'utilisateur à rechercher.
46
- * @returns {Promise<object|null>} L'objet utilisateur ou null.
47
- */
48
- async findUserByUsername(username) {
49
- throw new Error("Method 'findUserByUsername()' must be implemented.");
50
- }
51
-
52
- /**
53
- * Valide le mot de passe d'un utilisateur.
54
- * @param {object} user - L'objet utilisateur.
55
- * @param {string} password - Le mot de passe à vérifier.
56
- * @returns {Promise<boolean>} True si le mot de passe est valide.
57
- */
58
- async validatePassword(user, password) {
59
- throw new Error("Method 'validatePassword()' must be implemented.");
60
- }
61
-
62
- /**
63
- * Initialise un utilisateur sur la requête (pour les middlewares).
64
- * @param {object} req - L'objet requête Express.
65
- * @returns {Promise<void>}
66
- */
67
- async initiateUser(req) {
68
- throw new Error("Method 'initiateUser()' must be implemented.");
69
- }
70
-
71
- /**
72
- * Récupère la limite de stockage d'un utilisateur.
73
- * @param user
74
- * @returns {Promise<number>}
75
- */
76
- async getUserStorageLimit(user) {
77
- return Config.Get('maxTotalPrivateFilesSize', maxTotalPrivateFilesSize);
78
- }
79
-
80
- /**
81
- * Récupère la fréquence de sauvegarde d'un utilisateur.
82
- * @param user
83
- * @returns {Promise<void>}
84
- */
85
- async getBackupFrequency(user){
86
- return 'daily';
87
- }
88
-
89
- /**
90
- * Récupère les middlewares d'un utilisateur.
91
- * @returns {Promise<*[]>}
92
- */
93
- async getMiddlewares(){
94
- return [];
95
- }
96
-
97
- getUserPlans(){
98
- return {
99
- free: {
100
- requestLimitPerHour: 10000,
101
- features: ['indexes'] // Feature list : indexes is the only option for now. Use a dedicated collection for this user
102
- }
103
- };
104
- }
105
-
106
- async hasFeature(user, feature) {
107
- return this.plans[user?.userPlan ?? 'free']?.features.some(f => f === feature);
108
- }
109
-
110
- // Ajoutez ici d'autres méthodes nécessaires : findUserById, createUser, etc.
111
- }
112
-
113
-
114
- export class DefaultUserProvider extends UserProvider {
115
-
116
- users= [{ username: "demo", password: "demo", temporary: true }];
117
-
118
- /**
119
- * Trouve un utilisateur. Gère spécifiquement les utilisateurs de démo.
120
- */
121
- async findUserByUsername(username) {
122
- // Si le nom d'utilisateur commence par "demo", on le considère comme un utilisateur de démo valide et volatile.
123
- if (this.isDemoUser(username)){
124
- // On retourne un objet utilisateur de démo avec la structure attendue.
125
- return { username: username, password: "demo", userPlan: 'free', temporary: true }; // Ajout de userPlan pour la cohérence
126
- }
127
- // Logique pour les vrais utilisateurs (si vous en ajoutez plus tard)
128
- return this.users.find(user => user.username === username);
129
- }
130
-
131
- async getUserStorageLimit(user) {
132
- return Config.Get('maxTotalPrivateFilesSize', maxTotalPrivateFilesSize);
133
- }
134
-
135
- async getBackupFrequency(user){
136
- return 'daily';
137
- }
138
-
139
- async getMiddlewares(){
140
- return []
141
- }
142
-
143
- isDemoUser(user) {
144
- return (typeof (user) === 'string' && (/^demo[0-9]{0,2}$/.test(user) || /^perf[0-9]{1,8}$/.test(user)))
145
- }
146
-
147
- async validatePassword(user, password) {
148
- // Pour un utilisateur de démo, le mot de passe est toujours valide.
149
- if (this.isDemoUser(user)){
150
- return true;
151
- }
152
- // Logique pour les vrais utilisateurs
153
- return true; // ou votre logique de comparaison bcrypt
154
- }
155
-
156
- async updateUser(user, data) {
157
- this.users = this.users.map(user => user.username === user.username ? {...user, ...data} : user);
158
- }
159
- /**
160
- * Initialise l'utilisateur sur la requête. C'est ici que la magie opère.
161
- */
162
- async initiateUser(req) {
163
- // Priorité 1: Un utilisateur est déjà authentifié via une session (pour les vrais comptes).
164
- if (req.session?.user?.username) {
165
- const user = await this.findUserByUsername(req.session.user.username);
166
- if (user) {
167
- req.me = user;
168
- return; // L'utilisateur est trouvé, on s'arrête là.
169
- }
170
- }
171
-
172
- // Priorité 2: Pas de session, mais on vérifie la présence d'un cookie "username" pour la démo.
173
- const demoUsername = req.cookies?.username;
174
- if (demoUsername && typeof demoUsername === 'string' && this.isDemoUser(demoUsername)) {
175
- // On a trouvé un cookie de démo. On crée l'objet utilisateur correspondant.
176
- const demoUser = await this.findUserByUsername(demoUsername);
177
- if (demoUser) {
178
- req.me = demoUser;
179
- return;
180
- }
181
- }
182
-
183
- // Priorité 2: PasF de session, mais on vérifie la présence d'un user dans la requete
184
- const user = req.query._user;
185
- if (user && typeof user === 'string' && this.isDemoUser(user)) {
186
- const demoUser = await this.findUserByUsername(user);
187
- if (demoUser) {
188
- req.me = demoUser;
189
- return;
190
- }
191
- }
192
- }
193
-
194
- getUserPlans(){
195
- return {
196
- free: {
197
- features: ['indexes']
198
- }
199
- }
200
- }
201
- }
202
-
203
- /**
204
- * @class MongoUserProvider
205
- * Un fournisseur d'utilisateurs prêt pour la production qui stocke les utilisateurs
206
- * dans MongoDB et gère les mots de passe de manière sécurisée avec bcrypt.
207
- */
208
- export class MongoUserProvider extends UserProvider {
209
- constructor(engine) {
210
- super(engine);
211
- this.usersCollection = getCollection("users");
212
- this.logger = engine.getComponent(Logger);
213
- this.logger.info("MongoUserProvider initialized, using 'users' collection for local accounts.");
214
- }
215
-
216
- /**
217
- * Trouve un utilisateur par son ID MongoDB.
218
- * @param {string} id - L'ID de l'utilisateur.
219
- * @returns {Promise<object|null>}
220
- */
221
- async findUserById(id) {
222
- try {
223
- if (!ObjectId.isValid(id)) return null;
224
- // On exclut le mot de passe des requêtes pour des raisons de sécurité
225
- return await this.usersCollection.findOne({ _id: new ObjectId(id) }, { projection: { password: 0 } });
226
- } catch (e) {
227
- this.logger.error(`Error in findUserById: ${e.message}`);
228
- return null;
229
- }
230
- }
231
-
232
- /**
233
- * Trouve un utilisateur par son nom d'utilisateur ou son email.
234
- * @param {string} username - Le nom d'utilisateur ou l'email.
235
- * @returns {Promise<object|null>}
236
- */
237
- async findUserByUsername(username) {
238
- // Permet de se connecter avec l'username ou l'email
239
- return await this.usersCollection.findOne({ $or: [{ username }, { email: username }] });
240
- }
241
-
242
- /**
243
- * Valide le mot de passe fourni par rapport au hash stocké.
244
- * @param {object} user - L'objet utilisateur complet (avec le hash du mot de passe).
245
- * @param {string} password - Le mot de passe en clair à vérifier.
246
- * @returns {Promise<boolean>}
247
- */
248
- async validatePassword(user, password) {
249
- if (!user || !user.password || !password) {
250
- return false;
251
- }
252
- return await compare(password, user.password);
253
- }
254
-
255
- /**
256
- * Crée un nouvel utilisateur avec un mot de passe haché.
257
- * @param {object} userData - Données de l'utilisateur (username, email, password, etc.).
258
- * @returns {Promise<object>} L'utilisateur nouvellement créé (sans le mot de passe).
259
- */
260
- async createUser(userData) {
261
- const { username, email, password, ...otherData } = userData;
262
- if (!username || !email || !password) {
263
- throw new Error("Username, email, and password are required to create a user.");
264
- }
265
-
266
- const existingUser = await this.usersCollection.findOne({ $or: [{ username }, { email }] });
267
- if (existingUser) {
268
- throw new Error("User with this username or email already exists.");
269
- }
270
-
271
- const hashedPassword = await hash(password, 12);
272
-
273
- const result = await this.usersCollection.insertOne({
274
- username,
275
- email,
276
- password: hashedPassword,
277
- createdAt: new Date(),
278
- userPlan: 'free', // Plan par défaut
279
- ...otherData
280
- });
281
-
282
- return await this.findUserById(result.insertedId);
283
- }
284
-
285
- /**
286
- * Initialise l'utilisateur sur la requête à partir de la session.
287
- * Ne gère que les utilisateurs locaux. Le SSO est géré par Passport.
288
- * @param {object} req - L'objet requête Express.
289
- */
290
- async initiateUser(req) {
291
- if (req.session?.user?._id) {
292
- const user = await this.findUserById(req.session.user._id);
293
- if (user) {
294
- req.me = user; // Attache l'utilisateur à la requête
295
- }
296
- }
297
- }
1
+ import {compare, hash} from "bcrypt";
2
+ import {maxTotalPrivateFilesSize} from "./constants.js";
3
+ import {getCollection, ObjectId} from "./modules/mongodb.js";
4
+ import {Logger} from "./gameObject.js";
5
+ import {Config} from "./config.js";
6
+
7
+ /**
8
+ * @class UserProvider
9
+ * Définit le contrat pour toutes les implémentations de fournisseurs d'utilisateurs.
10
+ * C'est une classe de base abstraite, elle n'est pas destinée à être utilisée directement.
11
+ */
12
+ export class UserProvider {
13
+
14
+ plans = {};
15
+
16
+ constructor(engine) {
17
+ if (this.constructor === UserProvider) {
18
+ throw new Error("Abstract classes can't be instantiated.");
19
+ }
20
+ this.engine = engine;
21
+ this.plans = this.getUserPlans();
22
+ }
23
+
24
+ /**
25
+ * Trouve un utilisateur par son ID unique. Nécessaire pour la désérialisation de Passport.
26
+ * @param {string} id - L'ID de l'utilisateur.
27
+ * @returns {Promise<object|null>} L'objet utilisateur ou null.
28
+ */
29
+ async findUserById(id) {
30
+ throw new Error("Method 'findUserById()' must be implemented.");
31
+ }
32
+
33
+
34
+ /**
35
+ * Met à jour un nouvel utilisateur.
36
+ * @param user
37
+ * @returns {Promise<void>}
38
+ */
39
+ async updateUser(user, data) {
40
+
41
+ }
42
+
43
+ /**
44
+ * Trouve un utilisateur par son nom d'utilisateur.
45
+ * @param {string} username - Le nom d'utilisateur à rechercher.
46
+ * @returns {Promise<object|null>} L'objet utilisateur ou null.
47
+ */
48
+ async findUserByUsername(username) {
49
+ throw new Error("Method 'findUserByUsername()' must be implemented.");
50
+ }
51
+
52
+ /**
53
+ * Valide le mot de passe d'un utilisateur.
54
+ * @param {object} user - L'objet utilisateur.
55
+ * @param {string} password - Le mot de passe à vérifier.
56
+ * @returns {Promise<boolean>} True si le mot de passe est valide.
57
+ */
58
+ async validatePassword(user, password) {
59
+ throw new Error("Method 'validatePassword()' must be implemented.");
60
+ }
61
+
62
+ /**
63
+ * Initialise un utilisateur sur la requête (pour les middlewares).
64
+ * @param {object} req - L'objet requête Express.
65
+ * @returns {Promise<void>}
66
+ */
67
+ async initiateUser(req) {
68
+ throw new Error("Method 'initiateUser()' must be implemented.");
69
+ }
70
+
71
+ /**
72
+ * Récupère la limite de stockage d'un utilisateur.
73
+ * @param user
74
+ * @returns {Promise<number>}
75
+ */
76
+ async getUserStorageLimit(user) {
77
+ return Config.Get('maxTotalPrivateFilesSize', maxTotalPrivateFilesSize);
78
+ }
79
+
80
+ /**
81
+ * Récupère la fréquence de sauvegarde d'un utilisateur.
82
+ * @param user
83
+ * @returns {Promise<void>}
84
+ */
85
+ async getBackupFrequency(user){
86
+ return 'daily';
87
+ }
88
+
89
+ /**
90
+ * Récupère les middlewares d'un utilisateur.
91
+ * @returns {Promise<*[]>}
92
+ */
93
+ async getMiddlewares(){
94
+ return [];
95
+ }
96
+
97
+ getUserPlans(){
98
+ return {
99
+ free: {
100
+ requestLimitPerHour: 10000,
101
+ features: ['indexes'] // Feature list : indexes is the only option for now. Use a dedicated collection for this user
102
+ }
103
+ };
104
+ }
105
+
106
+ async hasFeature(user, feature) {
107
+ return this.plans[user?.userPlan ?? 'free']?.features.some(f => f === feature);
108
+ }
109
+
110
+ // Ajoutez ici d'autres méthodes nécessaires : findUserById, createUser, etc.
111
+ }
112
+
113
+
114
+ export class DefaultUserProvider extends UserProvider {
115
+
116
+ users= [{ username: "demo", password: "demo", temporary: true }];
117
+
118
+ /**
119
+ * Trouve un utilisateur. Gère spécifiquement les utilisateurs de démo.
120
+ */
121
+ async findUserByUsername(username) {
122
+ // Si le nom d'utilisateur commence par "demo", on le considère comme un utilisateur de démo valide et volatile.
123
+ if (this.isDemoUser(username)){
124
+ // On retourne un objet utilisateur de démo avec la structure attendue.
125
+ return { username: username, password: "demo", userPlan: 'free', temporary: true }; // Ajout de userPlan pour la cohérence
126
+ }
127
+ // Logique pour les vrais utilisateurs (si vous en ajoutez plus tard)
128
+ return this.users.find(user => user.username === username);
129
+ }
130
+
131
+ async getUserStorageLimit(user) {
132
+ return Config.Get('maxTotalPrivateFilesSize', maxTotalPrivateFilesSize);
133
+ }
134
+
135
+ async getBackupFrequency(user){
136
+ return 'daily';
137
+ }
138
+
139
+ async getMiddlewares(){
140
+ return []
141
+ }
142
+
143
+ isDemoUser(user) {
144
+ return (typeof (user) === 'string' && (/^demo[0-9]{0,2}$/.test(user) || /^perf[0-9]{1,8}$/.test(user)))
145
+ }
146
+
147
+ async validatePassword(user, password) {
148
+ // Pour un utilisateur de démo, le mot de passe est toujours valide.
149
+ if (this.isDemoUser(user)){
150
+ return true;
151
+ }
152
+ // Logique pour les vrais utilisateurs
153
+ return true; // ou votre logique de comparaison bcrypt
154
+ }
155
+
156
+ async updateUser(user, data) {
157
+ this.users = this.users.map(user => user.username === user.username ? {...user, ...data} : user);
158
+ }
159
+ /**
160
+ * Initialise l'utilisateur sur la requête. C'est ici que la magie opère.
161
+ */
162
+ async initiateUser(req) {
163
+ // Priorité 1: Un utilisateur est déjà authentifié via une session (pour les vrais comptes).
164
+ if (req.session?.user?.username) {
165
+ const user = await this.findUserByUsername(req.session.user.username);
166
+ if (user) {
167
+ req.me = user;
168
+ return; // L'utilisateur est trouvé, on s'arrête là.
169
+ }
170
+ }
171
+
172
+ // Priorité 2: Pas de session, mais on vérifie la présence d'un cookie "username" pour la démo.
173
+ const demoUsername = req.cookies?.username;
174
+ if (demoUsername && typeof demoUsername === 'string' && this.isDemoUser(demoUsername)) {
175
+ // On a trouvé un cookie de démo. On crée l'objet utilisateur correspondant.
176
+ const demoUser = await this.findUserByUsername(demoUsername);
177
+ if (demoUser) {
178
+ req.me = demoUser;
179
+ return;
180
+ }
181
+ }
182
+
183
+ // Priorité 2: PasF de session, mais on vérifie la présence d'un user dans la requete
184
+ const user = req.query._user;
185
+ if (user && typeof user === 'string' && this.isDemoUser(user)) {
186
+ const demoUser = await this.findUserByUsername(user);
187
+ if (demoUser) {
188
+ req.me = demoUser;
189
+ return;
190
+ }
191
+ }
192
+ }
193
+
194
+ getUserPlans(){
195
+ return {
196
+ free: {
197
+ features: ['indexes']
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ /**
204
+ * @class MongoUserProvider
205
+ * Un fournisseur d'utilisateurs prêt pour la production qui stocke les utilisateurs
206
+ * dans MongoDB et gère les mots de passe de manière sécurisée avec bcrypt.
207
+ */
208
+ export class MongoUserProvider extends UserProvider {
209
+ constructor(engine) {
210
+ super(engine);
211
+ this.usersCollection = getCollection("users");
212
+ this.logger = engine.getComponent(Logger);
213
+ this.logger.info("MongoUserProvider initialized, using 'users' collection for local accounts.");
214
+ }
215
+
216
+ /**
217
+ * Trouve un utilisateur par son ID MongoDB.
218
+ * @param {string} id - L'ID de l'utilisateur.
219
+ * @returns {Promise<object|null>}
220
+ */
221
+ async findUserById(id) {
222
+ try {
223
+ if (!ObjectId.isValid(id)) return null;
224
+ // On exclut le mot de passe des requêtes pour des raisons de sécurité
225
+ return await this.usersCollection.findOne({ _id: new ObjectId(id) }, { projection: { password: 0 } });
226
+ } catch (e) {
227
+ this.logger.error(`Error in findUserById: ${e.message}`);
228
+ return null;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Trouve un utilisateur par son nom d'utilisateur ou son email.
234
+ * @param {string} username - Le nom d'utilisateur ou l'email.
235
+ * @returns {Promise<object|null>}
236
+ */
237
+ async findUserByUsername(username) {
238
+ // Permet de se connecter avec l'username ou l'email
239
+ return await this.usersCollection.findOne({ $or: [{ username }, { email: username }] });
240
+ }
241
+
242
+ /**
243
+ * Valide le mot de passe fourni par rapport au hash stocké.
244
+ * @param {object} user - L'objet utilisateur complet (avec le hash du mot de passe).
245
+ * @param {string} password - Le mot de passe en clair à vérifier.
246
+ * @returns {Promise<boolean>}
247
+ */
248
+ async validatePassword(user, password) {
249
+ if (!user || !user.password || !password) {
250
+ return false;
251
+ }
252
+ return await compare(password, user.password);
253
+ }
254
+
255
+ /**
256
+ * Crée un nouvel utilisateur avec un mot de passe haché.
257
+ * @param {object} userData - Données de l'utilisateur (username, email, password, etc.).
258
+ * @returns {Promise<object>} L'utilisateur nouvellement créé (sans le mot de passe).
259
+ */
260
+ async createUser(userData) {
261
+ const { username, email, password, ...otherData } = userData;
262
+ if (!username || !email || !password) {
263
+ throw new Error("Username, email, and password are required to create a user.");
264
+ }
265
+
266
+ const existingUser = await this.usersCollection.findOne({ $or: [{ username }, { email }] });
267
+ if (existingUser) {
268
+ throw new Error("User with this username or email already exists.");
269
+ }
270
+
271
+ const hashedPassword = await hash(password, 12);
272
+
273
+ const result = await this.usersCollection.insertOne({
274
+ username,
275
+ email,
276
+ password: hashedPassword,
277
+ createdAt: new Date(),
278
+ userPlan: 'free', // Plan par défaut
279
+ storageUsed: 0, // NOUVEAU: Initialiser le compteur de stockage
280
+ ...otherData
281
+ });
282
+
283
+ return await this.findUserById(result.insertedId);
284
+ }
285
+
286
+ /**
287
+ * Initialise l'utilisateur sur la requête à partir de la session.
288
+ * Ne gère que les utilisateurs locaux. Le SSO est géré par Passport.
289
+ * @param {object} req - L'objet requête Express.
290
+ */
291
+ async initiateUser(req) {
292
+ if (req.session?.user?._id) {
293
+ const user = await this.findUserById(req.session.user._id);
294
+ if (user) {
295
+ req.me = user; // Attache l'utilisateur à la requête
296
+ }
297
+ }
298
+ }
298
299
  }