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.
- package/README.md +13 -9
- package/package.json +12 -13
- package/server.js +2 -28
- package/src/data.js +9 -2
- package/src/email.js +1 -1
- package/src/engine.js +7 -7
- package/src/i18n.js +603 -16631
- package/src/index.js +1 -1
- package/src/modules/assistant.js +52 -19
- package/src/modules/bucket.js +3 -3
- package/src/modules/data.js +44 -213
- package/src/modules/file.js +1 -1
- package/src/modules/user.js +2 -2
- package/src/modules/workflow.js +1 -1
- package/src/tutorials.js +0 -112
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,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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1658
|
-
await collection.updateMany({ _model: el.name }, { $set: { _model: req.
|
|
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.
|
|
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.
|
|
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
|
|
1757
|
-
const
|
|
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.
|
|
1598
|
+
const { pack } = req.fields;
|
|
1772
1599
|
|
|
1773
1600
|
try {
|
|
1774
|
-
const {data, count} = await searchData({query: {...req.query, model: req.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1849
|
-
const r = await patchData(req.
|
|
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.
|
|
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.
|
|
1862
|
-
const r = await editData(req.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
};
|
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";
|
|
@@ -79,7 +79,7 @@ export const generateLimiter = rateLimit({
|
|
|
79
79
|
standardHeaders: true,
|
|
80
80
|
legacyHeaders: false,
|
|
81
81
|
skip: (req) => {
|
|
82
|
-
return !!req.
|
|
82
|
+
return !!req.fields?.confirmedAction;
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
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/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
|
-
];
|