data-primals-engine 1.0.5 → 1.0.7

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.
@@ -20,7 +20,7 @@ export const assistantGlobalLimiter = rateLimit({
20
20
  legacyHeaders: false, // Désactive les anciens en-têtes `X-RateLimit-*`
21
21
  message: { success: false, message: "Trop de requêtes globales envoyées à l'assistant. Veuillez réessayer plus tard." },
22
22
  skip: (req) => {
23
- return !!req.body?.confirmedAction;
23
+ return !!req.fields?.confirmedAction;
24
24
  }
25
25
  });
26
26
 
@@ -63,7 +63,9 @@ Tu as accès aux outils suivants. Tu ne dois utiliser QUE ces outils.
63
63
 
64
64
  La spécification, pour t'aider à construire les filtres, est la suivante :
65
65
  utilise une chaine de caractère convertible en ObjectId (mongodb) lorsque le nom du champ est _id
66
- utilise une chaine de caracteres lorsque le type de champ est : code, string, string_t , password, url, phone, email, richtext
66
+ utilise une chaine de caracteres lorsque le type de champ est : string, string_t , password, url, phone, email, richtext
67
+ utilise un $FILTER en retour si le type de champ est code et language='json' et conditionBuilder=true
68
+ utilise une chaine si c'est un type de champ code par défaut.
67
69
  utilise une structure { "iso2langcode":"content..." } pour le champ multi-traductions richtext_t
68
70
  utilise un booléen pour : boolean
69
71
  utilise un nombre pour number
@@ -80,11 +82,18 @@ PROCESSUS DE RAISONNEMENT:
80
82
  2. Tu analyses la question et choisis l'outil approprié.
81
83
  3. Tu réponds IMMÉDIATEMENT avec l'objet JSON correspondant SANS COMMENTAIRE.
82
84
 
85
+ Chacune de tes réponses doit être une étape en soi, ou au plus près d'une étape.
86
+
83
87
  CONTEXTE ACTUEL:
84
88
  - L'utilisateur a accès aux modèles de données suivants et ne peut utiliser les filtres que sur les champs associés:
