data-primals-engine 1.0.4 → 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 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
- │ ├── engine.js
203
+ │ ├── middlewares/
204
+ │ ├── migrations/
201
205
  │ ├── modules/
202
- │ ├── providers/
203
- │ ├── constants.js
204
- │ ├── defaultModels.js
205
- ├── config/
206
- │ ├── models/
207
- └── packs/
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,6 +1,6 @@
1
1
  {
2
2
  "name": "data-primals-engine",
3
- "version": "1.0.4",
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",
@@ -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 "../data-primals-engine/src/config.js";
8
- import {Engine} from "../data-primals-engine/src/engine.js";
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/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
- const translatedValue = t(value, {defaultValue: value});
488
- return translatedValue || value.toString();
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 "../../data-primals-engine/src/i18n.js";
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";
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // data-primals-engine/src/index.js
1
+ // data-primals-engine/index
2
2
 
3
3
  // --- Core Engine & Utilities ---
4
4
  export { Engine } from './engine.js';
@@ -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 "../../../data-primals-engine/src/i18n.js";
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, conditionBuilderExample) =>`
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 suivante peut t'aider à construire les filtres) est la suivante :
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 conditionBuilderExample = JSON.stringify({ "$and": [{ "$eq": ["$fieldName", "value"] }] }, null, 2);
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: "```json\n" + JSON.stringify(parsedResponse.params.message, null, 2) + "\n```" };
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
- displayMessage: i18n.t('assistant.confirmAction', `Je vais exécuter : ${parsedResponse.action}. Confirmez-vous ?`),
223
- confirmationRequest: parsedResponse
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').
@@ -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 "../../../data-primals-engine/src/i18n.js";
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
 
@@ -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 ExpressSitemap from "express-sitemap-xml";
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 engine.userProvider.updateUser(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;
@@ -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 "../../../data-primals-engine/src/i18n.js";
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";
@@ -1,4 +1,4 @@
1
- import i18n from "../../../data-primals-engine/src/i18n.js";
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";
@@ -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 "../../../data-primals-engine/src/i18n.js";
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/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
- ];