data-primals-engine 1.0.4 → 1.0.6

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.
@@ -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,11 +74,10 @@ 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';
83
- import {encryptFile, removeFile} from "./file.js";
80
+ import {addFile, encryptFile, removeFile} from "./file.js";
84
81
  import {listS3Backups, uploadToS3} from "./bucket.js";
85
82
  import {
86
83
  calculateTotalUserStorageUsage, generateLimiter, hasPermission,
@@ -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,29 +1228,11 @@ 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) => {
1287
1234
  const user = req.me;
1288
- const { name, description, modelNames } = req.body; // Noms des modèles à inclure
1235
+ const { name, description, modelNames } = req.fields; // Noms des modèles à inclure
1289
1236
 
1290
1237
  if (!name || !Array.isArray(modelNames) || modelNames.length === 0) {
1291
1238
  return res.status(400).json({ error: 'Name and a list of model names are required.' });
@@ -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;
@@ -1575,7 +1401,7 @@ export async function onInit(defaultEngine) {
1575
1401
 
1576
1402
  engine.post('/api/data/import', [middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1577
1403
  // ... (vérifications de permissions existantes) ...
1578
- const result = await importData(req.body, req.files, req.me);
1404
+ const result = await importData(req.fields, req.files, req.me);
1579
1405
  if( result.success ){
1580
1406
  res.status(202).json(result);
1581
1407
  }else{
@@ -1586,7 +1412,7 @@ export async function onInit(defaultEngine) {
1586
1412
 
1587
1413
  engine.post('/api/model/generate', [middlewareAuthenticator, userInitiator, assistantGlobalLimiter, generateLimiter, setTimeoutMiddleware(30000)], async (req, res) => {
1588
1414
  // --- NOUVELLE LOGIQUE : Accepter le prompt ET un modèle existant ---
1589
- const { prompt, history = [], existingModel } = req.body;
1415
+ const { prompt, history = [], existingModel } = req.fields;
1590
1416
  const user = req.me;
1591
1417
  const lang = req.query.lang || 'en';
1592
1418
 
@@ -1637,7 +1463,7 @@ export async function onInit(defaultEngine) {
1637
1463
  return res.status(403).json({success: false, error: i18n.t('api.permission.editModel', 'Cannot edit models from the API')})
1638
1464
  }
1639
1465
 
1640
- const dataModel = req.body;
1466
+ const dataModel = req.fields;
1641
1467
  try {
1642
1468
  const collection = getCollectionForUser(req.me);
1643
1469
  validateModelStructure(dataModel);
@@ -1654,13 +1480,13 @@ export async function onInit(defaultEngine) {
1654
1480
  }
1655
1481
 
1656
1482
  // renommage du modèle
1657
- if (typeof (req.body.name)==='string'&&el.name !== req.body.name && req.body.name ){
1658
- await collection.updateMany({ _model: el.name }, { $set: { _model: req.body.name }});
1483
+ if (typeof (req.fields.name)==='string'&&el.name !== req.fields.name && req.fields.name ){
1484
+ await collection.updateMany({ _model: el.name }, { $set: { _model: req.fields.name }});
1659
1485
  await modelsCollection.updateMany({ 'fields' : {
1660
1486
  '$elemMatch' : { relation: el.name }
1661
1487
  }}, {
1662
1488
  $set : {
1663
- 'fields.$.relation' : req.body.name
1489
+ 'fields.$.relation' : req.fields.name
1664
1490
  }
1665
1491
  })
1666
1492
  }
@@ -1700,7 +1526,7 @@ export async function onInit(defaultEngine) {
1700
1526
  await collection.updateMany({ _model: el.name }, { $unset: unset });
1701
1527
 
1702
1528
  // sauvegarde du modele
1703
- const set = {...req.body};
1529
+ const set = {...req.fields};
1704
1530
  delete set['_id'];
1705
1531
  await modelsCollection.updateOne({_id: new ObjectId(req.params.id)}, {$set: set});
1706
1532
 
@@ -1753,8 +1579,9 @@ export async function onInit(defaultEngine) {
1753
1579
  });
1754
1580
 
1755
1581
  engine.post('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(15000)], async (req, res) => {
1756
- const modelName = req.body.model; // Les données à insérer/mettre à jour (assurez-vous de valider et nettoyer ces données côté client et serveur !)
1757
- const data = req.body.data || (req.body._data && JSON.parse(req.body._data));
1582
+ const body = req.files ? req.fields : req.fields;
1583
+ const modelName = body.model; // Les données à insérer/mettre à jour (assurez-vous de valider et nettoyer ces données côté client et serveur !)
1584
+ const data = body.data || (body._data && JSON.parse(body._data));
1758
1585
 
1759
1586
  try {
1760
1587
  const model = await getModel(modelName, req.me);
@@ -1768,10 +1595,10 @@ export async function onInit(defaultEngine) {
1768
1595
  });
1769
1596
 
1770
1597
  engine.post('/api/data/search', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(30000)], async (req, res) => {
1771
- const { pack } = req.body;
1598
+ const { pack } = req.fields;
1772
1599
 
1773
1600
  try {
1774
- const {data, count} = await searchData({query: {...req.query, model: req.body.model || req.query.model, filter: req.body.filter, pack}, user: req.me });
1601
+ const {data, count} = await searchData({query: {...req.query, model: req.fields.model || req.query.model, filter: req.fields.filter, pack}, user: req.me });
1775
1602
 
1776
1603
  if( req.query.attachment ) {
1777
1604
  res.attachment(req.query.attachment);
@@ -1796,7 +1623,7 @@ export async function onInit(defaultEngine) {
1796
1623
 
1797
1624
  engine.delete('/api/data/:ids', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, setTimeoutMiddleware(15000)], async (req, res) => {
1798
1625
  const ids = req.params.ids.split(',');
1799
- const r = await deleteData(req.body.model, ids, {}, req.me);
1626
+ const r = await deleteData(req.fields.model, ids, {}, req.me);
1800
1627
  if( r.error) {
1801
1628
  return res.status(r.statusCode || 400).json(r);
1802
1629
  }else{
@@ -1805,7 +1632,7 @@ export async function onInit(defaultEngine) {
1805
1632
  });
1806
1633
 
1807
1634
  engine.delete('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, setTimeoutMiddleware(15000)], async (req, res) => {
1808
- const r = await deleteData(req.body.model, [], req.body.filter, req.me);
1635
+ const r = await deleteData(req.fields.model, [], req.fields.filter, req.me);
1809
1636
  if( r.error) {
1810
1637
  return res.status(r.statusCode || 400).json(r);
1811
1638
  }else{
@@ -1816,9 +1643,9 @@ export async function onInit(defaultEngine) {
1816
1643
  // --- Export Endpoint ---
1817
1644
  engine.post('/api/data/export', [middlewareAuthenticator, throttle, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1818
1645
  try {
1819
- const results = await exportData({...req.body, depth:req.query.depth, lang: req.query.lang}, req.me);
1646
+ const results = await exportData({...req.fields, depth:req.query.depth, lang: req.query.lang}, req.me);
1820
1647
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1821
- const filename = `export-${req.body.models.join('-')}-${timestamp}.json`;
1648
+ const filename = `export-${req.fields.models.join('-')}-${timestamp}.json`;
1822
1649
 
1823
1650
  if (!results.success) {
1824
1651
  res.status(400).json(results); // Renvoyer l'objet d'erreur complet
@@ -1843,10 +1670,10 @@ export async function onInit(defaultEngine) {
1843
1670
  });
1844
1671
 
1845
1672
  engine.patch('/api/data/:id?', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1846
- const filter = req.body.filter;
1673
+ const filter = req.fields.filter;
1847
1674
  const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1848
- const data = req.body.data || (req.body._data && JSON.parse(req.body._data));
1849
- const r = await patchData(req.body.model, filter || hash, data, req.files, req.me);
1675
+ const data = req.fields.data || (req.fields._data && JSON.parse(req.fields._data));
1676
+ const r = await patchData(req.fields.model, filter || hash, data, req.files, req.me);
1850
1677
  if (r.error) {
1851
1678
  res.status(400).json(r);
1852
1679
  } else {
@@ -1856,10 +1683,10 @@ export async function onInit(defaultEngine) {
1856
1683
 
1857
1684
  engine.put('/api/data/:id?', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1858
1685
  try {
1859
- const filter = req.body.filter;
1686
+ const filter = req.fields.filter;
1860
1687
  const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1861
- const data = req.body.data || (req.body._data && JSON.parse(req.body._data));
1862
- const r = await editData(req.body.model, filter || hash, data, req.files, req.me)
1688
+ const data = req.fields.data || (req.fields._data && JSON.parse(req.fields._data));
1689
+ const r = await editData(req.fields.model, filter || hash, data, req.files, req.me)
1863
1690
  if (r.error)
1864
1691
  res.status(400).json(r);
1865
1692
  else
@@ -1915,7 +1742,7 @@ export async function onInit(defaultEngine) {
1915
1742
  return res.status(403).json({success: false, error: i18n.t('api.permission.addModel')})
1916
1743
  }
1917
1744
  try {
1918
- const modelData = req.body;
1745
+ const modelData = req.fields;
1919
1746
  validateModelStructure(modelData);
1920
1747
 
1921
1748
 
@@ -1969,7 +1796,7 @@ export async function onInit(defaultEngine) {
1969
1796
  return res.status(403).json({success: false, error: i18n.t('api.permission.importModels')})
1970
1797
  }
1971
1798
  try {
1972
- const modelsToImport = await modelsCollection.find({name: { $in: req.body.models }, _user: { $exists: false } }).toArray();
1799
+ const modelsToImport = await modelsCollection.find({name: { $in: req.fields.models }, _user: { $exists: false } }).toArray();
1973
1800
  const ids = [];
1974
1801
 
1975
1802
  const getPromise = () => {
@@ -1982,13 +1809,17 @@ export async function onInit(defaultEngine) {
1982
1809
  return Promise.reject();
1983
1810
  })));
1984
1811
  }
1985
- const install = !!req.body.install;
1812
+ const install = !!req.fields.install;
1986
1813
  if( install && /^demo[0-9]{1,2}$/.test(req.me.username) ){
1987
1814
 
1988
1815
  await datasCollection.deleteMany({ _user: req.me.username});
1989
1816
  await modelsCollection.deleteMany({ _user: req.me.username});
1990
1817
  const files = await filesCollection.find({ mainUser: req.me.username}).toArray();
1991
- files.forEach(file =>removeFile(file.guid, req.me));
1818
+ try {
1819
+ files.forEach(file =>removeFile(file.guid, req.me));
1820
+ } catch (e) {
1821
+
1822
+ }
1992
1823
 
1993
1824
  await cancelAlerts(req.me);
1994
1825
 
@@ -2062,7 +1893,7 @@ export async function onInit(defaultEngine) {
2062
1893
 
2063
1894
  try {
2064
1895
  const modelId = req.params.modelId;
2065
- const { oldFieldName, newFieldName } = req.body;
1896
+ const { oldFieldName, newFieldName } = req.fields;
2066
1897
 
2067
1898
  // Basic validation
2068
1899
  if (!oldFieldName || !newFieldName) {
@@ -2271,7 +2102,7 @@ export async function onInit(defaultEngine) {
2271
2102
 
2272
2103
  engine.post('/api/charts/aggregate', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(15000)], async (req, res) => {
2273
2104
  // --- Récupérer groupByLabelField ---
2274
- const { model: modelName, type, xAxis, yAxis, groupBy, aggregationType, groupByLabelField, filter: chartFilter } = req.body;
2105
+ const { model: modelName, type, xAxis, yAxis, groupBy, aggregationType, groupByLabelField, filter: chartFilter } = req.fields;
2275
2106
 
2276
2107
  // --- Validation (inchangée) ---
2277
2108
  const isGroupingChart = ['pie', 'doughnut'].includes(type);
@@ -2495,7 +2326,7 @@ export async function onInit(defaultEngine) {
2495
2326
  if( !/^demo[0-9]{1,2}$/.test(req.me.username) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_CREATE_PACK"], req.me)){
2496
2327
  return res.status(403).json({success: false, error: i18n.t('api.permission.createPack')})
2497
2328
  }
2498
- const { itemIds } = req.body;
2329
+ const { itemIds } = req.fields;
2499
2330
  if (!Array.isArray(itemIds) || itemIds.length === 0 || !itemIds.every(isObjectId)) { // Assurez-vous que isObjectId est importé/défini
2500
2331
  return res.status(400).json({ success: false, error: 'itemIds must be a non-empty array of valid ObjectIds.' });
2501
2332
  }
@@ -2625,7 +2456,7 @@ export async function onInit(defaultEngine) {
2625
2456
  });
2626
2457
 
2627
2458
  engine.post('/api/data/addToPack', [throttle, middlewareAuthenticator, userInitiator,myFreePremiumAnonymousLimiter], async (req, res) => {
2628
- const { packName, itemIds } = req.body;
2459
+ const { packName, itemIds } = req.fields;
2629
2460
  const user = req.me;
2630
2461
 
2631
2462
  if( !/^demo[0-9]{1,2}$/.test(req.me.username) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_CREATE_PACK"], req.me)){
@@ -2695,7 +2526,7 @@ export async function onInit(defaultEngine) {
2695
2526
  /*
2696
2527
  engine.post('/api/packs/install', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
2697
2528
 
2698
- const { pack } = req.body;
2529
+ const { pack } = req.fields;
2699
2530
  const initialModelName = req.query.model; // The starting model
2700
2531
  const user = req.me;
2701
2532
  const collection = getCollectionForUser(user);
@@ -3560,12 +3391,12 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
3560
3391
  // Traitement des fichiers
3561
3392
  const fileFields = model.fields.filter(f => f.type === 'file' || (f.type === 'array' && f.itemsType === 'file'));
3562
3393
  for (const field of fileFields) {
3563
- if (files?.[field.name]) {
3394
+ if (files?.[field.name+'[0]']) {
3564
3395
  if (field.type === 'file') {
3565
- updateData[field.name] = await addFile(files[field.name][0], user);
3396
+ updateData[field.name] = await addFile(files[field.name+'[0]'][0], user);
3566
3397
  } else if (field.type === 'array' && field.itemsType === 'file') {
3567
3398
  const currentFiles = existingDocs[0]?.[field.name] || [];
3568
- const newFiles = await processFileArray(files[field.name], currentFiles, user);
3399
+ const newFiles = await processFileArray(files[field.name+'[0]'], currentFiles, user);
3569
3400
  updateData[field.name] = newFiles;
3570
3401
  }
3571
3402
  }
@@ -3664,7 +3495,7 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
3664
3495
  // Fonctions helper
3665
3496
  async function processFileArray(files, currentFiles, user) {
3666
3497
  const newFiles = await Promise.allSettled(
3667
- files.map(async (file, i) => {
3498
+ Object.keys(files).map(f=>files[f]).map(async (file, i) => {
3668
3499
  const oldFile = currentFiles.find(f => f.name === file.name);
3669
3500
  if (oldFile && !file.newFile) return oldFile;
3670
3501
  if (file.guid) return file;
@@ -3674,7 +3505,7 @@ async function processFileArray(files, currentFiles, user) {
3674
3505
  ).then(results => results.map(r => r.value).filter(Boolean));
3675
3506
 
3676
3507
  // Suppression des anciens fichiers non réutilisés
3677
- await Promise.all(
3508
+ await Promise.allSettled(
3678
3509
  currentFiles
3679
3510
  .filter(f => !newFiles.some(nf => nf._id === f._id))
3680
3511
  .map(f => removeFile(f, user))
@@ -5415,7 +5246,7 @@ async function logApiRequest(req, res, user, startTime, responseBody = null, err
5415
5246
  // Champs optionnels
5416
5247
  ip: req.clientIp.substring('::ffff:'.length) || req.clientIp, // Obtenir l'IP du client
5417
5248
  //requestHeaders: JSON.stringify(req.headers).substring(0, maxStringLength), // Optionnel: Peut être volumineux
5418
- requestBody: req.body,
5249
+ requestBody: req.fields,
5419
5250
  responseBody: res.statusCode >= 400 && responseBody ? JSON.stringify(responseBody).substring(0, maxStringLength) : null, // Optionnel: Peut être volumineux
5420
5251
  error: error ? String(error.message || error) : null // Message d'erreur si applicable
5421
5252
  };
@@ -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";
@@ -79,7 +79,7 @@ export const generateLimiter = rateLimit({
79
79
  standardHeaders: true,
80
80
  legacyHeaders: false,
81
81
  skip: (req) => {
82
- return !!req.body?.confirmedAction;
82
+ return !!req.fields?.confirmedAction;
83
83
  }
84
84
  });
85
85
 
@@ -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
- ];