data-primals-engine 1.0.3 → 1.0.5
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 +13 -9
- package/package.json +2 -3
- package/server.js +2 -28
- package/src/constants.js +4 -0
- package/src/data.js +9 -2
- package/src/email.js +3 -202
- package/src/index.js +1 -1
- package/src/modules/assistant.js +41 -15
- package/src/modules/bucket.js +5 -11
- package/src/modules/data.js +1 -175
- package/src/modules/file.js +1 -1
- package/src/modules/user.js +1 -1
- package/src/modules/workflow.js +1 -1
- package/src/providers.js +13 -0
- package/src/tutorials.js +0 -112
package/README.md
CHANGED
|
@@ -30,13 +30,16 @@
|
|
|
30
30
|
|
|
31
31
|
## ⚡ Quick Start
|
|
32
32
|
|
|
33
|
+
```bash
|
|
34
|
+
npm i data-primals-engine
|
|
35
|
+
```
|
|
36
|
+
or
|
|
33
37
|
```bash
|
|
34
38
|
git clone https://your-repo/data-primals-engine.git
|
|
35
39
|
cd data-primals-engine
|
|
36
40
|
npm install
|
|
37
41
|
```
|
|
38
|
-
|
|
39
|
-
Create a `.env` file:
|
|
42
|
+
Possibly create a `.env` file:
|
|
40
43
|
```env
|
|
41
44
|
MONGO_DB_URL=mongodb://127.0.0.1:27017
|
|
42
45
|
```
|
|
@@ -197,14 +200,15 @@ curl -X DELETE http://localhost:7633/api/data?_user=demo \
|
|
|
197
200
|
```
|
|
198
201
|
data-primals-engine/
|
|
199
202
|
├── src/
|
|
200
|
-
│ ├──
|
|
203
|
+
│ ├── middlewares/
|
|
204
|
+
│ ├── migrations/
|
|
201
205
|
│ ├── modules/
|
|
202
|
-
│ ├──
|
|
203
|
-
│ ├──
|
|
204
|
-
│ ├──
|
|
205
|
-
├──
|
|
206
|
-
│ ├── models
|
|
207
|
-
│
|
|
206
|
+
│ ├── workers/
|
|
207
|
+
│ ├── engine.js // The Express engine that serves the API
|
|
208
|
+
│ ├── constants.js // The inner-application constants definitions
|
|
209
|
+
│ ├── packs.js // The packs that will be loaded and available with installPack() method
|
|
210
|
+
│ ├── defaultModels.js // The default models available to import
|
|
211
|
+
│ ├── ...
|
|
208
212
|
└── server.js
|
|
209
213
|
```
|
|
210
214
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-primals-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "data-primals-engine is the package responsible from handling large amount of data in a practical and performant way. It can handle large amount of data in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation.",
|
|
5
5
|
"main": "src/engine.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"preinstall": "npx
|
|
8
|
+
"preinstall": "npx force-resolutions",
|
|
9
9
|
"devserver": "cross-env NODE_ENV=development node server.js",
|
|
10
10
|
"server": "cross-env NODE_ENV=production node server.js",
|
|
11
11
|
"migrate:create": "node src/migrate.js create",
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"express-csrf-double-submit-cookie": "^1.2.1",
|
|
49
49
|
"express-rate-limit": "^8.0.1",
|
|
50
50
|
"express-session": "^1.18.1",
|
|
51
|
-
"express-sitemap-xml": "^3.1.0",
|
|
52
51
|
"juice": "^11.0.1",
|
|
53
52
|
"mathjs": "^14.4.0",
|
|
54
53
|
"mongodb": "^6.12.0",
|
package/server.js
CHANGED
|
@@ -4,34 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
//set ES modules to be loaded by the script
|
|
6
6
|
import process from "node:process";
|
|
7
|
-
import {Config} from "
|
|
8
|
-
|
|
9
|
-
import {BenchmarkTool, GameObject, Logger} from "../data-primals-engine/src/gameObject.js";
|
|
10
|
-
import {
|
|
11
|
-
createModel, DATAS_API_TOKEN,
|
|
12
|
-
getModels, getResource,
|
|
13
|
-
searchData,
|
|
14
|
-
validateModelStructure
|
|
15
|
-
} from "../data-primals-engine/src/modules/data.js";
|
|
16
|
-
import {availableLangs, install, langs, maxBytesPerSecondThrottleFile} from "../data-primals-engine/src/constants.js";
|
|
17
|
-
import fs from "node:fs";
|
|
18
|
-
import {
|
|
19
|
-
event_on, getFileExtension,
|
|
20
|
-
shuffle,
|
|
21
|
-
uuidv4
|
|
22
|
-
} from "../data-primals-engine/src/core.js";
|
|
23
|
-
import {datasCollection, filesCollection, modelsCollection, packsCollection} from "../data-primals-engine/src/modules/mongodb.js";
|
|
24
|
-
import {translations} from "../data-primals-engine/src/i18n.js";
|
|
25
|
-
import {getFieldValueHash} from "../data-primals-engine/src/data.js";
|
|
26
|
-
|
|
27
|
-
import swaggerUi from 'swagger-ui-express';
|
|
28
|
-
|
|
29
|
-
import YAML from 'yaml';
|
|
30
|
-
import util from "node:util";
|
|
31
|
-
import path from "node:path";
|
|
32
|
-
import { getAllPacks} from "../data-primals-engine/src/packs.js";
|
|
33
|
-
import {middlewareAuthenticator, userInitiator} from "../data-primals-engine/src/modules/user.js";
|
|
34
|
-
import {defaultModels} from "../data-primals-engine/src/defaultModels.js";
|
|
7
|
+
import {Config, Engine, BenchmarkTool, GameObject, Logger} from "./src/index.js";
|
|
8
|
+
|
|
35
9
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant", "swagger"])
|
|
36
10
|
Config.Set("middlewares", []);
|
|
37
11
|
|
package/src/constants.js
CHANGED
|
@@ -40,6 +40,10 @@ export const awsDefaultConfig = {
|
|
|
40
40
|
region: 'eu-north-1',
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export const emailDefaultConfig = {
|
|
44
|
+
from: "Support - data@primals.net <data@primals.net>"
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
// 10000 tiny users
|
|
44
48
|
// 1000 modern users
|
|
45
49
|
// 100 mega utilisateurs potentiality
|
package/src/data.js
CHANGED
|
@@ -420,6 +420,7 @@ export const getFieldValueHash = (model, data) => {
|
|
|
420
420
|
* @returns {string} - La chaîne de caractères représentant la donnée.
|
|
421
421
|
*/
|
|
422
422
|
export const getDataAsString = (model, data, t, allModels, extended=false) => {
|
|
423
|
+
|
|
423
424
|
// Cas de base : si le modèle ou les données sont manquants, on ne peut rien faire.
|
|
424
425
|
if (!model || !data) {
|
|
425
426
|
return '';
|
|
@@ -484,8 +485,14 @@ export const getDataAsString = (model, data, t, allModels, extended=false) => {
|
|
|
484
485
|
return value.value; // Champs traduits
|
|
485
486
|
}
|
|
486
487
|
|
|
487
|
-
|
|
488
|
-
|
|
488
|
+
if( typeof(value) === 'string' ) {
|
|
489
|
+
const translatedValue = t(value, {defaultValue: value});
|
|
490
|
+
return translatedValue || value.toString();
|
|
491
|
+
}
|
|
492
|
+
if( typeof(value) === 'object' ) {
|
|
493
|
+
return JSON.stringify(value, null, 2);
|
|
494
|
+
}
|
|
495
|
+
return value;
|
|
489
496
|
|
|
490
497
|
}).filter(Boolean).join(', ');
|
|
491
498
|
|
package/src/email.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { translations } from "
|
|
1
|
+
import { translations } from "data-primals-engine/i18n";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import nodemailer from "nodemailer";
|
|
4
4
|
import juice from "juice";
|
|
@@ -44,14 +44,14 @@ const createTransporter = (smtpConfig) => {
|
|
|
44
44
|
* @param {string|null} tpl - Le template HTML à utiliser.
|
|
45
45
|
*/
|
|
46
46
|
export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl = null) => {
|
|
47
|
-
const contactEmail = smtpConfig ? (smtpConfig.from || "Our company <noreply@ourdomain.tld>"
|
|
47
|
+
const contactEmail = smtpConfig ? (smtpConfig.from || emailDefaultConfig.from) :"Our company <noreply@ourdomain.tld>";
|
|
48
48
|
const emails = Array.isArray(email) ? email : [email];
|
|
49
49
|
if (emails.length === 0) return;
|
|
50
50
|
|
|
51
51
|
// Choisir le transporteur à utiliser
|
|
52
52
|
const transporter = smtpConfig ? createTransporter(smtpConfig) : defaultTransporter;
|
|
53
53
|
|
|
54
|
-
if (tpl === null) tpl =
|
|
54
|
+
if (tpl === null) tpl = event_trigger("sendEmail:template",data, lang);
|
|
55
55
|
let html = tpl;
|
|
56
56
|
try {
|
|
57
57
|
html = juice(tpl);
|
|
@@ -80,205 +80,6 @@ export const sendEmail = async (email = "", data, smtpConfig = null, lang, tpl =
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
export const etemplate1 = (data, lang = "de", font = "Roboto") => {
|
|
84
|
-
const { title, content } = data;
|
|
85
|
-
// S'assurer que les traductions existent pour la langue demandée
|
|
86
|
-
const trs = translations[lang]?.translation || translations['en']['translation'];
|
|
87
|
-
return (
|
|
88
|
-
`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
89
|
-
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
90
|
-
<head>
|
|
91
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
92
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
93
|
-
<title>Newsletter web.primals.net</title>
|
|
94
|
-
<script type="application/ld+json">
|
|
95
|
-
{
|
|
96
|
-
"@context": "http://schema.org",
|
|
97
|
-
"@type": "EmailMessage",
|
|
98
|
-
"potentialAction": {
|
|
99
|
-
"@type": "ViewAction",
|
|
100
|
-
"url": "https://web.primals.net",
|
|
101
|
-
"name": "web.primals.net"
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
</script>
|
|
105
|
-
<style type="text/css">
|
|
106
|
-
/* Based on The MailChimp Reset INLINE: Yes. */
|
|
107
|
-
/* Client-specific Styles */
|
|
108
|
-
#outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
|
|
109
|
-
body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0; color:black}
|
|
110
|
-
/* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
|
|
111
|
-
.ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
|
|
112
|
-
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
|
|
113
|
-
/* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
|
|
114
|
-
#backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
|
|
115
|
-
/* End reset */
|
|
116
|
-
|
|
117
|
-
/* Some sensible defaults for images
|
|
118
|
-
Bring inline: Yes. */
|
|
119
|
-
img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
|
|
120
|
-
a img {border:none;}
|
|
121
|
-
.image_fix {display:block;}
|
|
122
|
-
|
|
123
|
-
/* Yahoo paragraph fix
|
|
124
|
-
Bring inline: Yes. */
|
|
125
|
-
p {margin: 0;}
|
|
126
|
-
|
|
127
|
-
/* Hotmail header color reset
|
|
128
|
-
Bring inline: Yes. */
|
|
129
|
-
h1, h2, h3, h4, h5, h6 {color: black !important;}
|
|
130
|
-
|
|
131
|
-
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: blue !important;}
|
|
132
|
-
|
|
133
|
-
h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {
|
|
134
|
-
color: red !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {
|
|
138
|
-
color: purple !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/* Outlook 07, 10 Padding issue fix
|
|
142
|
-
Bring inline: No.*/
|
|
143
|
-
table td {border-collapse: collapse; }
|
|
144
|
-
|
|
145
|
-
/* Remove spacing around Outlook 07, 10 tables
|
|
146
|
-
Bring inline: Yes */
|
|
147
|
-
|
|
148
|
-
/* Styling your links has become much simpler with the new Yahoo. In fact, it falls in line with the main credo of styling in email and make sure to bring your styles inline. Your link colors will be uniform across clients when brought inline.
|
|
149
|
-
Bring inline: Yes. */
|
|
150
|
-
a {color: #1381b1;}
|
|
151
|
-
p { color:black; }
|
|
152
|
-
|
|
153
|
-
h2 {
|
|
154
|
-
padding: 20px 0;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/***************************************************
|
|
159
|
-
****************************************************
|
|
160
|
-
MOBILE TARGETING
|
|
161
|
-
****************************************************
|
|
162
|
-
***************************************************/
|
|
163
|
-
@media only screen and (max-device-width: 480px) {
|
|
164
|
-
/* Part one of controlling phone number linking for mobile. */
|
|
165
|
-
a[href^="tel"], a[href^="sms"] {
|
|
166
|
-
text-decoration: none;
|
|
167
|
-
color: blue; /* or whatever your want */
|
|
168
|
-
pointer-events: none;
|
|
169
|
-
cursor: default;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
|
|
173
|
-
text-decoration: default;
|
|
174
|
-
color: orange !important;
|
|
175
|
-
pointer-events: auto;
|
|
176
|
-
cursor: default;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/* More Specific Targeting */
|
|
182
|
-
|
|
183
|
-
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
|
|
184
|
-
/* You guessed it, ipad (tablets, smaller screens, etc) */
|
|
185
|
-
/* repeating for the ipad */
|
|
186
|
-
a[href^="tel"], a[href^="sms"] {
|
|
187
|
-
text-decoration: none;
|
|
188
|
-
color: blue; /* or whatever your want */
|
|
189
|
-
pointer-events: none;
|
|
190
|
-
cursor: default;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
|
|
194
|
-
text-decoration: default;
|
|
195
|
-
color: orange !important;
|
|
196
|
-
pointer-events: auto;
|
|
197
|
-
cursor: default;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
|
|
202
|
-
/* Put your iPhone 4g styles in here */
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/* Android targeting */
|
|
206
|
-
@media only screen and (-webkit-device-pixel-ratio:.75){
|
|
207
|
-
/* Put CSS for low density (ldpi) Android layouts in here */
|
|
208
|
-
}
|
|
209
|
-
@media only screen and (-webkit-device-pixel-ratio:1){
|
|
210
|
-
/* Put CSS for medium density (mdpi) Android layouts in here */
|
|
211
|
-
}
|
|
212
|
-
@media only screen and (-webkit-device-pixel-ratio:1.5){
|
|
213
|
-
/* Put CSS for high density (hdpi) Android layouts in here */
|
|
214
|
-
}
|
|
215
|
-
/* end Android targeting */
|
|
216
|
-
|
|
217
|
-
</style>
|
|
218
|
-
|
|
219
|
-
<!-- Targeting Windows Mobile -->
|
|
220
|
-
<!--[if IEMobile 7]>
|
|
221
|
-
<style type="text/css">
|
|
222
|
-
|
|
223
|
-
</style>
|
|
224
|
-
<![endif]-->
|
|
225
|
-
|
|
226
|
-
<!-- ***********************************************
|
|
227
|
-
****************************************************
|
|
228
|
-
END MOBILE TARGETING
|
|
229
|
-
****************************************************
|
|
230
|
-
************************************************ -->
|
|
231
|
-
|
|
232
|
-
<!--[if gte mso 9]>
|
|
233
|
-
<style>
|
|
234
|
-
/* Target Outlook 2007 and 2010 */
|
|
235
|
-
</style>
|
|
236
|
-
<![endif]-->
|
|
237
|
-
</head>
|
|
238
|
-
<body><style>
|
|
239
|
-
@import url(https://fonts.googleapis.com/css?family=${font});
|
|
240
|
-
|
|
241
|
-
/* Type styles for all clients */
|
|
242
|
-
h1,h2,h3 {
|
|
243
|
-
font-family: Helvetica, Arial, serif;
|
|
244
|
-
text-align: center;
|
|
245
|
-
}
|
|
246
|
-
h3 {
|
|
247
|
-
margin: 0;
|
|
248
|
-
}
|
|
249
|
-
p{
|
|
250
|
-
font-family: Helvetica, Arial, serif;
|
|
251
|
-
}
|
|
252
|
-
/* Type styles for WebKit clients */
|
|
253
|
-
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
|
254
|
-
h1,h2,h3 {
|
|
255
|
-
font-family: ${font}, Helvetica, Arial, serif !important;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
</style>` +
|
|
259
|
-
etable(
|
|
260
|
-
etable(
|
|
261
|
-
erow(
|
|
262
|
-
etag("h2", {'text-align': 'left'}, title) +
|
|
263
|
-
etable(
|
|
264
|
-
erow(etag('p', {}, trs[content] || content || ''), { 'text-align': 'left' })+
|
|
265
|
-
erow(etag('p', {}, trs['email.defaultSignature'] ||'')),
|
|
266
|
-
{ 'text-align': 'left' }),
|
|
267
|
-
{ 'text-align': 'left' }
|
|
268
|
-
)+
|
|
269
|
-
erow(
|
|
270
|
-
etag('hr', {}, '')+
|
|
271
|
-
etag('a', { href: "https://data.primals.net"}, 'https://data.primals.net')+
|
|
272
|
-
etag('div', {}, 'Service gratuit de base de donnée en ligne')+
|
|
273
|
-
etag("div", {}, 'Support: '+etag('a', { href:'mailto:data@primals.net'}, 'data@primals.net')),
|
|
274
|
-
{ 'text-align': 'left' }
|
|
275
|
-
)
|
|
276
|
-
),
|
|
277
|
-
{ width: "1024px", 'text-align': "left" },
|
|
278
|
-
)
|
|
279
|
-
);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
83
|
export const etag = (name, attrs, content = "") => {
|
|
283
84
|
return (
|
|
284
85
|
"<" +
|
package/src/index.js
CHANGED
package/src/modules/assistant.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
|
7
7
|
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
8
8
|
import {searchData, patchData, deleteData, insertData} from "./data.js";
|
|
9
9
|
import { getDataAsString } from "../data.js";
|
|
10
|
-
import i18n from "
|
|
10
|
+
import i18n from "data-primals-engine/i18n";
|
|
11
11
|
import {generateLimiter} from "./user.js";
|
|
12
12
|
import rateLimit from "express-rate-limit";
|
|
13
13
|
|
|
@@ -24,7 +24,13 @@ export const assistantGlobalLimiter = rateLimit({
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
const createSystemPrompt = (modelDefs
|
|
27
|
+
const createSystemPrompt = (modelDefs) => {
|
|
28
|
+
const cond1 = JSON.stringify({ "model": "event", "sort": "startDate:ASC", "limit": 0, "filter": { "$and": [{ "$nin": ["$tags", "festival"] }, { "$lt": ["$endDate", "$$NOW"] }] }}, null, 2);
|
|
29
|
+
const cond2 = JSON.stringify({ "model": "contact", "sort": "legalName:ASC", "limit": 5,"filter": {"$ne": [{ "$type": "$legalName"}, "missing"] }}, null, 2);
|
|
30
|
+
const cond3 = JSON.stringify({ "model": "order", "sort": "_id:DESC", "limit": 10,"filter": {"lang": { "$find": [{ "$eq": ["$$this.code", "fr"] }] } }}, null, 2);
|
|
31
|
+
const cond4 = JSON.stringify({ "model": "order", "sort": "updatedAt:DESC", "limit": 0,"filter": {"user": { "$find": { "roles": { "$find": [{ "$in": ["$$this.name", ["admin", "moderator"]] }] } }}}}, null, 2);
|
|
32
|
+
|
|
33
|
+
return `
|
|
28
34
|
Tu es "Prior", un assistant expert en analyse de données pour la plateforme data.primals.net.
|
|
29
35
|
Ta mission est d'aider l'utilisateur en répondant à ses questions sur ses données.
|
|
30
36
|
|
|
@@ -55,7 +61,8 @@ Tu as accès aux outils suivants. Tu ne dois utiliser QUE ces outils.
|
|
|
55
61
|
5. **displayMessage**: Pour répondre directement à l'utilisateur. N'utilise cette action QUE lorsque tu as toutes les informations nécessaires pour formuler une réponse finale.
|
|
56
62
|
- Utilisation: \`{ "action": "displayMessage", "params": { "message": "Ta réponse textuelle." } }\`
|
|
57
63
|
|
|
58
|
-
La spécification
|
|
64
|
+
La spécification, pour t'aider à construire les filtres, est la suivante :
|
|
65
|
+
utilise une chaine de caractère convertible en ObjectId (mongodb) lorsque le nom du champ est _id
|
|
59
66
|
utilise une chaine de caracteres lorsque le type de champ est : code, string, string_t , password, url, phone, email, richtext
|
|
60
67
|
utilise une structure { "iso2langcode":"content..." } pour le champ multi-traductions richtext_t
|
|
61
68
|
utilise un booléen pour : boolean
|
|
@@ -76,8 +83,17 @@ PROCESSUS DE RAISONNEMENT:
|
|
|
76
83
|
CONTEXTE ACTUEL:
|
|
77
84
|
- L'utilisateur a accès aux modèles de données suivants et ne peut utiliser les filtres que sur les champs associés:
|
|
78
85
|
${modelDefs.map(m => ` - Modèle "${m.name}":\n Champs: ${JSON.stringify(m.fields.map(f => ({ name: f.name, type: f.type, hint: f.hint })), null, 2)}`).join('\n')}
|
|
79
|
-
- Le format pour les filtres ($FILTER) est : ${conditionBuilderExample}
|
|
80
86
|
- Le format de $DATA est { modelFieldName: "value", otherModelFieldName: { subObj : true } }
|
|
87
|
+
- Exemples de filtres "params" utilisables :
|
|
88
|
+
Je voudrais les événements non terminés, qui ne sont pas des festivals ou des salons :
|
|
89
|
+
\`${cond1}\`
|
|
90
|
+
Donne moi les 5 nouvelles entreprises
|
|
91
|
+
\`${cond2}\`
|
|
92
|
+
Je veux les 10 dernières traductions ajoutées dans la langue française.
|
|
93
|
+
\`${cond3}\`
|
|
94
|
+
Je veux les commandes qui ont été faites par un admin ou un modérateur
|
|
95
|
+
\`${cond4}\`
|
|
96
|
+
Ton but est de créer un filtre au plus précis, et mets toujours un limit à 1 par défaut pour éviter de faire des actions en masse non désirées.
|
|
81
97
|
|
|
82
98
|
|
|
83
99
|
Règles ABSOLUES:
|
|
@@ -97,13 +113,19 @@ json
|
|
|
97
113
|
\`\`\`
|
|
98
114
|
|
|
99
115
|
Exemple de réponse INCORRECTE (INTERDIT) :
|
|
100
|
-
"Je vais d'abord rechercher ces requêtes. Voici la commande que je vais exécuter..."
|
|
116
|
+
"Je vais d'abord rechercher ces requêtes. Voici la commande que je vais exécuter..."
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
101
119
|
|
|
102
120
|
|
|
103
121
|
/**
|
|
104
122
|
* Gère la requête de chat, soit en exécutant une action confirmée,
|
|
105
123
|
* soit en lançant la boucle de raisonnement de l'IA.
|
|
106
124
|
*/
|
|
125
|
+
/**
|
|
126
|
+
* Gère la requête de chat, soit en exécutant une action confirmée,
|
|
127
|
+
* soit en lanant la boucle de raisonnement de l'IA.
|
|
128
|
+
*/
|
|
107
129
|
async function handleChatRequest(message, history, provider, context, user, confirmedAction) {
|
|
108
130
|
|
|
109
131
|
// --- GESTION D'UNE ACTION CONFIRMÉE ---
|
|
@@ -152,8 +174,7 @@ async function handleChatRequest(message, history, provider, context, user, conf
|
|
|
152
174
|
const relevantModelNames = new Set([modelName, 'alert', 'kpi', "dashboard", "request", "translation"]);
|
|
153
175
|
const modelDefs = allModels.filter(m => relevantModelNames.has(m.name));
|
|
154
176
|
|
|
155
|
-
const
|
|
156
|
-
const systemPrompt = createSystemPrompt(modelDefs, conditionBuilderExample);
|
|
177
|
+
const systemPrompt = createSystemPrompt(modelDefs);
|
|
157
178
|
|
|
158
179
|
const conversationHistory = history
|
|
159
180
|
.filter(msg => !(msg.from === 'bot' && msg.text.startsWith(i18n.t('assistant.welcome'))))
|
|
@@ -207,21 +228,27 @@ async function handleChatRequest(message, history, provider, context, user, conf
|
|
|
207
228
|
|
|
208
229
|
return { success: true, displayMessage: resultString };
|
|
209
230
|
}
|
|
210
|
-
|
|
211
231
|
case 'displayMessage':
|
|
212
232
|
return { success: true, displayMessage: parsedResponse.params.message };
|
|
213
|
-
|
|
214
233
|
case 'code':
|
|
215
|
-
return { success: true, displayMessage: "
|
|
216
|
-
|
|
234
|
+
return { success: true, displayMessage: "Voici le code : " + parsedResponse.params.code };
|
|
217
235
|
case 'post':
|
|
218
236
|
case 'update':
|
|
219
|
-
case 'delete':
|
|
237
|
+
case 'delete': {
|
|
238
|
+
const { model, filter, data } = parsedResponse.params;
|
|
239
|
+
|
|
240
|
+
// Un message générique. Les détails seront affichés par le front-end.
|
|
241
|
+
const confirmationMessage = i18n.t('assistant.confirmActionPrompt', "Veuillez confirmer l'action suivante :");
|
|
242
|
+
|
|
220
243
|
return {
|
|
221
244
|
success: true,
|
|
222
|
-
|
|
223
|
-
|
|
245
|
+
model,
|
|
246
|
+
filter,
|
|
247
|
+
data,
|
|
248
|
+
displayMessage: confirmationMessage, // Message détaillé pour l'utilisateur
|
|
249
|
+
confirmationRequest: parsedResponse // Action complète pour l'exécution
|
|
224
250
|
};
|
|
251
|
+
}
|
|
225
252
|
|
|
226
253
|
default:
|
|
227
254
|
return {
|
|
@@ -234,7 +261,6 @@ async function handleChatRequest(message, history, provider, context, user, conf
|
|
|
234
261
|
logger.warn("[Assistant] La boucle a atteint le nombre maximum de tours sans réponse.");
|
|
235
262
|
return { success: true, displayMessage: i18n.t('assistant.loopTimeout', "Désolé, je n'ai pas réussi à terminer ma pensée. Pouvez-vous reformuler votre demande ?"), actions: [] };
|
|
236
263
|
}
|
|
237
|
-
|
|
238
264
|
/**
|
|
239
265
|
* Exécute une action de modification (post, update, delete) après confirmation de l'utilisateur.
|
|
240
266
|
* @param {string} action - L'action à exécuter ('post', 'update', 'delete').
|
package/src/modules/bucket.js
CHANGED
|
@@ -9,7 +9,7 @@ import {Logger} from "../gameObject.js";
|
|
|
9
9
|
import {middlewareAuthenticator, myFreePremiumAnonymousLimiter, userInitiator} from "./user.js";
|
|
10
10
|
import {awsDefaultConfig, maxBytesPerSecondThrottleData} from "../constants.js";
|
|
11
11
|
import crypto from "node:crypto";
|
|
12
|
-
import i18n from "
|
|
12
|
+
import i18n from "data-primals-engine/i18n";
|
|
13
13
|
import {sendEmail} from "../email.js";
|
|
14
14
|
import {throttleMiddleware} from "../middlewares/throttle.js";
|
|
15
15
|
|
|
@@ -188,9 +188,6 @@ export async function onInit(defaultEngine) {
|
|
|
188
188
|
// Autres validations possibles (longueur, format de la région, etc.)
|
|
189
189
|
|
|
190
190
|
try {
|
|
191
|
-
const primalsDb = MongoClient.db("primals"); // Assure-toi que MongoClient est bien ton client MongoDB connecté
|
|
192
|
-
const usersCollection = primalsDb.collection("users");
|
|
193
|
-
|
|
194
191
|
const updateData = {
|
|
195
192
|
's3Config.bucketName': bucketName,
|
|
196
193
|
's3Config.accessKeyId': accessKeyId, // Stocké en clair (généralement acceptable)
|
|
@@ -207,18 +204,15 @@ export async function onInit(defaultEngine) {
|
|
|
207
204
|
// Pour l'instant, on ne touche pas à s3Config.secretAccessKey si req.body.secretAccessKey est vide.
|
|
208
205
|
}
|
|
209
206
|
|
|
210
|
-
const result = await
|
|
207
|
+
const result = await engine.userProvider.updateUser(
|
|
211
208
|
{ username: user.username }, // ou user._id si c'est ce que tu utilises comme identifiant unique
|
|
212
|
-
|
|
209
|
+
updateData
|
|
213
210
|
);
|
|
214
211
|
|
|
215
|
-
if (result
|
|
216
|
-
logger.info(`S3 configuration updated for user ${user.username}`);
|
|
212
|
+
if (result) { // Succès même si rien n'a été modifié (déjà les bonnes valeurs)
|
|
217
213
|
res.json({ success: true, message: "S3 configuration updated successfully." });
|
|
218
214
|
} else {
|
|
219
|
-
|
|
220
|
-
// Si l'utilisateur n'est pas trouvé, c'est une erreur 404
|
|
221
|
-
const userExists = await usersCollection.findOne({ username: user.username });
|
|
215
|
+
const userExists = engine.userProvider.findUserByUsername(user.username);
|
|
222
216
|
if (!userExists) {
|
|
223
217
|
return res.status(404).json({ success: false, error: "User not found." });
|
|
224
218
|
}
|
package/src/modules/data.js
CHANGED
|
@@ -8,7 +8,6 @@ import sanitizeHtml from 'sanitize-html';
|
|
|
8
8
|
import * as tar from "tar";
|
|
9
9
|
import process from "node:process";
|
|
10
10
|
import {randomColor} from "randomcolor";
|
|
11
|
-
import rateLimit from "express-rate-limit";
|
|
12
11
|
import cronstrue from 'cronstrue/i18n.js';
|
|
13
12
|
import { setTimeoutMiddleware } from '../middlewares/timeout.js';
|
|
14
13
|
|
|
@@ -65,8 +64,7 @@ import {
|
|
|
65
64
|
import fs from "node:fs";
|
|
66
65
|
import schedule from "node-schedule";
|
|
67
66
|
import {middleware} from "../middlewares/middleware-mongodb.js";
|
|
68
|
-
import
|
|
69
|
-
import i18n from "../../../data-primals-engine/src/i18n.js";
|
|
67
|
+
import i18n from "data-primals-engine/i18n";
|
|
70
68
|
import {
|
|
71
69
|
runScheduledJobWithDbLock,
|
|
72
70
|
scheduleWorkflowTriggers,
|
|
@@ -76,7 +74,6 @@ import NodeCache from "node-cache";
|
|
|
76
74
|
import AWS from 'aws-sdk';
|
|
77
75
|
import {getUserStorageLimit} from "../user.js";
|
|
78
76
|
import {openaiJobModel} from "../openai.jobs.js";
|
|
79
|
-
import {tutorialsConfig} from "../tutorials.js";
|
|
80
77
|
import checkDiskSpace from "check-disk-space";
|
|
81
78
|
import { fileURLToPath } from 'url';
|
|
82
79
|
import { Worker } from 'worker_threads';
|
|
@@ -103,10 +100,6 @@ const sseConnections = new Map();
|
|
|
103
100
|
|
|
104
101
|
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
105
102
|
|
|
106
|
-
const isProduction = process.env.NODE_ENV === 'production';
|
|
107
|
-
export const urlData = isProduction ? 'https://data.primals.net/' : 'http://localhost:5173/';
|
|
108
|
-
export const DATAS_API_TOKEN = process.env.DATAS_API_TOKEN;
|
|
109
|
-
|
|
110
103
|
const backupDir = process.env.BACKUP_DIR || './backups'; // Répertoire de stockage des sauvegardes
|
|
111
104
|
const execAsync = promisify(exec);
|
|
112
105
|
|
|
@@ -1123,33 +1116,6 @@ export async function onInit(defaultEngine) {
|
|
|
1123
1116
|
engine = defaultEngine;
|
|
1124
1117
|
logger = engine.getComponent(Logger);
|
|
1125
1118
|
|
|
1126
|
-
const loadFromDb = async () => {
|
|
1127
|
-
const webpages = await searchData({user: {username:'primals', token: DATAS_API_TOKEN}, query: { depth: 2, model: 'webpage', filter:{ $eq: ['$inSitemap', true]}}});
|
|
1128
|
-
const contents = await searchData({user: {username:'primals', token: DATAS_API_TOKEN}, query: { depth: 2, model: 'content', filter:{ $eq: ['$inSitemap', true]}}});
|
|
1129
|
-
const alls = [
|
|
1130
|
-
"/",
|
|
1131
|
-
];
|
|
1132
|
-
|
|
1133
|
-
availableLangs.forEach(l => {
|
|
1134
|
-
alls.push('/?lang='+l);
|
|
1135
|
-
alls.push('/cgu?lang='+l);
|
|
1136
|
-
alls.push('/'+l+'/documentation');
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
webpages.data.forEach(w => {
|
|
1140
|
-
alls.push('/'+w.lang.code+(w.category.identifier?'/'+w.category.identifier:'')+(w.slug||''));
|
|
1141
|
-
});
|
|
1142
|
-
contents.data.forEach(w => {
|
|
1143
|
-
alls.push('/'+w.lang.code+(w.category.identifier?'/'+w.category.identifier:'')+(w.slug||''));
|
|
1144
|
-
});
|
|
1145
|
-
|
|
1146
|
-
return alls;
|
|
1147
|
-
};
|
|
1148
|
-
|
|
1149
|
-
engine.use(
|
|
1150
|
-
ExpressSitemap(loadFromDb, "https://data.primals.net", { maxAge: 60000 }),
|
|
1151
|
-
);
|
|
1152
|
-
|
|
1153
1119
|
engine.use(middleware({ whitelist: [
|
|
1154
1120
|
"$$NOW", "$in", "$eq", "$gt", "$gte", "$in", "$lt", "$lte", "$ne", "$nin", "$type", "$size",
|
|
1155
1121
|
"$and", "$not", "$nor", "$or", "$regexMatch", "$find", "$elemMatch", "$filter", "$toString", "$toObjectId",
|
|
@@ -1160,7 +1126,6 @@ export async function onInit(defaultEngine) {
|
|
|
1160
1126
|
"$toDate", "$toBool", "$toString", "$toInt", "$toDouble",
|
|
1161
1127
|
"$dateSubtract", "$dateAdd", "$dateToString",
|
|
1162
1128
|
'$year', '$month', '$week', '$dayOfMonth', '$dayOfWeek', '$dayOfYear', '$hour', '$minute', '$second', '$millisecond',
|
|
1163
|
-
|
|
1164
1129
|
]}));
|
|
1165
1130
|
|
|
1166
1131
|
let modelsCollection, datasCollection, filesCollection, packsCollection, magnetsCollection;
|
|
@@ -1263,24 +1228,6 @@ export async function onInit(defaultEngine) {
|
|
|
1263
1228
|
return await usersCollection.updateOne({ username: user.username }, { $set: data }, { upsert: true })
|
|
1264
1229
|
}
|
|
1265
1230
|
|
|
1266
|
-
/**
|
|
1267
|
-
* Remplace les placeholders dans un objet filtre.
|
|
1268
|
-
* Gère {{userId}}.
|
|
1269
|
-
*/
|
|
1270
|
-
const processFilterPlaceholders = (filter, user) => {
|
|
1271
|
-
const processedFilter = JSON.parse(JSON.stringify(filter));
|
|
1272
|
-
for (const key in processedFilter) {
|
|
1273
|
-
if (processedFilter[key] === '{{userId}}') {
|
|
1274
|
-
// Dans le système Primals, le champ utilisateur est souvent `_user`
|
|
1275
|
-
// et contient le nom d'utilisateur, pas l'ID. Adaptez si besoin.
|
|
1276
|
-
processedFilter[key] = user.username;
|
|
1277
|
-
} else if (typeof processedFilter[key] === 'object' && processedFilter[key] !== null) {
|
|
1278
|
-
processedFilter[key] = processFilterPlaceholders(processedFilter[key], user);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
return processedFilter;
|
|
1282
|
-
};
|
|
1283
|
-
|
|
1284
1231
|
// Dans C:/Dev/hackersonline-engine/server/src/modules/data.js, dans onInit()
|
|
1285
1232
|
|
|
1286
1233
|
engine.post('/api/magnets', [middlewareAuthenticator, userInitiator], async (req, res) => {
|
|
@@ -1385,127 +1332,6 @@ export async function onInit(defaultEngine) {
|
|
|
1385
1332
|
}
|
|
1386
1333
|
});
|
|
1387
1334
|
|
|
1388
|
-
engine.post('/api/tutorials/set-active', middlewareAuthenticator, async (req, res) => {
|
|
1389
|
-
try {
|
|
1390
|
-
const { tutorialState } = req.body;
|
|
1391
|
-
const user = req.me;
|
|
1392
|
-
|
|
1393
|
-
if (tutorialState !== null && (typeof tutorialState !== 'object' || !tutorialState.id)) {
|
|
1394
|
-
return res.status(400).json({ error: 'Invalid tutorial state payload.' });
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
// Créer une représentation de l'utilisateur mis à jour pour la réponse
|
|
1398
|
-
const updatedData = { activeTutorial: tutorialState };
|
|
1399
|
-
|
|
1400
|
-
await saveUser(user, updatedData);
|
|
1401
|
-
|
|
1402
|
-
// --- MODIFICATION ---
|
|
1403
|
-
res.json({
|
|
1404
|
-
success: true,
|
|
1405
|
-
updatedData // Renvoyer l'objet utilisateur complet
|
|
1406
|
-
});
|
|
1407
|
-
|
|
1408
|
-
} catch (error) {
|
|
1409
|
-
console.error('[Tutoriel Set Active Error]', error);
|
|
1410
|
-
res.status(500).json({ error: 'Error setting active tutorial.', details: error.message });
|
|
1411
|
-
}
|
|
1412
|
-
});
|
|
1413
|
-
|
|
1414
|
-
// ...
|
|
1415
|
-
|
|
1416
|
-
engine.post('/api/tutorials/:tutorialId/claim-rewards', middlewareAuthenticator, async (req, res) => {
|
|
1417
|
-
try {
|
|
1418
|
-
const { tutorialId } = req.params;
|
|
1419
|
-
const user = req.me; // L'objet utilisateur est déjà chargé
|
|
1420
|
-
const tutorial = tutorialsConfig.find(t => t.id === tutorialId);
|
|
1421
|
-
|
|
1422
|
-
if (!tutorial) return res.status(404).json({ error: 'Tutoriel non trouvé.' });
|
|
1423
|
-
if (!tutorial.rewards) return res.status(400).json({ error: 'Ce tutoriel n\'a pas de récompenses.' });
|
|
1424
|
-
if (user.completedTutorials?.includes(tutorialId)) {
|
|
1425
|
-
return res.status(400).json({ error: 'Tutoriel déjà terminé.' });
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
const { xpBonus, skill, achievement, notification } = tutorial.rewards;
|
|
1429
|
-
|
|
1430
|
-
// --- LOGIQUE CORRIGÉE ---
|
|
1431
|
-
let newData= {};
|
|
1432
|
-
newData.xp = newData.xp || 0;
|
|
1433
|
-
newData.achievements = newData.achievements || [];
|
|
1434
|
-
newData.skills = newData.skills || [];
|
|
1435
|
-
newData.completedTutorials = newData.completedTutorials || [];
|
|
1436
|
-
|
|
1437
|
-
// Appliquer les récompenses directement sur l'objet newData
|
|
1438
|
-
if (xpBonus) newData.xp += xpBonus;
|
|
1439
|
-
if (achievement && !newData.achievements.includes(achievement)) newData.achievements.push(achievement);
|
|
1440
|
-
if (skill) {
|
|
1441
|
-
const existingSkill = newData.skills.find(s => s.name === skill.name);
|
|
1442
|
-
if (existingSkill) {
|
|
1443
|
-
existingSkill.points += skill.points;
|
|
1444
|
-
} else {
|
|
1445
|
-
newData.skills.push({ name: skill.name, points: skill.points });
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
newData.completedTutorials.push(tutorialId);
|
|
1450
|
-
newData.activeTutorial = null;
|
|
1451
|
-
|
|
1452
|
-
await saveUser(user, newData);
|
|
1453
|
-
|
|
1454
|
-
const translatedNotification = {
|
|
1455
|
-
title: i18n.t(notification.title, notification.title),
|
|
1456
|
-
message: i18n.t(notification.message, notification.message),
|
|
1457
|
-
};
|
|
1458
|
-
|
|
1459
|
-
res.json({
|
|
1460
|
-
success: true,
|
|
1461
|
-
userUpdate: newData, // Renvoyer l'objet utilisateur complet et mis à jour
|
|
1462
|
-
notification: translatedNotification
|
|
1463
|
-
});
|
|
1464
|
-
|
|
1465
|
-
} catch (error) {
|
|
1466
|
-
console.error('[Tutoriel Rewards Error]', error);
|
|
1467
|
-
res.status(500).json({ error: 'Erreur lors de l\'attribution des récompenses.', details: error.message });
|
|
1468
|
-
}
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
/**
|
|
1473
|
-
* Route pour vérifier si une condition de complétion est remplie.
|
|
1474
|
-
* Utilise la fonction `searchData` existante pour interroger les données.
|
|
1475
|
-
*/
|
|
1476
|
-
engine.post('/api/tutorials/check-completion', middlewareAuthenticator, async (req, res) => {
|
|
1477
|
-
try {
|
|
1478
|
-
const { model, filter, limit } = req.body;
|
|
1479
|
-
const user = req.me;
|
|
1480
|
-
|
|
1481
|
-
if (!model || !filter || limit === undefined) {
|
|
1482
|
-
return res.status(400).json({ error: 'Payload de condition de complétion invalide.' });
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
const processedFilter = processFilterPlaceholders(filter, user);
|
|
1486
|
-
|
|
1487
|
-
// On utilise la fonction de recherche interne de l'application
|
|
1488
|
-
const searchResult = await searchData({
|
|
1489
|
-
query: {
|
|
1490
|
-
model,
|
|
1491
|
-
filter: processedFilter,
|
|
1492
|
-
limit, // Optimisation : pas besoin de plus de résultats
|
|
1493
|
-
page: 1,
|
|
1494
|
-
},
|
|
1495
|
-
user: user,
|
|
1496
|
-
});
|
|
1497
|
-
|
|
1498
|
-
// searchData devrait renvoyer un `count` total des documents correspondants
|
|
1499
|
-
const isCompleted = searchResult.count >= limit;
|
|
1500
|
-
|
|
1501
|
-
res.json({ isCompleted });
|
|
1502
|
-
|
|
1503
|
-
} catch (error) {
|
|
1504
|
-
console.error('[Tutoriel Check Error]', error);
|
|
1505
|
-
res.status(500).json({ error: 'Erreur lors de la vérification de la complétion.', details: error.message });
|
|
1506
|
-
}
|
|
1507
|
-
});
|
|
1508
|
-
|
|
1509
1335
|
engine.get('/api/import/progress/:jobId', [middlewareAuthenticator], async (req, res) => {
|
|
1510
1336
|
const { jobId } = req.params;
|
|
1511
1337
|
const user = req.me;
|
package/src/modules/file.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Nouvelle fonction pour ajouter un fichier privé
|
|
3
3
|
import {maxPrivateFileSize, megabytes} from "../constants.js";
|
|
4
4
|
import {isLocalUser} from "../data.js";
|
|
5
|
-
import i18n from "
|
|
5
|
+
import i18n from "data-primals-engine/i18n";
|
|
6
6
|
import {getUserStorageLimit} from "../user.js";
|
|
7
7
|
import {getCollection} from "./mongodb.js";
|
|
8
8
|
import {getFileExtension, isGUID, uuidv4} from "../core.js";
|
package/src/modules/user.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import i18n from "
|
|
1
|
+
import i18n from "data-primals-engine/i18n";
|
|
2
2
|
import {MongoClient, MongoDatabase} from "../engine.js";
|
|
3
3
|
import {getCollection, getCollectionForUser, getUserCollectionName} from "./mongodb.js";
|
|
4
4
|
import {dbName, plans} from "../constants.js";
|
package/src/modules/workflow.js
CHANGED
|
@@ -10,7 +10,7 @@ import {maxExecutionsByStep, maxWorkflowSteps} from "../constants.js";
|
|
|
10
10
|
import { ChatOpenAI } from "@langchain/openai";
|
|
11
11
|
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
12
12
|
import { ChatPromptTemplate } from "@langchain/core/prompts";
|
|
13
|
-
import i18n from "
|
|
13
|
+
import i18n from "data-primals-engine/i18n";
|
|
14
14
|
import {sendEmail} from "../email.js";
|
|
15
15
|
|
|
16
16
|
// 1. ADD THIS IMPORT AT THE TOP OF THE FILE
|
package/src/providers.js
CHANGED
|
@@ -13,6 +13,15 @@ export class UserProvider {
|
|
|
13
13
|
this.engine = engine;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Met à jour un nouvel utilisateur.
|
|
18
|
+
* @param user
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
async updateUser(user, data) {
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* Trouve un utilisateur par son nom d'utilisateur.
|
|
18
27
|
* @param {string} username - Le nom d'utilisateur à rechercher.
|
|
@@ -57,6 +66,10 @@ export class DefaultUserProvider extends UserProvider {
|
|
|
57
66
|
return true;
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
async updateUser(user, data) {
|
|
70
|
+
this.users = this.users.map(user => user.username === user.username ? {...user, ...data} : user);
|
|
71
|
+
}
|
|
72
|
+
|
|
60
73
|
async initiateUser(req) {
|
|
61
74
|
req.me = this.users[0];
|
|
62
75
|
}
|
package/src/tutorials.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// Fichier : src/tutorials.js
|
|
2
|
-
|
|
3
|
-
export const tutorialsConfig = [
|
|
4
|
-
{
|
|
5
|
-
id: 'contact-management-basics',
|
|
6
|
-
name: 'Gestion des contacts : Les bases',
|
|
7
|
-
description: 'Apprenez les opérations fondamentales : créer et supprimer un contact.',
|
|
8
|
-
icon: 'FaUserPlus',
|
|
9
|
-
stages: [
|
|
10
|
-
{
|
|
11
|
-
stage: 1,
|
|
12
|
-
tourName: 'tour-create-contact', // Nom du tour guidé à lancer
|
|
13
|
-
name: 'Créez votre premier contact',
|
|
14
|
-
description: 'Ajoutez un nouveau contact avec le prénom "Richard".',
|
|
15
|
-
completionCondition: { model: 'contact', limit: 1, filter: { "firstName": "Richard" } }
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
stage: 2,
|
|
19
|
-
tourName: 'tour-delete-contact',
|
|
20
|
-
name: 'Effacez vos traces',
|
|
21
|
-
description: 'Maintenant, supprimez le contact "Richard" que vous venez de créer.',
|
|
22
|
-
// La condition pour valider une suppression est de vérifier que le nombre d'éléments correspondants est 0.
|
|
23
|
-
completionCondition: { model: 'contact', limit: 0, filter: { "firstName": "Richard" } }
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
rewards: {
|
|
27
|
-
xpBonus: 150,
|
|
28
|
-
achievement: 'CONTACT_MANAGER_NOVICE',
|
|
29
|
-
notification: { title: 'Gestionnaire de contacts', message: 'Vous maîtrisez les bases de la gestion de contacts !' }
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: 'advanced-permissions',
|
|
34
|
-
name: 'Permissions & rôles',
|
|
35
|
-
description: 'Maîtrisez la gestion des droits du bout des doigts !',
|
|
36
|
-
icon: 'FaUserShield',
|
|
37
|
-
stages: [
|
|
38
|
-
{
|
|
39
|
-
stage: 1,
|
|
40
|
-
tourName: 'tour-set-permissions',
|
|
41
|
-
name: 'Gestion des droits utilisateurs',
|
|
42
|
-
description: 'Trouvez l\'utilisateur "userTuto" et donnez-lui le droit "visitor".',
|
|
43
|
-
completionCondition: {model: 'user', limit: 1, filter: {"username": "userTuto", "roles": { "$find": {"$eq":["$$this.name","visitor"]}}}}
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
stage: 2,
|
|
47
|
-
tourName: 'tour-create-role',
|
|
48
|
-
name: 'Créer un nouveau rôle',
|
|
49
|
-
description: 'Créez un rôle personnalisé nommé "Modérateur".',
|
|
50
|
-
// Condition : un document dans le modèle 'role' avec le nom 'Modérateur' doit exister.
|
|
51
|
-
// (On suppose ici l'existence d'un modèle 'role' pour gérer les rôles).
|
|
52
|
-
completionCondition: { model: 'role', limit: 1, filter: { "name": "moderator" } }
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
stage: 3,
|
|
56
|
-
tourName: 'tour-assign-permission-to-role',
|
|
57
|
-
name: 'Ajouter une permission au rôle',
|
|
58
|
-
description: 'Modifiez le rôle "moderator" pour lui ajouter la permission "API_EDIT_DATA_content".',
|
|
59
|
-
// Condition : le rôle 'Modérateur' doit avoir une permission nommée 'delete_contact'.
|
|
60
|
-
// (On suppose que le modèle 'role' a un champ 'permissions' qui est un tableau de relations).
|
|
61
|
-
completionCondition: { model: 'role', limit: 1, filter: { "name": "moderator", "permissions": { "$find": { "$eq": ["$$this.name", "API_EDIT_DATA_content"] } } } }
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
stage: 4,
|
|
65
|
-
tourName: 'tour-assign-role-to-user',
|
|
66
|
-
name: 'Promouvoir un utilisateur',
|
|
67
|
-
description: 'Maintenant, assignez votre rôle "moderator" à l\'utilisateur "userTuto".',
|
|
68
|
-
// Condition : l'utilisateur 'userTuto' doit maintenant aussi avoir le rôle 'Modérateur'.
|
|
69
|
-
completionCondition: { model: 'user', limit: 1, filter: { "username": "userTuto", "roles": { "$find": { "$eq": ["$$this.name", "moderator"] } } } }
|
|
70
|
-
}
|
|
71
|
-
],
|
|
72
|
-
rewards: {
|
|
73
|
-
xpBonus: 500,
|
|
74
|
-
achievement: 'PERMISSION_ARCHITECT',
|
|
75
|
-
notification: { title: 'Architecte des Permissions', message: 'Vous savez maintenant comment finement gérer les accès !' }
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: 'requests',
|
|
80
|
-
name: 'Suivi d\'Activité',
|
|
81
|
-
description: 'Découvrez les succès liés à l\'activité.',
|
|
82
|
-
icon: 'FaUserShield',
|
|
83
|
-
stages: [
|
|
84
|
-
{
|
|
85
|
-
stage: 1,
|
|
86
|
-
tourName: null, // Pas de tour guidé, l'action peut se faire n'importe où
|
|
87
|
-
name: 'Première requête',
|
|
88
|
-
description: 'Effectuez des actions pour générer 50 requêtes système pour débloquer votre premier succès d\'activité.',
|
|
89
|
-
completionCondition: {model: 'request', limit: 50, filter: {}}
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
stage: 2,
|
|
93
|
-
tourName: null,
|
|
94
|
-
name: 'Activité soutenue',
|
|
95
|
-
description: 'Continuez votre activité et atteignez 250 requêtes pour le prochain palier.',
|
|
96
|
-
completionCondition: {model: 'request', limit: 250, filter: {}}
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
stage: 3,
|
|
100
|
-
tourName: null,
|
|
101
|
-
name: 'Expert en requêtes',
|
|
102
|
-
description: 'Impressionnant ! Atteignez 1000 requêtes pour prouver votre maîtrise.',
|
|
103
|
-
completionCondition: {model: 'request', limit: 1000, filter: {}}
|
|
104
|
-
}
|
|
105
|
-
],
|
|
106
|
-
rewards: {
|
|
107
|
-
xpBonus: 1000,
|
|
108
|
-
achievement: 'REQUEST_MASTER',
|
|
109
|
-
notification: { title: 'Maître des Requêtes', message: 'Votre activité sur le réseau est remarquable !' }
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
];
|