@tiledesk/tiledesk-server
Version:
The Tiledesk server module
709 lines (577 loc) • 21 kB
JavaScript
var express = require('express');
var router = express.Router();
var Faq = require("../models/faq");
var Faq_kb = require("../models/faq_kb");
var multer = require('multer')
const faqBotEvent = require('../event/faqBotEvent');
var winston = require('../config/winston');
const faqEvent = require('../event/faqBotEvent')
var parsecsv = require("fast-csv");
const botEvent = require('../event/botEvent');
const uuidv4 = require('uuid/v4');
csv = require('csv-express');
csv.separator = ';';
const axios = require("axios").default;
var configGlobal = require('../config/global');
const roleConstants = require('../models/roleConstants');
const roleChecker = require('../middleware/has-role');
const { ChatbotService } = require('../services/chatbotService');
const apiUrl = process.env.API_URL || configGlobal.apiUrl;
let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;
let uploadlimits = undefined;
if (MAX_UPLOAD_FILE_SIZE) {
uploadlimits = {fileSize: parseInt(MAX_UPLOAD_FILE_SIZE)} ;
winston.debug("Max upload file size is : " + MAX_UPLOAD_FILE_SIZE);
} else {
winston.debug("Max upload file size is infinity");
}
var upload = multer({limits: uploadlimits});
// POST CSV FILE UPLOAD FROM CLIENT
router.post('/uploadcsv', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), upload.single('uploadFile'), function (req, res, next) {
winston.debug(' -> -> REQ BODY ', req.body);
winston.debug(' -> ID FAQ-KB ', req.body.id_faq_kb);
winston.debug(' -> DELIMITER ', req.body.delimiter);
winston.debug(' -> FILE ', req.file);
var id_faq_kb = req.body.id_faq_kb;
winston.debug('id_faq_kb: ' + id_faq_kb);
var delimiter = req.body.delimiter || ";";
winston.debug('delimiter: ' + delimiter);
var csv = req.file.buffer.toString('utf8');
winston.debug("--> csv: ", csv)
// winston.debug(' -> CSV STRING ', csv);
// res.json({ success: true, msg: 'Importing CSV...' });
// PARSE CSV
Faq_kb.findById(id_faq_kb).exec(function (err, faq_kb) {
if (err) {
return res.status(500).send({ success: false, msg: 'Error getting object.' });
}
if (!faq_kb) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
winston.debug('faq_kb ', faq_kb.toJSON());
// getFaqKbKeyById(req.body.id_faq_kb, function (remote_faqkb_key) {
parsecsv.parseString(csv, { headers: false, delimiter: delimiter })
.on("data", function (data) {
winston.debug('PARSED CSV ', data);
winston.debug('--> PARSED CSV ', data);
var question = data[0]
//var answer = data[1]
var intent_id = data[2];
var intent_display_name = data[3];
var webhook_enabled = data[4];
var actions = [
{
_tdActionType: "reply",
_tdActionId: uuidv4(),
text: data[1],
attributes: {
commands: [
{
type: "wait",
time: 500
},
{
type: "message",
message: {
type: "text",
text: data[1]
}
}
]
}
}
]
var webhook_enabled_boolean = false;
if (webhook_enabled) {
webhook_enabled_boolean = (webhook_enabled == 'true');
}
// var row = {question: element.question, answer: element.answer,
// intent_id: element.intent_id, intent_display_name: element.intent_display_name,
// webhook_enabled: element.webhook_enabled || false }
var newFaq = new Faq({
id_faq_kb: id_faq_kb,
question: question,
//answer: answer,
actions: actions,
intent_id: intent_id,
intent_display_name: intent_display_name,
webhook_enabled: webhook_enabled_boolean,
language: faq_kb.language,
id_project: req.projectid,
createdBy: req.user.id,
updatedBy: req.user.id
});
winston.debug("--> newFaq: ", JSON.stringify(newFaq, null, 2));
newFaq.save(function (err, savedFaq) {
if (err) {
winston.error('--- > ERROR uploadcsv', err)
// return res.status(500).send({ success: false, msg: 'Error saving object.' }); // ADDED 24 APR
} else {
faqBotEvent.emit('faq.create', savedFaq);
}
});
})
.on("end", function () {
winston.debug("PARSE DONE");
//faqBotEvent.emit('faq_train.create', id_faq_kb)
res.json({ success: true, msg: 'CSV Parsed' });
})
.on("error", function (err) {
winston.error("PARSE ERROR uploadcsv", err);
res.json({ success: false, msg: 'Parsing error' });
});
});
});
router.post('/', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
winston.debug(req.body);
Faq_kb.findById(req.body.id_faq_kb).exec(function (err, faq_kb) {
if (err) {
return res.status(500).send({ success: false, msg: 'Error getting object.' });
}
if (!faq_kb) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
winston.debug('faq_kb ', faq_kb.toJSON());
var newFaq = new Faq({
_id: req.body._id,
id_faq_kb: req.body.id_faq_kb,
question: req.body.question,
answer: req.body.answer,
reply: req.body.reply,
form: req.body.form,
enabled: true,
id_project: req.projectid,
topic: req.body.topic,
language: faq_kb.language,
webhook_enabled: req.body.webhook_enabled,
intent_display_name: req.body.intent_display_name,
createdBy: req.user.id,
updatedBy: req.user.id
});
if (req.body.enabled != undefined) {
newFaq.enabled = req.body.enabled;
}
if (req.body.actions) {
newFaq.actions = req.body.actions
}
if (req.body.attributes) {
newFaq.attributes = req.body.attributes
}
if (req.body.intent_id) {
newFaq.intent_id = req.body.intent_id;
}
newFaq.save(function (err, savedFaq) {
if (err) {
if (err.code == 11000) {
return res.status(409).send({ success: false, msg: 'Duplicate intent_display_name.' });
} else {
winston.debug('--- > ERROR ', err)
return res.status(500).send({ success: false, msg: 'Error saving object.' });
}
}
winston.debug('1. ID OF THE NEW FAQ CREATED ', savedFaq._id)
winston.debug('1. QUESTION OF THE NEW FAQ CREATED ', savedFaq.question)
winston.debug('1. ANSWER OF THE NEW FAQ CREATED ', savedFaq.answer)
winston.debug('1. ID FAQKB GET IN THE OBJECT OF NEW FAQ CREATED ', savedFaq.id_faq_kb);
faqBotEvent.emit('faq.create', savedFaq);
//faqBotEvent.emit('faq_train.create', req.body.id_faq_kb)
res.json(savedFaq);
});
});
});
router.post('/ops_update', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), async (req, res) => {
let id_faq_kb = req.body.id_faq_kb;
let operations = req.body.operations;
let chatbotService = new ChatbotService();
for (let op of operations) {
let HTTPREQUEST;
let id;
// method post
switch (op.type) {
case 'post':
HTTPREQUEST = {
url: apiUrl + '/' + req.projectid + '/faq/',
headers: {
'Content-Type': 'application/json',
'Authorization': req.headers.authorization
},
json: op.intent,
method: 'post'
}
winston.debug("operation HTTPREQUEST: ", HTTPREQUEST);
myrequest(
HTTPREQUEST, async (err, resbody) => {
if (err) {
winston.error("err performing operation: ", err);
} else {
winston.debug("\n\nresbody operation: ", resbody);
chatbotService.setModified(id_faq_kb, true)
}
}
)
break;
// method put
case 'put':
id = op.intent._id;
if (op.intent.intent_id) {
id = "intentId" + op.intent.intent_id;
}
HTTPREQUEST = {
url: apiUrl + '/' + req.projectid + '/faq/' + id,
headers: {
'Content-Type': 'application/json',
'Authorization': req.headers.authorization
},
json: op.intent,
method: 'put'
}
winston.debug("operation HTTPREQUEST: ", HTTPREQUEST);
myrequest(
HTTPREQUEST, async (err, resbody) => {
if (err) {
winston.error("err performing operation: ", err);
} else {
winston.debug("\n\nresbody operation: ", resbody);
chatbotService.setModified(id_faq_kb, true)
}
}
)
break;
// method patch
case 'patch':
HTTPREQUEST = {
url: apiUrl + '/' + req.projectid + '/faq/' + op.intent._id + '/attributes',
headers: {
'Content-Type': 'application/json',
'Authorization': req.headers.authorization
},
json: op.intent.attributes,
method: 'patch'
}
winston.debug("operation HTTPREQUEST: ", HTTPREQUEST);
myrequest(
HTTPREQUEST, async (err, resbody) => {
if (err) {
winston.error("err performing operation: ", err);
} else {
winston.debug("\n\nresbody operation: ", resbody);
chatbotService.setModified(id_faq_kb, true)
}
}
)
break;
// method delete
case 'delete':
id = op.intent._id;
if (op.intent.intent_id) {
id = "intentId" + op.intent.intent_id;
}
HTTPREQUEST = {
url: apiUrl + '/' + req.projectid + '/faq/' + id + '?id_faq_kb=' + id_faq_kb,
headers: {
'Content-Type': 'application/json',
'Authorization': req.headers.authorization
},
method: 'delete'
}
winston.debug("operation HTTPREQUEST: ", HTTPREQUEST);
myrequest(
HTTPREQUEST, async (err, resbody) => {
if (err) {
winston.error("err performing operation: ", err);
} else {
winston.debug("\n\nresbody operation: ", resbody);
chatbotService.setModified(id_faq_kb, true)
}
}
)
break;
}
}
res.status(200).send({ success: true });
})
router.patch('/:faqid/attributes', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
let data = req.body;
winston.debug("data: ", data);
// aggiugnere controllo su intent_id qui
Faq.findById(req.params.faqid, function (err, updatedFaq) {
if (err) {
winston.error('Find Faq by id ERROR: ', err);
return res.status(500).send({ success: false, msg: 'Error updating object.' });
}
if (!updatedFaq) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
if (!updatedFaq.attributes) {
winston.debug("empty attributes");
winston.debug("empty attributes");
updatedFaq.attributes = {};
}
winston.debug("updatedFaq attributes", updatedFaq.attributes);
Object.keys(data).forEach(function (key) {
var val = data[key];
winston.debug("data attributes" + key + " " + val);
updatedFaq.attributes[key] = val;
})
winston.debug("updatedFaq: ", updatedFaq);
winston.debug("updatedFaq attributes: ", updatedFaq.attributes);
winston.debug("updatedBot attributes", updatedFaq.attributes)
updatedFaq.markModified('attributes');
//cache invalidation
updatedFaq.save(function (err, savedFaq) {
if (err) {
winston.error("saving faq attributes ERROR: ", err);
return res.status(500).send({ success: false, msg: 'Error saving object.' });
}
winston.debug("saved faq attributes", savedFaq.toObject());
winston.verbose("saved faq attributes", savedFaq.toObject());
faqBotEvent.emit('faq.update', savedFaq);
res.json(savedFaq);
})
})
})
router.put('/:faqid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
winston.debug('UPDATE FAQ ', req.body);
let faqid = req.params.faqid;
if (!req.body.id_faq_kb) {
return res.status(422).send({ err: "Missing id_faq_kb in Request Body" })
}
let id_faq_kb = req.body.id_faq_kb;
var update = {};
if (req.body.intent != undefined) {
update.intent = req.body.intent;
}
if (req.body.question != undefined) {
update.question = req.body.question;
}
if (req.body.answer != undefined) {
update.answer = req.body.answer;
}
if (req.body.topic != undefined) {
update.topic = req.body.topic;
}
if (req.body.status != undefined) {
update.status = req.body.status;
}
if (req.body.language != undefined) {
update.language = req.body.language;
}
if (req.body.intent_display_name != undefined) {
update.intent_display_name = req.body.intent_display_name;
}
if (req.body.webhook_enabled != undefined) {
update.webhook_enabled = req.body.webhook_enabled;
}
if (req.body.enabled != undefined) {
update.enabled = req.body.enabled;
}
if (req.body.reply != undefined) {
update.reply = req.body.reply;
}
if (req.body.form != undefined) {
update.form = req.body.form;
}
if (req.body.actions != undefined) {
update.actions = req.body.actions;
}
if (req.body.attributes != undefined) {
update.attributes = req.body.attributes;
}
if (req.body.agents_available != undefined) {
update.agents_available = req.body.agents_available;
}
if (faqid.startsWith("intentId")) {
let intent_id = faqid.substring(8);
//Faq.findOneAndUpdate({ id_faq_kb: id_faq_kb, intent_id: intent_id }, update, { new: true, upsert: true }, (err, updatedFaq) => {
Faq.findOneAndUpdate({ id_faq_kb: id_faq_kb, intent_id: intent_id }, update, { new: true }, (err, updatedFaq) => {
if (err) {
if (err.code == 11000) {
return res.status(409).send({ success: false, msg: 'Duplicate intent_display_name.' });
} else {
return res.status(500).send({ success: false, msg: 'Error updating object.' });
}
}
faqBotEvent.emit('faq.update', updatedFaq);
//faqBotEvent.emit('faq_train.update', updatedFaq.id_faq_kb);
res.status(200).send(updatedFaq);
})
} else {
Faq.findByIdAndUpdate(req.params.faqid, update, { new: true, upsert: true }, function (err, updatedFaq) {
if (err) {
if (err.code == 11000) {
return res.status(409).send({ success: false, msg: 'Duplicate intent_display_name.' });
} else {
return res.status(500).send({ success: false, msg: 'Error updating object.' });
}
}
faqBotEvent.emit('faq.update', updatedFaq);
//faqBotEvent.emit('faq_train.update', updatedFaq.id_faq_kb);
res.status(200).send(updatedFaq);
// updateRemoteFaq(updatedFaq)
});
}
});
// DELETE REMOTE AND LOCAL FAQ
router.delete('/:faqid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
winston.debug('DELETE FAQ - FAQ ID ', req.params.faqid);
let faqid = req.params.faqid;
let id_faq_kb;
if (req.query && req.query.id_faq_kb) {
id_faq_kb = req.query.id_faq_kb;
}
if (faqid.startsWith("intentId")) {
let intent_id = faqid.substring(8);
if (!id_faq_kb) {
return res.status(500).send({ success: false, msg: "Unable to delete object. Query param 'id_faq_kb' is mandatory if you want to delete via intent_id" })
}
Faq.findOneAndDelete({ intent_id: intent_id, id_faq_kb: id_faq_kb }, (err, faq) => {
if (err) {
return res.status(500).send({ success: false, msg: "Error deleting object." });
}
if (!faq) {
return res.status(404).send({ success: false, msg: "Error deleting object. The object does not exists." })
}
winston.debug('Deleted FAQ ', faq);
faqBotEvent.emit('faq.delete', faq);
//faqBotEvent.emit('faq_train.delete', faq.id_faq_kb);
res.status(200).send(faq);
})
} else {
Faq.findByIdAndRemove({ _id: req.params.faqid }, function (err, faq) {
if (err) {
return res.status(500).send({ success: false, msg: 'Error deleting object.' });
}
winston.debug('Deleted FAQ ', faq);
faqBotEvent.emit('faq.delete', faq);
//faqBotEvent.emit('faq_train.delete', faq.id_faq_kb);
res.status(200).send(faq);
});
}
});
// EXPORT FAQ TO CSV
router.get('/csv', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
var query = {};
winston.debug('req.query', req.query);
if (req.query.id_faq_kb) {
query.id_faq_kb = req.query.id_faq_kb;
}
winston.debug('EXPORT FAQS TO CSV QUERY', query);
Faq.find(query, 'question answer intent_id intent_display_name webhook_enabled -_id').lean().exec(function (err, faqs) {
if (err) {
winston.debug('EXPORT FAQS TO CSV ERR', err)
return (err)
};
var csv = [];
faqs.forEach(function (element) {
var row = {
question: element.question, answer: element.answer,
intent_id: element.intent_id, intent_display_name: element.intent_display_name,
webhook_enabled: element.webhook_enabled || false
}
csv.push(row);
});
winston.debug('EXPORT FAQ TO CSV FAQS', csv)
res.csv(csv, true)
// res.json(faq);
});
});
router.get('/:faqid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
winston.debug(req.body);
Faq.findById(req.params.faqid, function (err, faq) {
if (err) {
return res.status(500).send({ success: false, msg: 'Error getting object.' });
}
if (!faq) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
res.json(faq);
});
});
router.get('/', roleChecker.hasRoleOrTypes('agent', ['bot', 'subscription']), function (req, res, next) {
var query = {};
winston.debug("GET ALL FAQ OF THE BOT ID (req.query): ", req.query);
let restricted_mode;
let project_user = req.projectuser;
if (project_user && project_user.role === roleConstants.AGENT) {
restricted_mode = true;
}
if (req.query.id_faq_kb) {
query.id_faq_kb = req.query.id_faq_kb;
}
var limit = 3000; // Number of request per page
if (req.query.limit) {
limit = parseInt(req.query.limit);
winston.debug('faq ROUTE - limit: ' + limit);
}
var page = 0;
if (req.query.page) {
page = req.query.page;
}
var skip = page * limit;
winston.debug('faq ROUTE - SKIP PAGE ', skip);
if (req.query.text) {
winston.debug("GET FAQ req.projectid", req.projectid);
// query.$text = req.query.text;
query.$text = { "$search": req.query.text };
query.id_project = req.projectid
}
if (req.query.intent_display_name) {
query.intent_display_name = req.query.intent_display_name
}
if (restricted_mode) {
query.agents_available = {
$in: [ null, true ]
}
}
winston.debug("GET FAQ query", query);
// query.$text = {"$search": "question"};
// TODO ORDER BY SCORE
// return Faq.find(query, {score: { $meta: "textScore" } })
// .sort( { score: { $meta: "textScore" } } ) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score
// in testing...
// return Faq.search('a closer', (err, result) => {
// console.log("result: ", result);
// })
return Faq.find(query)
.skip(skip).limit(limit)
.populate({ path: 'faq_kb' })
.lean()
.exec(function (err, faqs) {
winston.debug("GET FAQ ", faqs);
if (err) {
winston.debug('GET FAQ err ', err)
return next(err)
};
if (restricted_mode === true) {
faqs = faqs.map(({ webhook_enabled, faq_kb, actions, attributes, createdBy, createdAt, updatedAt, __v, ...keepAttrs }) => keepAttrs)
faqs = faqs.filter(f => (f.intent_display_name !== "start" && f.intent_display_name !== 'defaultFallback'));
}
res.status(200).send(faqs);
});
});
async function myrequest(options, callback) {
winston.debug("myrequest options: ", options)
return await axios({
url: options.url,
method: options.method,
data: options.json,
params: options.params,
headers: options.headers
}).then((res) => {
if (res && res.status == 200 && res.data) {
if (callback) {
callback(null, res.data);
}
}
else {
if (callback) {
callback(TiledeskClient.getErr({ message: "Response status not 200" }, options, res), null, null);
}
}
}).catch((err) => {
if (callback) {
callback(err, null, null);
}
})
}
module.exports = router;