data-primals-engine 1.7.2 → 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 (78) hide show
  1. package/README.md +160 -160
  2. package/client/package-lock.json +8430 -8430
  3. package/client/src/APIInfo.jsx +1 -1
  4. package/client/src/App.jsx +2 -1
  5. package/client/src/App.scss +1635 -1626
  6. package/client/src/AssistantChat.jsx +1 -0
  7. package/client/src/CalendarView.jsx +1 -0
  8. package/client/src/ContentView.jsx +3 -3
  9. package/client/src/Dashboard.jsx +5 -2
  10. package/client/src/DashboardChart.jsx +1 -0
  11. package/client/src/DashboardFlexViewItem.jsx +1 -0
  12. package/client/src/DashboardHtmlViewItem.jsx +1 -0
  13. package/client/src/DashboardView.jsx +2 -0
  14. package/client/src/DataEditor.jsx +0 -1
  15. package/client/src/DataImporter.jsx +489 -468
  16. package/client/src/DataLayout.jsx +6 -3
  17. package/client/src/DataTable.jsx +4 -3
  18. package/client/src/Dialog.jsx +92 -90
  19. package/client/src/Dialog.scss +122 -116
  20. package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
  21. package/client/src/FlexBuilderControls.jsx +1 -0
  22. package/client/src/FlexBuilderPreview.jsx +1 -1
  23. package/client/src/FlexNode.jsx +1 -1
  24. package/client/src/HistoryDialog.jsx +3 -2
  25. package/client/src/KPIWidget.jsx +1 -1
  26. package/client/src/KanbanView.jsx +1 -0
  27. package/client/src/ModelCreator.jsx +4 -0
  28. package/client/src/ModelList.jsx +1 -0
  29. package/client/src/PackGallery.jsx +5 -4
  30. package/client/src/RTETrans.jsx +1 -1
  31. package/client/src/RelationField.jsx +2 -0
  32. package/client/src/RelationSelectorWidget.jsx +2 -0
  33. package/client/src/RelationValue.jsx +1 -0
  34. package/client/src/RestoreDialog.jsx +1 -0
  35. package/client/src/WorkflowEditor.jsx +3 -0
  36. package/client/src/contexts/CommandContext.jsx +2 -1
  37. package/client/src/contexts/ModelContext.jsx +3 -3
  38. package/client/src/hooks/data.js +1 -0
  39. package/client/src/hooks/useValidation.js +1 -0
  40. package/client/src/translations.js +24 -24
  41. package/doc/AI-assistance.md +87 -63
  42. package/doc/Concepts.md +122 -0
  43. package/doc/Custom-Endpoints.md +31 -0
  44. package/doc/Event-system.md +13 -14
  45. package/doc/Home.md +33 -0
  46. package/doc/Modules.md +83 -0
  47. package/doc/Packs-gallery.md +8 -23
  48. package/doc/Workflows.md +32 -0
  49. package/doc/automation-workflows.md +141 -102
  50. package/doc/dashboards-kpis-charts.md +47 -49
  51. package/doc/data-management.md +126 -120
  52. package/doc/data-models.md +68 -75
  53. package/doc/roles-permissions.md +144 -43
  54. package/doc/sharding-replication.md +158 -0
  55. package/doc/users.md +54 -30
  56. package/package.json +1 -1
  57. package/server.js +37 -37
  58. package/src/constants.js +7 -8
  59. package/src/data.js +521 -520
  60. package/src/email.js +157 -154
  61. package/src/engine.js +117 -7
  62. package/src/gameObject.js +6 -0
  63. package/src/i18n.js +0 -1
  64. package/src/modules/auth-google/index.js +53 -50
  65. package/src/modules/bucket.js +346 -339
  66. package/src/modules/data/data.backup.js +400 -376
  67. package/src/modules/data/data.cluster.js +410 -42
  68. package/src/modules/data/data.operations.js +3666 -3635
  69. package/src/modules/data/data.replication.js +106 -64
  70. package/src/modules/data/data.routes.js +87 -64
  71. package/src/modules/data/data.scheduling.js +2 -1
  72. package/src/modules/file.js +248 -247
  73. package/src/modules/user.js +11 -1
  74. package/src/sso.js +91 -25
  75. package/test/cluster.test.js +221 -0
  76. package/test/core.test.js +0 -2
  77. package/test/replication.test.js +163 -0
  78. package/doc/core-concepts.md +0 -33
