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.
- package/README.md +160 -160
- package/client/package-lock.json +8430 -8430
- package/client/src/APIInfo.jsx +1 -1
- package/client/src/App.jsx +2 -1
- package/client/src/App.scss +1635 -1626
- package/client/src/AssistantChat.jsx +1 -0
- package/client/src/CalendarView.jsx +1 -0
- package/client/src/ContentView.jsx +3 -3
- package/client/src/Dashboard.jsx +5 -2
- package/client/src/DashboardChart.jsx +1 -0
- package/client/src/DashboardFlexViewItem.jsx +1 -0
- package/client/src/DashboardHtmlViewItem.jsx +1 -0
- package/client/src/DashboardView.jsx +2 -0
- package/client/src/DataEditor.jsx +0 -1
- package/client/src/DataImporter.jsx +489 -468
- package/client/src/DataLayout.jsx +6 -3
- package/client/src/DataTable.jsx +4 -3
- package/client/src/Dialog.jsx +92 -90
- package/client/src/Dialog.scss +122 -116
- package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
- package/client/src/FlexBuilderControls.jsx +1 -0
- package/client/src/FlexBuilderPreview.jsx +1 -1
- package/client/src/FlexNode.jsx +1 -1
- package/client/src/HistoryDialog.jsx +3 -2
- package/client/src/KPIWidget.jsx +1 -1
- package/client/src/KanbanView.jsx +1 -0
- package/client/src/ModelCreator.jsx +4 -0
- package/client/src/ModelList.jsx +1 -0
- package/client/src/PackGallery.jsx +5 -4
- package/client/src/RTETrans.jsx +1 -1
- package/client/src/RelationField.jsx +2 -0
- package/client/src/RelationSelectorWidget.jsx +2 -0
- package/client/src/RelationValue.jsx +1 -0
- package/client/src/RestoreDialog.jsx +1 -0
- package/client/src/WorkflowEditor.jsx +3 -0
- package/client/src/contexts/CommandContext.jsx +2 -1
- package/client/src/contexts/ModelContext.jsx +3 -3
- package/client/src/hooks/data.js +1 -0
- package/client/src/hooks/useValidation.js +1 -0
- package/client/src/translations.js +24 -24
- package/doc/AI-assistance.md +87 -63
- package/doc/Concepts.md +122 -0
- package/doc/Custom-Endpoints.md +31 -0
- package/doc/Event-system.md +13 -14
- package/doc/Home.md +33 -0
- package/doc/Modules.md +83 -0
- package/doc/Packs-gallery.md +8 -23
- package/doc/Workflows.md +32 -0
- package/doc/automation-workflows.md +141 -102
- package/doc/dashboards-kpis-charts.md +47 -49
- package/doc/data-management.md +126 -120
- package/doc/data-models.md +68 -75
- package/doc/roles-permissions.md +144 -43
- package/doc/sharding-replication.md +158 -0
- package/doc/users.md +54 -30
- package/package.json +1 -1
- package/server.js +37 -37
- package/src/constants.js +7 -8
- package/src/data.js +521 -520
- package/src/email.js +157 -154
- package/src/engine.js +117 -7
- package/src/gameObject.js +6 -0
- package/src/i18n.js +0 -1
- package/src/modules/auth-google/index.js +53 -50
- package/src/modules/bucket.js +346 -339
- package/src/modules/data/data.backup.js +400 -376
- package/src/modules/data/data.cluster.js +410 -42
- package/src/modules/data/data.operations.js +3666 -3635
- package/src/modules/data/data.replication.js +106 -64
- package/src/modules/data/data.routes.js +87 -64
- package/src/modules/data/data.scheduling.js +2 -1
- package/src/modules/file.js +248 -247
- package/src/modules/user.js +11 -1
- package/src/sso.js +91 -25
- package/test/cluster.test.js +221 -0
- package/test/core.test.js +0 -2
- package/test/replication.test.js +163 -0
- package/doc/core-concepts.md +0 -33
package/src/email.js
CHANGED
|
@@ -1,155 +1,158 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
* @param {
|
|
44
|
-
* @param {object
|
|
45
|
-
* @param {
|
|
46
|
-
* @param {string
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const user = await ssoUserProvider.findOrCreate(profile);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return done(
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
}
|