@tiledesk/tiledesk-server 2.28.2 → 2.29.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,10 @@
5
5
  🚀 IN PRODUCTION 🚀
6
6
  (https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
7
7
 
8
+ # 2.29.0
9
+ - Added support Subagents
10
+ - Bumped tybot-connector version to 2.3.0
11
+
8
12
  # 2.28.2
9
13
  - Bumped whatsapp-connector version to 1.0.31: broadcast jobs are published on a single shared AMQP connection instead of opening a new one per request.
10
14
  - Bumped whatsapp-jobworker version to 0.0.14: the broadcast consumer now recovers after failed reconnections, closed channels and consumer cancellations, always acks the jobs and uses a timeout for the WhatsApp Graph API calls.
package/app.js CHANGED
@@ -29,9 +29,11 @@ var cors = require('cors');
29
29
  var Project = require("./models/project");
30
30
  var validtoken = require('./middleware/valid-token');
31
31
  var roleChecker = require('./middleware/has-role');
32
+ var errorHandler = require('./middleware/errorHandler');
32
33
 
33
34
  const MaskData = require("maskdata");
34
35
  var winston = require('./config/winston');
36
+ var pjson = require('./package.json');
35
37
 
36
38
 
37
39
  // DATABASE CONNECTION
@@ -458,7 +460,7 @@ if (process.env.ROUTELOGGER_ENABLED==="true") {
458
460
  }
459
461
 
460
462
  app.get('/', function (req, res) {
461
- res.send('Hello from Tiledesk server. It\'s UP. See the documentation here http://developer.tiledesk.com');
463
+ res.send('Tiledesk Server version: ' + pjson.version + ' is UP. See the documentation here http://developer.tiledesk.com');
462
464
  });
463
465
 
464
466
 
@@ -698,39 +700,8 @@ app.use(function (err, req, res, next) {
698
700
 
699
701
  // mettere middleware qui per le quote
700
702
 
701
-
702
-
703
- // error handler
704
- app.use((err, req, res, next) => {
705
-
706
- winston.debug("err.name", err.name)
707
- if (err.name === "IpDeniedError") {
708
- winston.debug("IpDeniedError");
709
- return res.status(401).json({ err: "error ip filter" });
710
- }
711
-
712
- const realIp = req.headers['x-forwarded-for']?.split(',')[0] || req.headers['x-real-ip'] || req.ip;
713
-
714
- //emitted by multer when the file is too big
715
- if (err.code === "LIMIT_FILE_SIZE") {
716
- winston.debug("LIMIT_FILE_SIZE");
717
- winston.warn(`LIMIT_FILE_SIZE on ${req.originalUrl}`, {
718
- limit: process.env.MAX_UPLOAD_FILE_SIZE,
719
- ip: req.ip,
720
- realIp: realIp
721
- });
722
- return res.status(413).json({ err: "Content Too Large", limit_file_size: process.env.MAX_UPLOAD_FILE_SIZE });
723
- }
724
-
725
- if (err.type === "entity.too.large" || err.name === "PayloadTooLargeError") {
726
- winston.warn("Payload too large", { expected: err.expected, limit: err.limit, length: err.length });
727
- return res.status(413).json({ err: "Request entity too large", limit: err.limit});
728
- }
729
-
730
-
731
- winston.error("General error: ", err);
732
- return res.status(500).json({ err: "error" });
733
- });
703
+ // error handler (must be registered last)
704
+ app.use(errorHandler);
734
705
 
735
706
 
736
707
  module.exports = app;
@@ -0,0 +1,599 @@
1
+ const Chatbot = require("../models/faq_kb");
2
+ const Faq = require("../models/faq");
3
+ const faqService = require("../services/faqService");
4
+ const AppError = require("../utils/AppError");
5
+ const intentsController = require("./intents.controller");
6
+ const botEvent = require("../event/botEvent");
7
+ const winston = require("../config/winston");
8
+
9
+ const IMPORT_OVERRIDE_FIELDS = [
10
+ "webhook_enabled",
11
+ "webhook_url",
12
+ "language",
13
+ "name",
14
+ "description",
15
+ "type",
16
+ "subtype"
17
+ ];
18
+
19
+ class ChatbotController {
20
+
21
+ constructor() { }
22
+
23
+ canAccess(chatbot, id_project) {
24
+ const isPublic = chatbot.public === true;
25
+ const isOwner = chatbot.id_project === id_project;
26
+ if (!isPublic && !isOwner) {
27
+ return false
28
+ }
29
+ return true;
30
+ }
31
+
32
+ _sanitizeIntent(intent) {
33
+ const {
34
+ _id, id_project, id_faq_kb, topic, status, chatbot_id,
35
+ createdBy, createdAt, updatedAt, __v, ...keepAttrs
36
+ } = intent;
37
+ return keepAttrs;
38
+ }
39
+
40
+ _sanitizeSubagent(subagent) {
41
+ return {
42
+ // Keep source _id so import can remap intents.actions[].botId
43
+ // to the newly created subagent. It is stripped before insert.
44
+ _id: subagent._id ? subagent._id.toString() : undefined,
45
+ webhook_enabled: subagent.webhook_enabled,
46
+ webhook_url: subagent.webhook_url,
47
+ language: subagent.language,
48
+ name: subagent.name,
49
+ slug: subagent.slug,
50
+ type: subagent.type,
51
+ subtype: subagent.subtype,
52
+ description: subagent.description,
53
+ attributes: subagent.attributes,
54
+ intents: (subagent.intents || []).map((intent) => this._sanitizeIntent(intent))
55
+ };
56
+ }
57
+
58
+ export(chatbot, intents, intentsOnly, subagents) {
59
+ const sanitizedIntents = (intents || []).map((intent) => this._sanitizeIntent(intent));
60
+
61
+ if (intentsOnly) {
62
+ return JSON.stringify({ intents: sanitizedIntents });
63
+ }
64
+
65
+ return JSON.stringify({
66
+ webhook_enabled: chatbot.webhook_enabled,
67
+ webhook_url: chatbot.webhook_url,
68
+ language: chatbot.language,
69
+ name: chatbot.name,
70
+ slug: chatbot.slug,
71
+ type: chatbot.type,
72
+ subtype: chatbot.subtype,
73
+ description: chatbot.description,
74
+ attributes: chatbot.attributes,
75
+ parent_id: chatbot.parent_id,
76
+ intents: sanitizedIntents,
77
+ subagents: (subagents || []).map((subagent) => this._sanitizeSubagent(subagent))
78
+ })
79
+ }
80
+
81
+ _rewriteRulesParticipants(attributes, chatbot_id) {
82
+ if (!attributes?.rules?.length) {
83
+ return attributes;
84
+ }
85
+ for (const rule of attributes.rules) {
86
+ if (rule.do?.[0]?.message?.participants?.[0]) {
87
+ rule.do[0].message.participants[0] = "bot_" + chatbot_id;
88
+ }
89
+ }
90
+ return attributes;
91
+ }
92
+
93
+ _sourceId(doc) {
94
+ if (!doc || doc._id == null) {
95
+ return null;
96
+ }
97
+ return doc._id.toString();
98
+ }
99
+
100
+ _stripImportedIdentity(doc) {
101
+ if (!doc) {
102
+ return;
103
+ }
104
+ delete doc._id;
105
+ delete doc.id_project;
106
+ delete doc.createdBy;
107
+ delete doc.createdAt;
108
+ delete doc.updatedAt;
109
+ delete doc.__v;
110
+ }
111
+
112
+ _collectBotIds(value, botIds = new Set()) {
113
+ if (!value) {
114
+ return botIds;
115
+ }
116
+ if (Array.isArray(value)) {
117
+ value.forEach((item) => this._collectBotIds(item, botIds));
118
+ return botIds;
119
+ }
120
+ if (typeof value !== "object") {
121
+ return botIds;
122
+ }
123
+ if (value.botId != null) {
124
+ botIds.add(value.botId.toString());
125
+ }
126
+ Object.keys(value).forEach((key) => {
127
+ if (key === "botId") {
128
+ return;
129
+ }
130
+ this._collectBotIds(value[key], botIds);
131
+ });
132
+ return botIds;
133
+ }
134
+
135
+ _mappedBotId(botId, idMap) {
136
+ if (botId == null || !idMap) {
137
+ return null;
138
+ }
139
+ const raw = botId.toString();
140
+ if (idMap[raw]) {
141
+ return idMap[raw];
142
+ }
143
+ if (raw.startsWith("bot_")) {
144
+ const mapped = idMap[raw.slice(4)];
145
+ if (mapped) {
146
+ return "bot_" + mapped;
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+
152
+ _rewriteBotIdsInValue(value, idMap) {
153
+ if (!value || !idMap) {
154
+ return value;
155
+ }
156
+ if (Array.isArray(value)) {
157
+ value.forEach((item) => this._rewriteBotIdsInValue(item, idMap));
158
+ return value;
159
+ }
160
+ if (typeof value !== "object") {
161
+ return value;
162
+ }
163
+ if (value.botId != null) {
164
+ const mapped = this._mappedBotId(value.botId, idMap);
165
+ if (mapped) {
166
+ value.botId = mapped;
167
+ }
168
+ }
169
+ Object.keys(value).forEach((key) => {
170
+ if (key === "botId") {
171
+ return;
172
+ }
173
+ this._rewriteBotIdsInValue(value[key], idMap);
174
+ });
175
+ return value;
176
+ }
177
+
178
+ _rewriteBotIdsInIntents(intents, idMap) {
179
+ (intents || []).forEach((intent) => {
180
+ this._rewriteBotIdsInValue(intent.actions, idMap);
181
+ this._rewriteBotIdsInValue(intent.attributes, idMap);
182
+ });
183
+ }
184
+
185
+ async _fillBotIdMapFromSourceBots(intents, idMap, nameMap) {
186
+ const botIds = this._collectBotIds(intents);
187
+ for (const botId of botIds) {
188
+ const lookupId = botId.startsWith("bot_") ? botId.slice(4) : botId;
189
+ if (idMap[lookupId] || idMap[botId]) {
190
+ continue;
191
+ }
192
+ try {
193
+ const sourceBot = await Chatbot.findById(lookupId).lean();
194
+ if (sourceBot?.name && nameMap[sourceBot.name]) {
195
+ idMap[lookupId] = nameMap[sourceBot.name];
196
+ }
197
+ } catch (err) {
198
+ winston.debug("(ChatbotController) skip botId remap for " + botId);
199
+ }
200
+ }
201
+ }
202
+
203
+ _buildSubagentPayload(subagentJson, parent_id, id_project) {
204
+ this._stripImportedIdentity(subagentJson);
205
+ return {
206
+ name: subagentJson.name,
207
+ description: subagentJson.description,
208
+ type: subagentJson.type || "tilebot",
209
+ subtype: "subagent",
210
+ language: subagentJson.language,
211
+ webhook_url: subagentJson.webhook_url,
212
+ webhook_enabled: subagentJson.webhook_enabled,
213
+ attributes: subagentJson.attributes,
214
+ parent_id: parent_id.toString(),
215
+ id_project: id_project.toString(),
216
+ template: "empty"
217
+ };
218
+ }
219
+
220
+ async findSubagents(chatbot_id, id_project, select) {
221
+ try {
222
+ let query = Chatbot.find({
223
+ id_project: id_project,
224
+ parent_id: chatbot_id,
225
+ trashed: { $in: [null, false] }
226
+ });
227
+ if (select) {
228
+ query = query.select(select);
229
+ } else {
230
+ query = query.sort({ createdAt: -1 });
231
+ }
232
+ return await query.lean().exec();
233
+ } catch (err) {
234
+ winston.error("(ChatbotController) findSubagents error: ", err);
235
+ throw new AppError(500, `Error finding subagents with id ${chatbot_id}`);
236
+ }
237
+
238
+ }
239
+
240
+ async findSubagentCaller(chatbot, id_project) {
241
+ if (!chatbot || !chatbot.parent_id) {
242
+ return null;
243
+ }
244
+
245
+ const chatbotId = chatbot._id.toString();
246
+ const parentId = chatbot.parent_id.toString();
247
+
248
+ let siblingIds;
249
+ try {
250
+ siblingIds = await Chatbot.find({
251
+ id_project: id_project,
252
+ parent_id: parentId,
253
+ _id: { $ne: chatbot._id }
254
+ }).select('_id').lean();
255
+ } catch (err) {
256
+ winston.error("(ChatbotController) findSubagentCaller error getting siblings: ", err);
257
+ throw new AppError(500, "Error checking subagent references");
258
+ }
259
+
260
+ const relatedIds = [parentId, ...siblingIds.map((bot) => bot._id.toString())];
261
+
262
+ const filter = {
263
+ id_project: id_project,
264
+ id_faq_kb: { $in: relatedIds },
265
+ $or: [{ 'actions.botId': chatbotId }]
266
+ };
267
+ if (chatbot.slug) {
268
+ filter.$or.push({ 'actions.botSlug': chatbot.slug });
269
+ }
270
+
271
+ try {
272
+ return await Faq.findOne(filter)
273
+ .select('_id id_faq_kb intent_display_name')
274
+ .lean();
275
+ } catch (err) {
276
+ winston.error("(ChatbotController) findSubagentCaller error: ", err);
277
+ throw new AppError(500, "Error checking subagent references");
278
+ }
279
+ }
280
+
281
+ async deleteSubagents(parent_id, id_project) {
282
+ const subagents = await this.findSubagents(parent_id, id_project, '_id');
283
+ await Promise.all(subagents.map(async (subagent) => {
284
+ await intentsController.deleteByChatbotId(subagent._id);
285
+ await Chatbot.deleteOne({ _id: subagent._id, id_project: id_project });
286
+ }));
287
+ }
288
+
289
+ async assertAreSubagents(id_project, parent_id, chatbot_ids) {
290
+ const parentId = parent_id.toString();
291
+ const idsToCheck = [...new Set(
292
+ (chatbot_ids || [])
293
+ .map((id) => (id == null ? '' : id.toString().trim()))
294
+ .filter((id) => id && id !== parentId)
295
+ )];
296
+
297
+ if (!idsToCheck.length) {
298
+ return true;
299
+ }
300
+
301
+ const subagents = await this.findSubagents(parent_id, id_project, '_id');
302
+ const foundSet = new Set(subagents.map((bot) => bot._id.toString()));
303
+ const invalid = idsToCheck.filter((id) => !foundSet.has(id));
304
+ if (invalid.length > 0) {
305
+ throw new AppError(400, `Subagent IDs are not valid: ${invalid.join(", ")}`);
306
+ }
307
+
308
+ return true;
309
+ }
310
+
311
+ async getChatbot(chatbot_id, id_project) {
312
+ let chatbot;
313
+ try {
314
+ chatbot = await Chatbot.findOne({ _id: chatbot_id, id_project: id_project });
315
+ } catch (err) {
316
+ winston.error("(ChatbotController) getChatbot error: ", err);
317
+ throw new AppError(500, `Error finding chatbot with id ${chatbot_id}`);
318
+ }
319
+ if (!chatbot) {
320
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id} for project ${id_project}`);
321
+ }
322
+ return chatbot;
323
+ }
324
+
325
+ async getChatbotById(chatbot_id) {
326
+ let chatbot;
327
+ try {
328
+ chatbot = await Chatbot.findById(chatbot_id);
329
+ } catch (err) {
330
+ winston.error("(ChatbotController) getChatbotById error: ", err);
331
+ throw new AppError(500, `Error finding chatbot with id ${chatbot_id}`);
332
+ }
333
+ if (!chatbot) {
334
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id}`);
335
+ }
336
+ return chatbot;
337
+ }
338
+
339
+ async _importIntents(chatbot_id, id_project, user_id, intents, overwrite) {
340
+ if (!intents?.length) {
341
+ return;
342
+ }
343
+ if (overwrite) {
344
+ await intentsController.upsertMany(chatbot_id, id_project, user_id, intents);
345
+ } else {
346
+ await intentsController.createMany(
347
+ chatbot_id,
348
+ id_project,
349
+ user_id,
350
+ intents,
351
+ { skipDuplicates: true }
352
+ );
353
+ }
354
+ }
355
+
356
+ async _createSubagent(id_project, user_id, payload) {
357
+ let subagent;
358
+ try {
359
+ subagent = await faqService.create(id_project, user_id, payload);
360
+ } catch (err) {
361
+ winston.error("(ChatbotController) create subagent error: ", err);
362
+ throw new AppError(400, "Error creating subagent");
363
+ }
364
+
365
+ if (payload.attributes) {
366
+ this._rewriteRulesParticipants(payload.attributes, subagent._id);
367
+ subagent = await this.updatedChatbot(subagent._id, id_project, { attributes: payload.attributes });
368
+ }
369
+
370
+ return subagent;
371
+ }
372
+
373
+ async _updateSubagent(subagent, payload, id_project) {
374
+ subagent.id_project = id_project.toString();
375
+ for (const field of IMPORT_OVERRIDE_FIELDS) {
376
+ if (payload[field] !== undefined && payload[field] !== null) {
377
+ subagent[field] = payload[field];
378
+ }
379
+ }
380
+
381
+ if (payload.attributes) {
382
+ this._rewriteRulesParticipants(payload.attributes, subagent._id);
383
+ subagent.attributes = payload.attributes;
384
+ }
385
+
386
+ try {
387
+ subagent = await Chatbot.findByIdAndUpdate(subagent._id, subagent, { new: true });
388
+ } catch (err) {
389
+ winston.error("(ChatbotController) update subagent error: ", err);
390
+ throw new AppError(400, "Error updating subagent");
391
+ }
392
+
393
+ botEvent.emit("faqbot.update", subagent);
394
+ return subagent;
395
+ }
396
+
397
+ async _importSubagents(parent, subagentsJson, id_project, user_id, replace, overwrite, idMap, nameMap) {
398
+ if (replace) {
399
+ await this.deleteSubagents(parent._id, id_project);
400
+ }
401
+
402
+ const imported = [];
403
+ if (!subagentsJson?.length) {
404
+ return imported;
405
+ }
406
+
407
+ for (const subagentJson of subagentsJson) {
408
+ const sourceId = this._sourceId(subagentJson);
409
+ const payload = this._buildSubagentPayload(subagentJson, parent._id, id_project);
410
+ let subagent = await Chatbot.findOne({
411
+ id_project: id_project,
412
+ parent_id: parent._id.toString(),
413
+ name: subagentJson.name
414
+ });
415
+
416
+ if (overwrite) {
417
+ if (subagent) {
418
+ subagent = await this._updateSubagent(subagent, payload, id_project);
419
+ } else {
420
+ subagent = await this._createSubagent(id_project, user_id, payload);
421
+ }
422
+ } else if (!subagent) {
423
+ subagent = await this._createSubagent(id_project, user_id, payload);
424
+ } else {
425
+ winston.debug("Skip existing subagent with name: " + subagentJson.name);
426
+ }
427
+
428
+ if (subagent) {
429
+ const newId = subagent._id.toString();
430
+ if (sourceId) {
431
+ idMap[sourceId] = newId;
432
+ }
433
+ if (subagent.name) {
434
+ nameMap[subagent.name] = newId;
435
+ }
436
+ imported.push({ subagent, intents: subagentJson.intents });
437
+ }
438
+ }
439
+ return imported;
440
+ }
441
+
442
+ async _remapImportedBotIds(json, importedSubagents, idMap, nameMap, keepExternalBotIds) {
443
+ if (!keepExternalBotIds) {
444
+ const allIntents = [
445
+ ...(json.intents || []),
446
+ ...importedSubagents.flatMap((item) => item.intents || [])
447
+ ];
448
+ await this._fillBotIdMapFromSourceBots(allIntents, idMap, nameMap);
449
+ }
450
+ this._rewriteBotIdsInIntents(json.intents, idMap);
451
+ importedSubagents.forEach((item) => {
452
+ this._rewriteBotIdsInIntents(item.intents, idMap);
453
+ });
454
+ }
455
+
456
+ async import(json, id_project, chatbot_id, create, replace, overwrite, user_id, skipSubagents) {
457
+ let chatbot;
458
+ const sourceParentId = this._sourceId(json);
459
+ this._stripImportedIdentity(json);
460
+
461
+ if (create) {
462
+ json.template = "empty";
463
+ try {
464
+ chatbot = await faqService.create(id_project, user_id, json);
465
+ } catch (err) {
466
+ winston.error("(ChatbotController) import create error: ", err);
467
+ throw new AppError(400, "Error creating new chatbot");
468
+ }
469
+
470
+ if (json.attributes) {
471
+ this._rewriteRulesParticipants(json.attributes, chatbot._id);
472
+ chatbot = await this.updatedChatbot(chatbot._id, id_project, { attributes: json.attributes });
473
+ }
474
+ } else {
475
+ if (!chatbot_id) {
476
+ throw new AppError(400, "With replace or overwrite option a id_faq_kb must be provided");
477
+ }
478
+
479
+ chatbot = await this.getChatbot(chatbot_id, id_project);
480
+
481
+ for (const field of IMPORT_OVERRIDE_FIELDS) {
482
+ if (json[field]) {
483
+ chatbot[field] = json[field];
484
+ }
485
+ }
486
+
487
+ if (json.attributes) {
488
+ this._rewriteRulesParticipants(json.attributes, chatbot._id);
489
+ chatbot.attributes = json.attributes;
490
+ }
491
+
492
+ try {
493
+ chatbot = await Chatbot.findByIdAndUpdate(chatbot._id, chatbot, { new: true });
494
+ } catch (err) {
495
+ winston.error("(ChatbotController) import update error: ", err);
496
+ throw new AppError(400, "Error updating chatbot");
497
+ }
498
+ if (!chatbot) {
499
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id}`);
500
+ }
501
+
502
+ botEvent.emit("faqbot.update", chatbot);
503
+ }
504
+
505
+ if (replace) {
506
+ await intentsController.deleteByChatbotId(chatbot._id);
507
+ }
508
+
509
+ const idMap = {};
510
+ const nameMap = {};
511
+ if (sourceParentId) {
512
+ idMap[sourceParentId] = chatbot._id.toString();
513
+ }
514
+ if (chatbot.name) {
515
+ nameMap[chatbot.name] = chatbot._id.toString();
516
+ }
517
+
518
+ let importedSubagents = [];
519
+ if (!skipSubagents) {
520
+ importedSubagents = await this._importSubagents(
521
+ chatbot,
522
+ json.subagents,
523
+ id_project,
524
+ user_id,
525
+ replace,
526
+ overwrite,
527
+ idMap,
528
+ nameMap
529
+ );
530
+ }
531
+ await this._remapImportedBotIds(json, importedSubagents, idMap, nameMap, skipSubagents);
532
+
533
+ await this._importIntents(chatbot._id, id_project, user_id, json.intents, overwrite);
534
+ await Promise.all(importedSubagents.map((item) =>
535
+ this._importIntents(item.subagent._id, id_project, user_id, item.intents, overwrite)
536
+ ));
537
+
538
+ return chatbot;
539
+ }
540
+
541
+ async _exportJson(chatbot, id_project, { exportGlobals = false } = {}) {
542
+ const chatbotData = chatbot.toObject ? chatbot.toObject() : { ...chatbot };
543
+ if (!exportGlobals && chatbotData.attributes) {
544
+ chatbotData.attributes = { ...chatbotData.attributes };
545
+ delete chatbotData.attributes.globals;
546
+ }
547
+
548
+ const intents = await intentsController.getIntents(chatbot._id);
549
+ const subagents = await this.findSubagents(chatbot._id, id_project);
550
+ await Promise.all(subagents.map(async (subagent) => {
551
+ subagent.intents = await intentsController.getIntents(subagent._id);
552
+ }));
553
+
554
+ const json = JSON.parse(this.export(chatbotData, intents, false, subagents));
555
+ json._id = chatbot._id.toString();
556
+ return json;
557
+ }
558
+
559
+ async restoreFromPublished(draft_id, publish_id, id_project, user_id) {
560
+ const draft = await this.getChatbot(draft_id, id_project);
561
+ if (draft.root_id) {
562
+ throw new AppError(400, "Cannot restore into a published chatbot");
563
+ }
564
+
565
+ const published = await this.getChatbot(publish_id, id_project);
566
+ if (!published.root_id || published.root_id.toString() !== draft._id.toString()) {
567
+ throw new AppError(400, `Chatbot ${publish_id} is not a published version of ${draft_id}`);
568
+ }
569
+
570
+ const json = await this._exportJson(published, id_project);
571
+ if (draft.attributes?.globals) {
572
+ json.attributes = { ...(json.attributes || {}), globals: draft.attributes.globals };
573
+ }
574
+
575
+ // A publish snapshot has no cloned subagents. Restoring it must not delete the draft ones.
576
+ const skipSubagents = !json.subagents?.length;
577
+ const restored = await this.import(json, id_project, draft_id, false, true, false, user_id, skipSubagents);
578
+ return this.updatedChatbot(restored._id, id_project, { modified: true });
579
+ }
580
+
581
+ async updatedChatbot(chatbot_id, id_project, update) {
582
+ let chatbot;
583
+ try {
584
+ chatbot = await Chatbot.findOneAndUpdate({ _id: chatbot_id, id_project: id_project }, update, { new: true });
585
+ } catch (err) {
586
+ winston.error("(ChatbotController) updatedChatbot error: ", err);
587
+ throw new AppError(500, `Error updating chatbot with id ${chatbot_id}`);
588
+ }
589
+ if (!chatbot) {
590
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id} for project ${id_project}`);
591
+ }
592
+ return chatbot;
593
+ }
594
+
595
+
596
+ }
597
+
598
+ const chatbotController = new ChatbotController();
599
+ module.exports = chatbotController;