package/src/email.js CHANGED
@@ -1,155 +1,158 @@
1
- import process from "node:process";
2
- import nodemailer from "nodemailer";
3
- import juice from "juice";
4
- import {Event} from "./events.js";
5
- import {emailDefaultConfig} from "./constants.js";
6
- import {Config} from "./config.js";
7
-
8
- // Le transporteur par défaut, utilisé si aucune config spécifique n'est fournie.
9
- const defaultTransporter = nodemailer.createTransport({
10
- host: process.env.SMTP_HOST,
11
- port: process.env.SMTP_PORT || 587,
12
- secure: false,
13
- auth: {
14
- user: process.env.SMTP_USER,
15
- pass: process.env.SMTP_PASS
16
- }
17
- });
18
-
19
- /**
20
- * Crn * @param {object} smtpConfig - L'objet de configuration SMTP.
21
- * @returns {import('nodemailer').Transporter} Un transporteur Nodemailer.
22
- */
23
- const createTransporter = (smtpConfig) => {
24
- // Valider que les informations de base sont présentes
25
- if (!smtpConfig.host || !smtpConfig.port || !smtpConfig.user || !smtpConfig.pass){
26
- throw new Error("SMTP configuration incomplete ('SMTP_HOST', 'SMTP_PORT','SMTP_USER', and 'SMTP_PASS' keys need to be completed in env').");
27
- }
28
- return nodemailer.createTransport({
29
- host: smtpConfig.host,
30
- port: parseInt(smtpConfig.port, 10),
31
- secure: smtpConfig.secure === true, // Par défaut à false si non défini
32
- auth: {
33
- user: smtpConfig.user,
34
- pass: smtpConfig.pass
35
- }
36
- });
37
- };
38
-
39
-
40
- /**
41
- * Envoie un email en utilisant une configuration SMTP spécifique ou celle par défaut.
42
- * @param {string|string[]} email - Le ou les destinataires.
43
- * @param {object} data - Les données de l'email (title, content).
44
- * @param {object|null} smtpConfig - La configuration SMTP à utiliser. Si null, utilise la configuration par défaut.
45
- * @param {string} lang - La langue pour le template.
46
- * @param {string|null} tpl - Le template HTML à utiliser.
47
- */
48
- export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl = null) => {
49
-
50
- const cfg = Config.Get('emailDefaultConfig', emailDefaultConfig);
51
- const contactEmail = smtpConfig ? (smtpConfig.from || cfg.from) :"Our company <noreply@ourdomain.tld>";
52
- const emails = Array.isArray(email) ? email : [email];
53
- if (emails.length === 0) return false;
54
-
55
- // Choisir le transporteur à utiliser
56
- const transporter = smtpConfig ? createTransporter(smtpConfig||cfg) : defaultTransporter;
57
-
58
- if (tpl === null) tpl = await Event.Trigger("OnEmailTemplate", "event", "system", data, lang);
59
- let html = tpl;
60
- try {
61
- html = juice(tpl);
62
- } catch (e) {
63
- console.error("Erreur lors de l'inlining du CSS de l'email :", e);
64
- }
65
-
66
- // Utiliser Promise.all pour gérer les envois de manière asynchrone
67
- const sendPromises = emails.map((e) => {
68
- const mailOptions = {
69
- from: contactEmail,
70
- to: e,
71
- subject: data.title,
72
- html
73
- };
74
- return transporter.sendMail(mailOptions);
75
- });
76
-
77
- try {
78
- await Promise.all(sendPromises);
79
- console.log(`Email(s) sent successfully to : ${emails.join(', ')}`);
80
- return true;
81
- } catch (error) {
82
- console.error("Error when sending to one ore more emails :", error);
83
- // Vous pouvez relancer l'erreur si vous voulez que l'appelant la gère
84
- throw error;
85
- }
86
- };
87
-
88
- export const etag = (name, attrs, content = "") => {
89
- return (
90
- "<" +
91
- name +
92
- " " +
93
- Object.keys(attrs)
94
- .map((m) => m + '="' + attrs[m] + '" ')
95
- .join("")
96
- .trim() +
97
- ">" +
98
- content +
99
- "</" +
100
- name +
101
- ">"
102
- );
103
- };
104
-
105
- export const ebutton = (content = "", attrs = {}, style = {}) => {
106
- return etable(
107
- erow(
108
- '<a href="' +
109
- attrs["href"] +
110
- '" style="' +
111
- estyle(style) +
112
- '">' +
113
- content +
114
- "</a>"
115
- )
116
- );
117
- };
118
-
119
- export const etable = (content = "", style = {}) => {
120
- return (
121
- '<table border="0" cellPadding="0" cellSpacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; box-sizing: border-box; ' +
122
- (style.width === "100%" ? "min-width: 100%;" : "") +
123
- " !important; " +
124
- estyle(style) +
125
- '" width="' +
126
- (style.width || "100%") +
127
- '">' +
128
- content +
129
- "</table>"
130
- );
131
- };
132
-
133
- export const erow = (content = "", style = {}) => {
134
- const contents = !Array.isArray(content) ? [content] : content;
135
- return (
136
- "<tr>" +
137
- contents
138
- .map(
139
- (c) =>
140
- '<td align="center" style="' +
141
- estyle(style) +
142
- '" valign="top">' +
143
- c +
144
- "</td>"
145
- )
146
- .join("") +
147
- "</tr>"
148
- );
149
- };
150
-
151
- export const estyle = (options) => {
152
- return Object.keys(options)
153
- .map((m) => m + ": " + options[m] + ";")
154
- .join("");
1
+ import nodemailer from "nodemailer";
2
+ import juice from "juice";
3
+ import {Event} from "./events.js";
4
+ import {emailDefaultConfig} from "./constants.js";
5
+ import {Config} from "./config.js";
6
+ import i18n from "./i18n.js";
7
+ import {websiteTranslations} from "../client/src/translations.js";
8
+ import i18next from "i18next";
9
+
10
+ /**
11
+ * Crn * @param {object} smtpConfig - L'objet de configuration SMTP.
12
+ * @returns {import('nodemailer').Transporter} Un transporteur Nodemailer.
13
+ */
14
+ const createTransporter = (smtpConfig) => {
15
+ // Valider que les informations de base sont présentes
16
+ if (!smtpConfig.host || !smtpConfig.port || !smtpConfig.user || !smtpConfig.pass){
17
+ throw new Error("SMTP configuration incomplete ('SMTP_HOST', 'SMTP_PORT','SMTP_USER', and 'SMTP_PASS' keys need to be completed in env').");
18
+ }
19
+ return nodemailer.createTransport({
20
+ host: smtpConfig.host,
21
+ port: parseInt(smtpConfig.port, 10),
22
+ secure: smtpConfig.secure === true, // Par défaut à false si non défini
23
+ auth: {
24
+ user: smtpConfig.user,
25
+ pass: smtpConfig.pass
26
+ }
27
+ });
28
+ };
29
+
30
+
31
+
32
+ export const changeLanguage = async (lang) => {
33
+ // --- NOUVELLE LOGIQUE DE GESTION DE LANGUE ---
34
+ await i18n.changeLanguage(lang, (err) => {
35
+ if (err) return console.error('something went wrong loading', err);
36
+ const trs = {...websiteTranslations[lang]?.['translation']};
37
+ i18next.addResourceBundle(lang, 'translation', trs, true);
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Envoie un email en utilisant une configuration SMTP spécifique ou celle par défaut.
43
+ * @param {string|string[]} email - Le ou les destinataires.
44
+ * @param {object} data - Les données de l'email (title, content).
45
+ * @param {object|null} smtpConfig - La configuration SMTP à utiliser. Si null, utilise la configuration par défaut.
46
+ * @param {string} lang - La langue pour le template.
47
+ * @param {string|null} tpl - Le template HTML à utiliser.
48
+ */
49
+ export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl = null) => {
50
+
51
+ const cfg = Config.Get('emailDefaultConfig', emailDefaultConfig);
52
+ const contactEmail = smtpConfig ? (smtpConfig.from || cfg.from) :"Our company <noreply@ourdomain.tld>";
53
+ const emails = Array.isArray(email) ? email : [email];
54
+ if (emails.length === 0) return false;
55
+
56
+ // Choisir le transporteur à utiliser
57
+ const transporter = createTransporter(smtpConfig || cfg); // Always create transporter dynamically
58
+
59
+ if (tpl === null) tpl = await Event.Trigger("OnEmailTemplate", "event", "system", data, lang);
60
+ let html = tpl;
61
+ try {
62
+ html = juice(tpl);
63
+ } catch (e) {
64
+ console.error("Erreur lors de l'inlining du CSS de l'email :", e);
65
+ }
66
+
67
+ // Utiliser Promise.all pour gérer les envois de manière asynchrone
68
+ const sendPromises = emails.map((e) => {
69
+ const mailOptions = {
70
+ from: contactEmail,
71
+ to: e,
72
+ subject: data.title,
73
+ html
74
+ };
75
+ return transporter.sendMail(mailOptions);
76
+ });
77
+
78
+ try {
79
+ await Promise.all(sendPromises);
80
+ console.log(`Email(s) sent successfully to : ${emails.join(', ')}`);
81
+ return true;
82
+ } catch (error) {
83
+ console.error("Error when sending to one ore more emails :", error);
84
+ // Vous pouvez relancer l'erreur si vous voulez que l'appelant la gère
85
+ throw error;
86
+ } finally {
87
+
88
+ }
89
+ };
90
+
91
+ export const etag = (name, attrs, content = "") => {
92
+ return (
93
+ "<" +
94
+ name +
95
+ " " +
96
+ Object.keys(attrs)
97
+ .map((m) => m + '="' + attrs[m] + '" ')
98
+ .join("")
99
+ .trim() +
100
+ ">" +
101
+ content +
102
+ "</" +
103
+ name +
104
+ ">"
105
+ );
106
+ };
107
+
108
+ export const ebutton = (content = "", attrs = {}, style = {}) => {
109
+ return etable(
110
+ erow(
111
+ '<a href="' +
112
+ attrs["href"] +
113
+ '" style="' +
114
+ estyle(style) +
115
+ '">' +
116
+ content +
117
+ "</a>"
118
+ )
119
+ );
120
+ };
121
+
122
+ export const etable = (content = "", style = {}) => {
123
+ return (
124
+ '<table border="0" cellPadding="0" cellSpacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; box-sizing: border-box; ' +
125
+ (style.width === "100%" ? "min-width: 100%;" : "") +
126
+ " !important; " +
127
+ estyle(style) +
128
+ '" width="' +
129
+ (style.width || "100%") +
130
+ '">' +
131
+ content +
132
+ "</table>"
133
+ );
134
+ };
135
+
136
+ export const erow = (content = "", style = {}) => {
137
+ const contents = !Array.isArray(content) ? [content] : content;
138
+ return (
139
+ "<tr>" +
140
+ contents
141
+ .map(
142
+ (c) =>
143
+ '<td align="center" style="' +
144
+ estyle(style) +
145
+ '" valign="top">' +
146
+ c +
147
+ "</td>"
148
+ )
149
+ .join("") +
150
+ "</tr>"
151
+ );
152
+ };
153
+
154
+ export const estyle = (options) => {
155
+ return Object.keys(options)
156
+ .map((m) => m + ": " + options[m] + ";")
157
+ .join("");
155
158
  };
package/src/engine.js CHANGED
@@ -172,6 +172,111 @@ export const Engine = {
172
172
  return engine._modules.find(m => m.module === module);
173
173
  };
174
174
 
175
+ /**
176
+ * Envoie une requête à un pair spécifique du cluster.
177
+ * Cette fonction gère la recherche du pair, la construction de l'URL,
178
+ * et l'ajout du header 'X-Target-Peer-Id' nécessaire pour le reverse proxy.
179
+ * @param {string} peerId - L'ID du pair cible (ex: "vox-main-1").
180
+ * @param {string} path - Le chemin de l'API à appeler sur le pair (ex: "/api/internal/replicate").
181
+ * @param {object} payload - Le corps de la requête (sera converti en JSON).
182
+ * @param {object} [options={}] - Options supplémentaires pour la requête fetch (method, headers, etc.).
183
+ * @returns {Promise<Response>} La promesse retournée par fetch.
184
+ */
185
+ engine.sendToPeer = async (peerId, path, payload, options = {}) => {
186
+ const targetPeer = engine.peers.find(p => p.id === peerId);
187
+
188
+ if (!targetPeer) {
189
+ const errorMessage = `[sendToPeer] Cannot send request: Peer '${peerId}' is not in the list of online peers.`;
190
+ logger.error(errorMessage);
191
+ return Promise.reject(new Error(errorMessage));
192
+ }
193
+
194
+ // Utilise le protocole défini dans les données du pair, ou https par défaut.
195
+ const protocol = targetPeer.protocol || 'https';
196
+ const apiPath = path.startsWith('/') ? path : `/${path}`;
197
+ const url = `${protocol}://${targetPeer.public_domain}${apiPath}`;
198
+
199
+ const fetchOptions = {
200
+ method: 'POST', // POST par défaut
201
+ ...options,
202
+ headers: {
203
+ 'Content-Type': 'application/json',
204
+ 'X-Target-Peer-Id': peerId, // Le header crucial pour le routage
205
+ ...(options.headers || {})
206
+ },
207
+ body: JSON.stringify(payload)
208
+ };
209
+
210
+ try {
211
+ logger.info(`[sendToPeer] Sending request to peer '${peerId}' at ${url}`);
212
+ return await fetch(url, fetchOptions);
213
+ } catch (error) {
214
+ logger.error(`[sendToPeer] Network error while sending to peer '${peerId}':`, error.message);
215
+ throw error; // Relancer l'erreur pour que l'appelant puisse la gérer
216
+ }
217
+ };
218
+
219
+ engine.peers = []; // Initialise la liste des pairs
220
+
221
+ const discoverPeers = async () => {
222
+ const endpoint = process.env.PEERS_ENDPOINT;
223
+ if (!endpoint) {
224
+ logger.info("PEERS_ENDPOINT not set. Skipping peer discovery.");
225
+ return;
226
+ }
227
+
228
+ try {
229
+ logger.info(`Discovering peers from ${endpoint}...`);
230
+ // Ajout d'un AbortController pour gérer le timeout
231
+ const controller = new AbortController();
232
+ const timeoutId = setTimeout(() => controller.abort(), 30000); // Timeout de 30 secondes
233
+
234
+ const response = await fetch(endpoint, { signal: controller.signal });
235
+ clearTimeout(timeoutId); // Annuler le timeout si la requête réussit
236
+
237
+ if (!response.ok) {
238
+ throw new Error(`Failed to fetch peers: ${response.status} ${response.statusText}`);
239
+ }
240
+ const data = await response.json(); // Parser en JSON
241
+
242
+ if (!data || !Array.isArray(data.peers)) { // Valider la structure
243
+ throw new Error("Invalid peer discovery response: 'peers' array not found.");
244
+ }
245
+
246
+ const allOnlinePeers = data.peers.filter(p => (p.status === 'online' || p.public_domain.startsWith(process.env.PEER_DOMAIN)));
247
+
248
+ // --- NOUVELLE LOGIQUE DE FILTRAGE PAR PRÉFIXE ---
249
+ // On se base sur le nom de domaine public de l'instance pour plus de robustesse.
250
+ if (process.env.PEER_DOMAIN) {
251
+ engine.selfUrl = process.env.PEER_DOMAIN;
252
+ const selfHostname = process.env.PEER_DOMAIN; // ex: data-api-shard-1.primals.net
253
+ logger.info(`[Cluster] Self hostname identified as '${selfHostname}'.`);
254
+ // --- CORRECTION DE LA LOGIQUE DE PRÉFIXE ---
255
+ // Extrait le préfixe du nom de domaine (ex: "data-api-shard" de "data-api-shard-1.primals.net" ou "data" de "data.primals.net")
256
+ // La regex capture tout jusqu'au premier point.
257
+ const selfPrefixMatch = selfHostname.match(/^([^.]+)/);
258
+ if (selfPrefixMatch) {
259
+ const selfPrefix = selfPrefixMatch[1].replace(/-[0-9]+$/, ''); // Supprime le suffixe numérique s'il existe
260
+ logger.info(`[Cluster] Detected prefix: '${selfPrefix}'. Filtering peers...`);
261
+
262
+ // Ne garder que les pairs qui ont le même préfixe
263
+ engine.peers = allOnlinePeers.filter(p => {
264
+ return (p.public_domain || '').startsWith(selfPrefix);
265
+ });
266
+ } else {
267
+ engine.peers = allOnlinePeers; // Pas de préfixe, on garde tout
268
+ }
269
+ } else {
270
+ engine.peers = allOnlinePeers; // Pas de selfUrl, on garde tout pour éviter une erreur
271
+ }
272
+
273
+ logger.info(`[Cluster] Final peer list: [${engine.peers.map(p => p.id).join(', ')}]`);
274
+ } catch (error) {
275
+ logger.error(`Could not discover peers from endpoint ${endpoint}:`, error.message);
276
+ engine.peers = []; // En cas d'erreur, on s'assure que la liste est vide.
277
+ }
278
+ };
279
+
175
280
  const importAndPrepareModule = async (moduleEntryPoint, moduleName) => {
176
281
  const moduleA = await import(moduleEntryPoint);
177
282
 
@@ -273,27 +378,30 @@ export const Engine = {
273
378
  // Start http server
274
379
  server = http.createServer(app);
275
380
 
381
+ await discoverPeers();
382
+
276
383
  // Server Timeout Settings
277
384
  server.timeout = 120000;
278
385
  server.headersTimeout = 20000;
279
386
  server.requestTimeout = 30000;
280
387
  server.keepAliveTimeout = 5000;
281
-
282
388
  server.listen(port);
283
389
 
284
390
  await setupInitialModels();
285
391
  await installAllPacks();
286
392
 
287
- if (cb)
288
- await cb();
289
-
290
393
  engine.get('/api/health', (req, res) => {
291
394
  res.status(200).json({
292
395
  status: 'ok',
293
396
  timestamp: new Date().toISOString()
294
397
  });
295
398
  });
296
-
399
+
400
+ // --- CORRECTION DE L'ORDRE DES MIDDLEWARES ---
401
+ // Les routes API doivent être enregistrées AVANT le serveur de fichiers statiques.
402
+ await Event.Trigger("OnServerStart", "event", "system", engine);
403
+
404
+ // Le serveur de fichiers statiques doit être le DERNIER middleware "catch-all".
297
405
  if( fs.existsSync('client/dist') ){
298
406
  app.use(sirv('client/dist', {
299
407
  single: true,
@@ -301,6 +409,10 @@ export const Engine = {
301
409
  }));
302
410
  }
303
411
 
412
+ if (cb) {
413
+ await cb();
414
+ }
415
+
304
416
  process.on('uncaughtException', function (exception) {
305
417
  console.error(exception);
306
418
  fs.appendFile('issues.txt', JSON.stringify({ code: exception.code, message: exception.message, stack: exception.stack }), function (err) {
@@ -310,8 +422,6 @@ export const Engine = {
310
422
  });
311
423
  process.exit(1);
312
424
  });
313
-
314
- await Event.Trigger("OnServerStart", "event", "system", engine);
315
425
  }
316
426
 
317
427
  engine.stop = async () => {
package/src/gameObject.js CHANGED
@@ -48,6 +48,12 @@ export class Behaviour {
48
48
  // Logique de nettoyage du comportement
49
49
  }
50
50
  }
51
+ export class DataHandler extends Behaviour {
52
+ constructor(gameObject) {
53
+ super(gameObject);
54
+ }
55
+ }
56
+
51
57
  // MovableBehavior.js
52
58
  export class MovableBehaviour extends Behaviour {
53
59
  constructor(gameObject, speed = 5) {
package/src/i18n.js CHANGED
@@ -328,7 +328,6 @@ export const translations = {
328
328
  "field_workflowRun_startedAt": "Démarré le",
329
329
  "field_workflowRun_completedAt": "Terminé le",
330
330
 
331
-
332
331
  "model_budget": "Budget",
333
332
  "model_request": "Requêtes",
334
333
  "model_imageGallery": "Galerie d'images",
@@ -1,51 +1,54 @@
1
- import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
2
- import { Sso, SSOUserProvider } from '../../sso.js';
3
- import {Logger} from "../../gameObject.js";
4
-
5
- let logger;
6
-
7
- export async function onInit(engine) {
8
- logger = engine.getComponent(Logger);
9
-
10
- // Le SSOUserProvider est nécessaire pour l'initialisation et pour la stratégie.
11
- // On le crée donc en premier.
12
- const ssoUserProvider = new SSOUserProvider(engine);
13
-
14
- // 1. Récupérer le composant PassportAuth central.
15
- let passportAuth = engine.getComponent(Sso);
16
- if (!passportAuth) {
17
- passportAuth = engine.addComponent(Sso);
18
- // C'EST ICI QUE L'INITIALISATION DOIT AVOIR LIEU, UNE SEULE FOIS.
19
- passportAuth.initialize({ ssoUserProvider });
20
- passportAuth.addLogoutRoute();
21
- logger.info("[auth-google] Sso component was not found, created and initialized a new one.");
22
- }
23
-
24
- // 2. Vérifier que les variables d'environnement nécessaires sont présentes.
25
- if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) {
26
- logger.warn("[auth-google] GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET are not set. Google SSO will be disabled.");
27
- return;
28
- }
29
-
30
- // 3. Créer l'instance de la stratégie Google.
31
- const googleStrategy = new GoogleStrategy({
32
- clientID: process.env.GOOGLE_CLIENT_ID,
33
- clientSecret: process.env.GOOGLE_CLIENT_SECRET,
34
- callbackURL: "/api/auth/google/callback"
35
- },
36
- async (accessToken, refreshToken, profile, done) => {
37
- try {
38
- // On utilise le SSOUserProvider pour trouver ou créer l'utilisateur.
39
- const user = await ssoUserProvider.findOrCreate(profile);
40
- return done(null, user);
41
- } catch (err) {
42
- return done(err);
43
- }
44
- });
45
-
46
- // 4. Enregistrer la stratégie auprès du composant PassportAuth.
47
- passportAuth.addStrategy('google', googleStrategy,
48
- { authPath: '/api/auth/google', callbackPath: '/api/auth/google/callback' },
49
- { scope: ['profile', 'email'] }
50
- );
1
+ import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
2
+ import { Sso, SSOUserProvider } from '../../sso.js';
3
+ import {Logger} from "../../gameObject.js";
4
+ import passport from "passport";
5
+
6
+ let logger;
7
+
8
+ export async function onInit(engine) {
9
+ logger = engine.getComponent(Logger);
10
+
11
+ // Le SSOUserProvider est nécessaire pour l'initialisation et pour la stratégie.
12
+ // On le crée donc en premier.
13
+ const ssoUserProvider = new SSOUserProvider(engine);
14
+
15
+ // 1. Récupérer le composant PassportAuth central.
16
+ let ssoComponent = engine.getComponent(Sso);
17
+ if (!ssoComponent) {
18
+ ssoComponent = engine.addComponent(Sso);
19
+ // C'EST ICI QUE L'INITIALISATION DOIT AVOIR LIEU, UNE SEULE FOIS.
20
+ ssoComponent.initialize({ ssoUserProvider });
21
+ ssoComponent.addLogoutRoute();
22
+ logger.info("[auth-google] Sso component was not found, created and initialized a new one.");
23
+ }
24
+
25
+ // 2. Vérifier que les variables d'environnement nécessaires sont présentes.
26
+ if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) {
27
+ logger.warn("[auth-google] GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET are not set. Google SSO will be disabled.");
28
+ return;
29
+ }
30
+
31
+ // 3. Créer l'instance de la stratégie Google.
32
+ const googleStrategy = new GoogleStrategy({
33
+ clientID: process.env.GOOGLE_CLIENT_ID,
34
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
35
+ callbackURL: "/api/auth/google/callback"
36
+ },
37
+ async (accessToken, refreshToken, profile, done) => {
38
+ try {
39
+ const user = await ssoUserProvider.findOrCreate(profile);
40
+ // On passe le profil brut dans le 3ème argument pour le récupérer dans le callback de `authenticate`
41
+ // et le passer à l'événement OnSsoLogin.
42
+ return done(null, user, { profile });
43
+ } catch (err) {
44
+ return done(err);
45
+ }
46
+ });
47
+
48
+ // 4. Enregistrer la stratégie auprès du composant SSO.
49
+ // Le composant Sso gère lui-même les routes, le callback, req.logIn() et le déclenchement de l'événement.
50
+ ssoComponent.addStrategy('google', googleStrategy,
51
+ { authPath: '/api/auth/google', callbackPath: '/api/auth/google/callback' },
52
+ { scope: ['profile', 'email'] }
53
+ );
51
54
  }