85
89
  ${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')}
86
90
  - Le format de $DATA est { modelFieldName: "value", otherModelFieldName: { subObj : true } }
87
- - Exemples de filtres "params" utilisables :
91
+ - Exemples de filtres $FILTER utilisables (ils sont compatibles MongoDB) :
92
+ Filtre utilisable : { $eq: ["$status", 500 ] }
93
+ Filtre incorrect : { "status": 500 }
94
+ ==
95
+
96
+ Exemples de "params" :
88
97
  Je voudrais les événements non terminés, qui ne sont pas des festivals ou des salons :
89
98
  \`${cond1}\`
90
99
  Donne moi les 5 nouvelles entreprises
@@ -93,8 +102,6 @@ Je veux les 10 dernières traductions ajoutées dans la langue française.
93
102
  \`${cond3}\`
94
103
  Je veux les commandes qui ont été faites par un admin ou un modérateur
95
104
  \`${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.
97
-
98
105
 
99
106
  Règles ABSOLUES:
100
107
  - UNE SEULE COMMANDE JSON PAR RÉPONSE
@@ -292,7 +299,7 @@ export async function onInit(engine) {
292
299
 
293
300
  engine.post('/api/assistant/chat', [middlewareAuthenticator, userInitiator, assistantGlobalLimiter, generateLimiter], async (req, res) => {
294
301
  // On récupère TOUTES les propriétés du body, y compris l'action confirmée
295
- const {message, history, provider, context, confirmedAction} = req.body;
302
+ const {message, history, provider, context, confirmedAction} = req.fields;
296
303
 
297
304
  // La validation ne s'applique que s'il n'y a pas d'action confirmée
298
305
  if (!confirmedAction) {
@@ -179,7 +179,7 @@ export async function onInit(defaultEngine) {
179
179
 
180
180
  engine.post('/api/user/s3-config', [middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
181
181
  const user = req.me;
182
- const { bucketName, accessKeyId, secretAccessKey, region, pathPrefix } = req.body;
182
+ const { bucketName, accessKeyId, secretAccessKey, region, pathPrefix } = req.fields;
183
183
 
184
184
  // Validation basique des entrées
185
185
  if (!bucketName || !accessKeyId || !region) {
@@ -201,7 +201,7 @@ export async function onInit(defaultEngine) {
201
201
  } else {
202
202
  // Si la clé secrète n'est pas fournie, on ne la modifie pas.
203
203
  // Si tu veux permettre de la supprimer, il faudrait une logique explicite.
204
- // Pour l'instant, on ne touche pas à s3Config.secretAccessKey si req.body.secretAccessKey est vide.
204
+ // Pour l'instant, on ne touche pas à s3Config.secretAccessKey si req.fields.secretAccessKey est vide.
205
205
  }
206
206
 
207
207
  const result = await engine.userProvider.updateUser(
@@ -77,7 +77,7 @@ import {openaiJobModel} from "../openai.jobs.js";
77
77
  import checkDiskSpace from "check-disk-space";
78
78
  import { fileURLToPath } from 'url';
79
79
  import { Worker } from 'worker_threads';
80
- import {encryptFile, removeFile} from "./file.js";
80
+ import {addFile, encryptFile, removeFile} from "./file.js";
81
81
  import {listS3Backups, uploadToS3} from "./bucket.js";
82
82
  import {
83
83
  calculateTotalUserStorageUsage, generateLimiter, hasPermission,
@@ -1232,7 +1232,7 @@ export async function onInit(defaultEngine) {
1232
1232
 
1233
1233
  engine.post('/api/magnets', [middlewareAuthenticator, userInitiator], async (req, res) => {
1234
1234
  const user = req.me;
1235
- const { name, description, modelNames } = req.body; // Noms des modèles à inclure
1235
+ const { name, description, modelNames } = req.fields; // Noms des modèles à inclure
1236
1236
 
1237
1237
  if (!name || !Array.isArray(modelNames) || modelNames.length === 0) {
1238
1238
  return res.status(400).json({ error: 'Name and a list of model names are required.' });
@@ -1401,7 +1401,7 @@ export async function onInit(defaultEngine) {
1401
1401
 
1402
1402
  engine.post('/api/data/import', [middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1403
1403
  // ... (vérifications de permissions existantes) ...
1404
- const result = await importData(req.body, req.files, req.me);
1404
+ const result = await importData(req.fields, req.files, req.me);
1405
1405
  if( result.success ){
1406
1406
  res.status(202).json(result);
1407
1407
  }else{
@@ -1412,7 +1412,7 @@ export async function onInit(defaultEngine) {
1412
1412
 
1413
1413
  engine.post('/api/model/generate', [middlewareAuthenticator, userInitiator, assistantGlobalLimiter, generateLimiter, setTimeoutMiddleware(30000)], async (req, res) => {
1414
1414
  // --- NOUVELLE LOGIQUE : Accepter le prompt ET un modèle existant ---
1415
- const { prompt, history = [], existingModel } = req.body;
1415
+ const { prompt, history = [], existingModel } = req.fields;
1416
1416
  const user = req.me;
1417
1417
  const lang = req.query.lang || 'en';
1418
1418
 
@@ -1463,7 +1463,7 @@ export async function onInit(defaultEngine) {
1463
1463
  return res.status(403).json({success: false, error: i18n.t('api.permission.editModel', 'Cannot edit models from the API')})
1464
1464
  }
1465
1465
 
1466
- const dataModel = req.body;
1466
+ const dataModel = req.fields;
1467
1467
  try {
1468
1468
  const collection = getCollectionForUser(req.me);
1469
1469
  validateModelStructure(dataModel);
@@ -1480,13 +1480,13 @@ export async function onInit(defaultEngine) {
1480
1480
  }
1481
1481
 
1482
1482
  // renommage du modèle
1483
- if (typeof (req.body.name)==='string'&&el.name !== req.body.name && req.body.name ){
1484
- 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 }});
1485
1485
  await modelsCollection.updateMany({ 'fields' : {
1486
1486
  '$elemMatch' : { relation: el.name }
1487
1487
  }}, {
1488
1488
  $set : {
1489
- 'fields.$.relation' : req.body.name
1489
+ 'fields.$.relation' : req.fields.name
1490
1490
  }
1491
1491
  })
1492
1492
  }
@@ -1526,7 +1526,7 @@ export async function onInit(defaultEngine) {
1526
1526
  await collection.updateMany({ _model: el.name }, { $unset: unset });
1527
1527
 
1528
1528
  // sauvegarde du modele
1529
- const set = {...req.body};
1529
+ const set = {...req.fields};
1530
1530
  delete set['_id'];
1531
1531
  await modelsCollection.updateOne({_id: new ObjectId(req.params.id)}, {$set: set});
1532
1532
 
@@ -1579,8 +1579,9 @@ export async function onInit(defaultEngine) {
1579
1579
  });
1580
1580
 
1581
1581
  engine.post('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(15000)], async (req, res) => {
1582
- 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 !)
1583
- 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));
1584
1585
 
1585
1586
  try {
1586
1587
  const model = await getModel(modelName, req.me);
@@ -1594,10 +1595,10 @@ export async function onInit(defaultEngine) {
1594
1595
  });
1595
1596
 
1596
1597
  engine.post('/api/data/search', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(30000)], async (req, res) => {
1597
- const { pack } = req.body;
1598
+ const { pack } = req.fields;
1598
1599
 
1599
1600
  try {
1600
- 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 });
1601
1602
 
1602
1603
  if( req.query.attachment ) {
1603
1604
  res.attachment(req.query.attachment);
@@ -1622,7 +1623,7 @@ export async function onInit(defaultEngine) {
1622
1623
 
1623
1624
  engine.delete('/api/data/:ids', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, setTimeoutMiddleware(15000)], async (req, res) => {
1624
1625
  const ids = req.params.ids.split(',');
1625
- const r = await deleteData(req.body.model, ids, {}, req.me);
1626
+ const r = await deleteData(req.fields.model, ids, {}, req.me);
1626
1627
  if( r.error) {
1627
1628
  return res.status(r.statusCode || 400).json(r);
1628
1629
  }else{
@@ -1631,7 +1632,7 @@ export async function onInit(defaultEngine) {
1631
1632
  });
1632
1633
 
1633
1634
  engine.delete('/api/data', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger, setTimeoutMiddleware(15000)], async (req, res) => {
1634
- 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);
1635
1636
  if( r.error) {
1636
1637
  return res.status(r.statusCode || 400).json(r);
1637
1638
  }else{
@@ -1642,9 +1643,9 @@ export async function onInit(defaultEngine) {
1642
1643
  // --- Export Endpoint ---
1643
1644
  engine.post('/api/data/export', [middlewareAuthenticator, throttle, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(60000)], async (req, res) => {
1644
1645
  try {
1645
- 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);
1646
1647
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1647
- const filename = `export-${req.body.models.join('-')}-${timestamp}.json`;
1648
+ const filename = `export-${req.fields.models.join('-')}-${timestamp}.json`;
1648
1649
 
1649
1650
  if (!results.success) {
1650
1651
  res.status(400).json(results); // Renvoyer l'objet d'erreur complet
@@ -1669,10 +1670,10 @@ export async function onInit(defaultEngine) {
1669
1670
  });
1670
1671
 
1671
1672
  engine.patch('/api/data/:id?', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1672
- const filter = req.body.filter;
1673
+ const filter = req.fields.filter;
1673
1674
  const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1674
- const data = req.body.data || (req.body._data && JSON.parse(req.body._data));
1675
- 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);
1676
1677
  if (r.error) {
1677
1678
  res.status(400).json(r);
1678
1679
  } else {
@@ -1682,10 +1683,10 @@ export async function onInit(defaultEngine) {
1682
1683
 
1683
1684
  engine.put('/api/data/:id?', [throttle, middlewareAuthenticator, userInitiator, middlewareLogger], async (req, res) => {
1684
1685
  try {
1685
- const filter = req.body.filter;
1686
+ const filter = req.fields.filter;
1686
1687
  const hash = req.params.id; // Récupérer l'identifiant de la ressource à modifier
1687
- const data = req.body.data || (req.body._data && JSON.parse(req.body._data));
1688
- 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)
1689
1690
  if (r.error)
1690
1691
  res.status(400).json(r);
1691
1692
  else
@@ -1741,7 +1742,7 @@ export async function onInit(defaultEngine) {
1741
1742
  return res.status(403).json({success: false, error: i18n.t('api.permission.addModel')})
1742
1743
  }
1743
1744
  try {
1744
- const modelData = req.body;
1745
+ const modelData = req.fields;
1745
1746
  validateModelStructure(modelData);
1746
1747
 
1747
1748
 
@@ -1795,7 +1796,7 @@ export async function onInit(defaultEngine) {
1795
1796
  return res.status(403).json({success: false, error: i18n.t('api.permission.importModels')})
1796
1797
  }
1797
1798
  try {
1798
- 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();
1799
1800
  const ids = [];
1800
1801
 
1801
1802
  const getPromise = () => {
@@ -1808,13 +1809,17 @@ export async function onInit(defaultEngine) {
1808
1809
  return Promise.reject();
1809
1810
  })));
1810
1811
  }
1811
- const install = !!req.body.install;
1812
+ const install = !!req.fields.install;
1812
1813
  if( install && /^demo[0-9]{1,2}$/.test(req.me.username) ){
1813
1814
 
1814
1815
  await datasCollection.deleteMany({ _user: req.me.username});
1815
1816
  await modelsCollection.deleteMany({ _user: req.me.username});
1816
1817
  const files = await filesCollection.find({ mainUser: req.me.username}).toArray();
1817
- files.forEach(file =>removeFile(file.guid, req.me));
1818
+ try {
1819
+ files.forEach(file =>removeFile(file.guid, req.me));
1820
+ } catch (e) {
1821
+
1822
+ }
1818
1823
 
1819
1824
  await cancelAlerts(req.me);
1820
1825
 
@@ -1888,7 +1893,7 @@ export async function onInit(defaultEngine) {
1888
1893
 
1889
1894
  try {
1890
1895
  const modelId = req.params.modelId;
1891
- const { oldFieldName, newFieldName } = req.body;
1896
+ const { oldFieldName, newFieldName } = req.fields;
1892
1897
 
1893
1898
  // Basic validation
1894
1899
  if (!oldFieldName || !newFieldName) {
@@ -2097,7 +2102,7 @@ export async function onInit(defaultEngine) {
2097
2102
 
2098
2103
  engine.post('/api/charts/aggregate', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter, setTimeoutMiddleware(15000)], async (req, res) => {
2099
2104
  // --- Récupérer groupByLabelField ---
2100
- 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;
2101
2106
 
2102
2107
  // --- Validation (inchangée) ---
2103
2108
  const isGroupingChart = ['pie', 'doughnut'].includes(type);
@@ -2321,7 +2326,7 @@ export async function onInit(defaultEngine) {
2321
2326
  if( !/^demo[0-9]{1,2}$/.test(req.me.username) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_CREATE_PACK"], req.me)){
2322
2327
  return res.status(403).json({success: false, error: i18n.t('api.permission.createPack')})
2323
2328
  }
2324
- const { itemIds } = req.body;
2329
+ const { itemIds } = req.fields;
2325
2330
  if (!Array.isArray(itemIds) || itemIds.length === 0 || !itemIds.every(isObjectId)) { // Assurez-vous que isObjectId est importé/défini
2326
2331
  return res.status(400).json({ success: false, error: 'itemIds must be a non-empty array of valid ObjectIds.' });
2327
2332
  }
@@ -2451,7 +2456,7 @@ export async function onInit(defaultEngine) {
2451
2456
  });
2452
2457
 
2453
2458
  engine.post('/api/data/addToPack', [throttle, middlewareAuthenticator, userInitiator,myFreePremiumAnonymousLimiter], async (req, res) => {
2454
- const { packName, itemIds } = req.body;
2459
+ const { packName, itemIds } = req.fields;
2455
2460
  const user = req.me;
2456
2461
 
2457
2462
  if( !/^demo[0-9]{1,2}$/.test(req.me.username) && isLocalUser(req.me) && !await hasPermission(["API_ADMIN", "API_CREATE_PACK"], req.me)){
@@ -2521,7 +2526,7 @@ export async function onInit(defaultEngine) {
2521
2526
  /*
2522
2527
  engine.post('/api/packs/install', [throttle, middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
2523
2528
 
2524
- const { pack } = req.body;
2529
+ const { pack } = req.fields;
2525
2530
  const initialModelName = req.query.model; // The starting model
2526
2531
  const user = req.me;
2527
2532
  const collection = getCollectionForUser(user);
@@ -3386,12 +3391,12 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
3386
3391
  // Traitement des fichiers
3387
3392
  const fileFields = model.fields.filter(f => f.type === 'file' || (f.type === 'array' && f.itemsType === 'file'));
3388
3393
  for (const field of fileFields) {
3389
- if (files?.[field.name]) {
3394
+ if (files?.[field.name+'[0]']) {
3390
3395
  if (field.type === 'file') {
3391
- updateData[field.name] = await addFile(files[field.name][0], user);
3396
+ updateData[field.name] = await addFile(files[field.name+'[0]'][0], user);
3392
3397
  } else if (field.type === 'array' && field.itemsType === 'file') {
3393
3398
  const currentFiles = existingDocs[0]?.[field.name] || [];
3394
- const newFiles = await processFileArray(files[field.name], currentFiles, user);
3399
+ const newFiles = await processFileArray(files[field.name+'[0]'], currentFiles, user);
3395
3400
  updateData[field.name] = newFiles;
3396
3401
  }
3397
3402
  }
@@ -3490,7 +3495,7 @@ const internalEditOrPatchData = async (modelName, filter, data, files, user, isP
3490
3495
  // Fonctions helper
3491
3496
  async function processFileArray(files, currentFiles, user) {
3492
3497
  const newFiles = await Promise.allSettled(
3493
- files.map(async (file, i) => {
3498
+ Object.keys(files).map(f=>files[f]).map(async (file, i) => {
3494
3499
  const oldFile = currentFiles.find(f => f.name === file.name);
3495
3500
  if (oldFile && !file.newFile) return oldFile;
3496
3501
  if (file.guid) return file;
@@ -3500,7 +3505,7 @@ async function processFileArray(files, currentFiles, user) {
3500
3505
  ).then(results => results.map(r => r.value).filter(Boolean));
3501
3506
 
3502
3507
  // Suppression des anciens fichiers non réutilisés
3503
- await Promise.all(
3508
+ await Promise.allSettled(
3504
3509
  currentFiles
3505
3510
  .filter(f => !newFiles.some(nf => nf._id === f._id))
3506
3511
  .map(f => removeFile(f, user))
@@ -5241,7 +5246,7 @@ async function logApiRequest(req, res, user, startTime, responseBody = null, err
5241
5246
  // Champs optionnels
5242
5247
  ip: req.clientIp.substring('::ffff:'.length) || req.clientIp, // Obtenir l'IP du client
5243
5248
  //requestHeaders: JSON.stringify(req.headers).substring(0, maxStringLength), // Optionnel: Peut être volumineux
5244
- requestBody: req.body,
5249
+ requestBody: req.fields,
5245
5250
  responseBody: res.statusCode >= 400 && responseBody ? JSON.stringify(responseBody).substring(0, maxStringLength) : null, // Optionnel: Peut être volumineux
5246
5251
  error: error ? String(error.message || error) : null // Message d'erreur si applicable
5247
5252
  };
@@ -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