@tiledesk/tiledesk-server
Version:
The Tiledesk server module
1,488 lines (1,104 loc) • 79.4 kB
JavaScript
var express = require('express');
var router = express.Router();
var Request = require("../models/request");
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var moment = require('moment');
var requestService = require('../services/requestService');
var emailService = require('../services/emailService');
var departmentService = require('../services/departmentService');
var winston = require('../config/winston');
const requestEvent = require('../event/requestEvent');
var Subscription = require("../models/subscription");
var leadService = require('../services/leadService');
var messageService = require('../services/messageService');
const uuidv4 = require('uuid/v4');
var MessageConstants = require("../models/messageConstants");
var Message = require("../models/message");
var cacheUtil = require('../utils/cacheUtil');
var RequestConstants = require("../models/requestConstants");
var cacheEnabler = require("../services/cacheEnabler");
var Project_user = require("../models/project_user");
var Lead = require("../models/lead");
var UIDGenerator = require("../utils/UIDGenerator");
let { Publisher } = require("@tiledesk/tiledesk-multi-worker");
csv = require('csv-express');
csv.separator = ';';
const { check, validationResult } = require('express-validator');
const RoleConstants = require('../models/roleConstants');
const eventService = require('../pubmodules/events/eventService');
const { Scheduler } = require('../services/Scheduler');
const faq_kb = require('../models/faq_kb');
//const JobManager = require('../utils/jobs-worker-queue-manager-v2/JobManagerV2');
// var messageService = require('../services/messageService');
const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;
let jobManager = new Publisher(AMQP_MANAGER_URL, {
debug: false,
queueName: "conversation-tags_queue",
exchange: "tiledesk-multi",
topic: "conversation-tags",
})
jobManager.connectAndStartPublisher((status, error) => {
if (error) {
winston.error("connectAndStartPublisher error: ", error);
} else {
winston.info("KbRoute - ConnectPublisher done with status: ", status);
}
})
router.post('/simple', [check('first_text').notEmpty()], async (req, res) => {
var startTimestamp = new Date();
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
if (req.projectuser) {
winston.debug("req.projectuser", req.projectuser);
}
var project_user = req.projectuser;
})
// undocumented, used by test
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.create
router.post('/',
[
check('first_text').notEmpty(),
],
async (req, res) => {
var startTimestamp = new Date();
winston.debug("request create timestamp: " + startTimestamp);
winston.debug("req.body", req.body);
winston.debug("req.projectid: " + req.projectid);
winston.debug("req.user.id: " + req.user.id);
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
if (req.projectuser) {
winston.debug("req.projectuser", req.projectuser);
}
var project_user = req.projectuser;
var sender = req.body.sender;
var fullname = req.body.senderFullname || req.user.fullName;
var email = req.body.email || req.user.email;
let messageStatus = req.body.status || MessageConstants.CHAT_MESSAGE_STATUS.SENDING;
winston.debug('messageStatus: ' + messageStatus);
var request_id = req.body.request_id || 'support-group-' + req.projectid + "-" + UIDGenerator.generate();
winston.debug('request_id: ' + request_id);
if (sender) {
var isObjectId = mongoose.Types.ObjectId.isValid(sender);
winston.debug("isObjectId:" + isObjectId);
var queryProjectUser = { id_project: req.projectid, status: "active" };
if (isObjectId) {
queryProjectUser.id_user = sender;
} else {
queryProjectUser.uuid_user = sender;
}
winston.debug("queryProjectUser", queryProjectUser);
project_user = await Project_user.findOne(queryProjectUser).populate({ path: 'id_user', select: { 'firstname': 1, 'lastname': 1, 'email': 1 } })
winston.debug("project_user", project_user);
if (!project_user) {
return res.status(403).send({ success: false, msg: 'Unauthorized. Project_user not found with user id : ' + sender });
}
if (project_user.id_user) {
fullname = project_user.id_user.fullName;
winston.debug("pu fullname: " + fullname);
email = project_user.id_user.email;
winston.debug("pu email: " + email);
} else if (project_user.uuid_user) {
var lead = await Lead.findOne({ lead_id: project_user.uuid_user, id_project: req.projectid });
winston.debug("lead: ", lead);
if (lead) {
fullname = lead.fullname;
winston.debug("lead fullname: " + fullname);
email = lead.email;
winston.debug("lead email: " + email);
} else {
winston.warn("lead not found: " + JSON.stringify({ lead_id: project_user.uuid_user, id_project: req.projectid }));
}
} else {
winston.warn("pu fullname and email empty");
}
}
// createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes) {
return leadService.createIfNotExistsWithLeadId(sender || req.user._id, fullname, email, req.projectid, null, req.body.attributes || req.user.attributes)
.then(function (createdLead) {
var new_request = {
request_id: request_id,
project_user_id: req.projectuser._id,
lead_id: createdLead._id,
id_project: req.projectid,
first_text: req.body.first_text,
departmentid: req.body.departmentid,
sourcePage: req.body.sourcePage,
language: req.body.language,
userAgent: req.body.userAgent,
status: null,
createdBy: req.user._id,
attributes: req.body.attributes,
subject: req.body.subject,
preflight: undefined,
channel: req.body.channel,
location: req.body.location,
participants: req.body.participants,
lead: createdLead, requester: project_user,
priority: req.body.priority,
followers: req.body.followers,
};
return requestService.create(new_request).then(function (savedRequest) {
// createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes) {
// return requestService.createWithIdAndRequester(request_id, req.projectuser._id, createdLead._id, req.projectid,
// req.body.text, req.body.departmentid, req.body.sourcePage,
// req.body.language, req.body.userAgent, null, req.user._id, req.body.attributes, req.body.subject).then(function (savedRequest) {
// return messageService.create(sender || req.user._id, fullname, request_id, req.body.text,
// req.projectid, req.user._id, messageStatus, req.body.attributes, req.body.type, req.body.metadata, req.body.language, undefined, req.body.channel).then(function(savedMessage){
// create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata) {
// return messageService.create(req.body.sender || req.user._id, req.body.senderFullname || req.user.fullName, request_id, req.body.text,
// req.projectid, req.user._id, messageStatus, req.body.attributes, req.body.type, req.body.metadata).then(function(savedMessage){
winston.debug('res.json(savedRequest)');
var endTimestamp = new Date();
winston.verbose("request create end: " + (endTimestamp - startTimestamp));
return res.json(savedRequest);
// });
// });
}).catch((err) => {
winston.error("(Request) create request error ", err)
return res.status(500).send({ success: false, message: "Unable to create request", err: err })
});
}).catch(function (err) {
winston.error('Error saving request.', err);
return res.status(500).send({ success: false, msg: 'Error saving object.', err: err });
});
});
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.patch('/:requestid', function (req, res) {
winston.debug(req.body);
// const update = _.assign({ "updatedAt": new Date() }, req.body);
//const update = req.body;
const update = {};
if (req.body.lead) {
update.lead = req.body.lead;
}
// TODO test it. does it work?
if (req.body.status) {
update.status = req.body.status;
}
if (req.body.tags) {
update.tags = req.body.tags;
}
// if (req.body.notes) {
// update.notes = req.body.notes;
// }
if (req.body.rating) {
update.rating = req.body.rating;
}
if (req.body.rating_message) {
update.rating_message = req.body.rating_message;
}
if (req.body.sourcePage) {
update.sourcePage = req.body.sourcePage;
}
if (req.body.language) {
update.language = req.body.language;
}
if (req.body.first_text) {
update.first_text = req.body.first_text;
}
if (req.body.subject) {
update.subject = req.body.subject;
}
if (req.body.location) {
update.location = req.body.location;
}
if (req.body.priority) {
update.priority = req.body.priority;
}
if (req.body.smartAssignment != undefined) {
update.smartAssignment = req.body.smartAssignment;
}
if (req.body.workingStatus != undefined) {
update.workingStatus = req.body.workingStatus;
}
if (req.body.channelName) {
update["channel.name"] = req.body.channelName;
}
winston.verbose("Request patch update", update);
//cacheinvalidation
return Request.findOneAndUpdate({ "request_id": req.params.requestid, "id_project": req.projectid }, { $set: update }, { new: true, upsert: false })
.populate('lead')
.populate('department')
.populate('participatingBots')
.populate('participatingAgents')
.populate({ path: 'requester', populate: { path: 'id_user' } })
.exec(function (err, request) {
if (err) {
winston.error('Error patching request.', err);
return res.status(500).send({ success: false, msg: 'Error updating object.' });
}
if (!request) {
return res.status(404).send({ success: false, msg: 'Request not found' });
}
requestEvent.emit("request.update", request);
requestEvent.emit("request.update.comment", { comment: "PATCH", request: request }); //Deprecated
requestEvent.emit("request.updated", { comment: "PATCH", request: request, patch: update });
return res.json(request);
});
});
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.put('/:requestid/close', async function (req, res) {
winston.debug(req.body);
let request_id = req.params.requestid;
/**
* Check on projectuser existence.
* If req.projectuser is null then the request was made by a chatbot.
*/
let project_user = req.projectuser;
let user_role;
if (project_user) {
user_role = project_user.role;
}
const closed_by = req.user.id;
if (user_role && (user_role !== RoleConstants.OWNER && user_role !== RoleConstants.ADMIN)) {
let request = await Request.findOne({ id_project: req.projectid, request_id: request_id }).catch((err) => {
winston.error("Error finding request: ", err);
return res.status(500).send({ success: false, error: "Error finding request with request_id " + request_id })
})
if (!request) {
winston.verbose("Request with request_id " + request_id)
return res.status(404).send({ success: false, error: "Request not found"})
}
if (!request.participantsAgents.includes(req.user.id)) {
winston.verbose("Request can't be closed by a non participant. Attempt made by " + req.user.id);
return res.status(403).send({ success: false, error: "You must be among the participants to close a conversation."})
}
}
return requestService.closeRequestByRequestId(req.params.requestid, req.projectid, false, true, closed_by, req.body.force).then(function (closedRequest) {
winston.verbose("request closed", closedRequest);
return res.json(closedRequest);
});
});
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.put('/:requestid/reopen', function (req, res) {
winston.debug(req.body);
// reopenRequestByRequestId(request_id, id_project) {
return requestService.reopenRequestByRequestId(req.params.requestid, req.projectid).then(function (reopenRequest) {
winston.verbose("request reopen", reopenRequest);
return res.json(reopenRequest);
});
});
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.put('/:requestid/assignee', function (req, res) {
winston.debug(req.body);
//TODO change assignee
});
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.post('/:requestid/participants',
[
check('member').notEmpty(),
],
function (req, res) {
winston.debug(req.body);
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
//addParticipantByRequestId(request_id, id_project, member)
return requestService.addParticipantByRequestId(req.params.requestid, req.projectid, req.body.member).then(function (updatedRequest) {
winston.verbose("participant added", updatedRequest);
return res.json(updatedRequest);
});
});
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
/*
error: uncaughtException: Cannot set property 'participants' of null
2020-03-08T12:53:35.793660+00:00 app[web.1]: TypeError: Cannot set property 'participants' of null
2020-03-08T12:53:35.793660+00:00 app[web.1]: at /app/services/requestService.js:672:30
2020-03-08T12:53:35.793661+00:00 app[web.1]: at /app/node_modules/mongoose/lib/model.js:4779:16
*/
router.put('/:requestid/participants', function (req, res) {
winston.debug("req.body", req.body);
var participants = [];
req.body.forEach(function (participant, index) {
participants.push(participant);
});
winston.debug("var participants", participants);
//setParticipantsByRequestId(request_id, id_project, participants)
return requestService.setParticipantsByRequestId(req.params.requestid, req.projectid, participants).then(function (updatedRequest) {
winston.debug("participant set", updatedRequest);
return res.json(updatedRequest);
});
});
router.put('/:requestid/replace', async (req, res) => {
let id;
let name;
let slug;
if (req.body.id) {
id = "bot_" + req.body.id;
} else if (req.body.name) {
name = req.body.name;
} else if (req.body.slug) {
slug = req.body.slug;
} else {
return res.status(400).send({ success: false, error: "Missing field 'id' or 'name' in body" })
}
if (name) {
let chatbot = await faq_kb.findOne({ id_project: req.projectid, name: name, trashed: false }).catch((err) => {
winston.error("Error finding bot ", err);
return res.status(500).send({ success: false, error: "An error occurred getting chatbot with name " + name })
})
if (!chatbot) {
return res.status(404).send({ success: false, error: "Chatbot with name '" + name + "' not found" })
}
id = "bot_" + chatbot._id;
winston.verbose("Chatbot found: ", id);
}
if (slug) {
let chatbot = await faq_kb.findOne({ id_project: req.projectid, slug: slug}).catch((err) => {
winston.error("Error finding bot ", err);
return res.status(500).send({ success: false, error: "An error occurred getting chatbot with slug " + slug })
})
if (!chatbot) {
return res.status(404).send({ success: false, error: "Chatbot with slug '" + slug + "' not found" })
}
id = "bot_" + chatbot._id;
winston.verbose("Chatbot found: " + id);
}
let participants = [];
participants.push(id);
winston.verbose("participants to be set: ", participants);
requestService.setParticipantsByRequestId(req.params.requestid, req.projectid, participants).then((updatedRequest) => {
winston.debug("SetParticipant response: ", updatedRequest);
res.status(200).send(updatedRequest);
}).catch((err) => {
winston.error("Error setting participants ", err);
res.status(500).send({ success: false, error: "Error setting participants to request"})
})
})
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.delete('/:requestid/participants/:participantid', function (req, res) {
winston.debug(req.body);
//removeParticipantByRequestId(request_id, id_project, member)
return requestService.removeParticipantByRequestId(req.params.requestid, req.projectid, req.params.participantid).then(function (updatedRequest) {
winston.verbose("participant removed", updatedRequest);
return res.json(updatedRequest);
}).catch((err) => {
//winston.error("(Request) removeParticipantByRequestId error", err)
return res.status(400).send({ success: false, error: "Unable to remove the participant " + req.params.participantid + " from the request " + req.params.requestid})
});
});
// // TODO deprecated
// router.delete('/:requestid/participants', function (req, res) {
// winston.debug(req.body);
// //removeParticipantByRequestId(request_id, id_project, member)
// return requestService.removeParticipantByRequestId(req.params.requestid, req.projectid, req.body.member ).then(function(updatedRequest) {
// winston.info("participant removed", updatedRequest);
// return res.json(updatedRequest);
// });
// });
// router.put('/queue/:requestid/assign', function (req, res) { //fai altro route file
// winston.debug(req.body);
// });
router.put('/:requestid/assign', function (req, res) {
winston.debug(req.body);
let user = req.user;
let pu;
if (req.projectuser) {
pu = req.projectuser._id
}
// leggi la request se già assegnata o già chiusa (1000) esci
//cacheinvalidation
return Request.findOne({ "request_id": req.params.requestid, "id_project": req.projectid })
.exec(function (err, request) {
if (err) {
winston.error('Error patching request.', err);
return res.status(500).send({ success: false, msg: 'Error updating object.' });
}
if (!request) {
return res.status(404).send({ success: false, msg: 'Request not found' });
}
if (request.status === RequestConstants.ASSIGNED || request.status === RequestConstants.SERVED || request.status === RequestConstants.CLOSED) {
winston.info('Request already assigned');
return res.json(request);
}
//route(request_id, departmentid, id_project) {
requestService.route(req.params.requestid, req.body.departmentid, req.projectid, req.body.nobot, req.body.no_populate).then(function (updatedRequest) {
winston.debug("department changed", updatedRequest);
if (updatedRequest.status === RequestConstants.ABANDONED) {
eventService.emit('request.fully_abandoned', updatedRequest, req.projectid, pu, user._id, undefined, user)
}
return res.json(updatedRequest);
}).catch(function (error) {
// TODO: error log removed due to attempt to reduces logs when no department is found
winston.verbose('Error changing the department.', error)
return res.status(500).send({ success: false, msg: 'Error changing the department.' });
})
});
});
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.put('/:requestid/departments', function (req, res) {
winston.debug(req.body);
//route(request_id, departmentid, id_project) {
requestService.route(req.params.requestid, req.body.departmentid, req.projectid, req.body.nobot, req.body.no_populate).then(function (updatedRequest) {
winston.debug("department changed", updatedRequest);
return res.json(updatedRequest);
}).catch(function (error) {
// TODO: error log removed due to attempt to reduces logs when no department is found
winston.verbose('Error changing the department.', error)
return res.status(500).send({ success: false, msg: 'Error changing the department.' });
})
});
router.put('/:requestid/agent', async (req, res) => {
winston.debug(req.body);
//route(request_id, departmentid, id_project) {
var request = await Request.findOne({ "request_id": req.params.requestid, id_project: req.projectid })
.exec();
if (!request) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
var departmentid = request.department;
winston.debug("departmentid before: " + departmentid);
if (!departmentid) {
var defaultDepartment = await departmentService.getDefaultDepartment(req.projectid);
winston.debug("defaultDepartment: ", defaultDepartment);
departmentid = defaultDepartment.id;
}
winston.debug("departmentid after: " + departmentid);
requestService.route(req.params.requestid, departmentid, req.projectid, true, undefined).then(function (updatedRequest) {
winston.debug("department changed", updatedRequest);
return res.json(updatedRequest);
}).catch(function (error) {
// TODO: error log removed due to attempt to reduces logs when no department is found
winston.verbose('Error changing the department.', error)
return res.status(500).send({ success: false, msg: 'Error changing the department.' });
})
});
// router.post('/:requestid/attributes', function (req, res) {
// winston.debug(req.body);
// //return Request.findOneAndUpdate({"request_id":req.params.requestid},{ $push: { attributes: req.body } } , { new: true, upsert: false }, function (err, updatedMessage) {
// return Request.findOneAndUpdate({"request_id":req.params.requestid},{ $set: { attributes: req.body } } , { new: true, upsert: false }, function (err, updatedMessage) {
// if (err) {
// winston.error('Error patching request.', err);
// return res.status(500).send({ success: false, msg: 'Error updating object.' });
// }
// requestEvent.emit("request.update", updatedMessage);
// return res.json(updatedMessage);
// });
// });
// router.put('/:requestid/attributes/:attributeid', function (req, res) {
// winston.debug(req.body);
// return Request.findOneAndUpdate({"request_id":req.params.requestid, "attributes._id": req.params.attributeid},{ $set: { "attributes.$": req.body}} , { new: true, upsert: false }, function (err, updatedMessage) {
// if (err) {
// winston.error('Error patching request.', err);
// return res.status(500).send({ success: false, msg: 'Error updating object.' });
// }
// requestEvent.emit("request.update", updatedMessage);
// return res.json(updatedMessage);
// });
// });
// router.delete('/:requestid/attributes/:attributeid', function (req, res) {
// winston.debug(req.body);
// return Request.findOneAndUpdate({"request_id":req.params.requestid},{ "$pull": { "attributes": { "_id": req.params.attributeid } }} , { new: true, upsert: false }, function (err, updatedMessage) {
// if (err) {
// winston.error('Error patching request.', err);
// return res.status(500).send({ success: false, msg: 'Error updating object.' });
// }
// requestEvent.emit("request.update", updatedMessage);
// return res.json(updatedMessage);
// });
// });
router.patch('/:requestid/attributes', function (req, res) {
var data = req.body;
var id_project = req.projectid;
// TODO use service method
Request.findOne({ "request_id": req.params.requestid, id_project: id_project })
.populate('lead')
.populate('department')
.populate('participatingBots')
.populate('participatingAgents')
.populate({ path: 'requester', populate: { path: 'id_user' } })
.exec(function (err, request) {
if (err) {
return res.status(500).send({ success: false, msg: 'Error getting object.' });
}
if (!request) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
if (!request.attributes) {
winston.debug("empty attributes")
request.attributes = {};
}
winston.debug(" req attributes", request.attributes)
Object.keys(data).forEach(function (key) {
var val = data[key];
winston.debug("data attributes " + key + " " + val)
request.attributes[key] = val;
});
winston.debug(" req attributes", request.attributes)
// https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object
request.markModified('attributes');
//cacheinvalidation
request.save(function (err, savedRequest) {
if (err) {
winston.error("error saving request attributes", err)
return res.status(500).send({ success: false, msg: 'Error getting object.' });
}
winston.verbose(" saved request attributes", savedRequest.toObject())
requestEvent.emit("request.update", savedRequest);
requestEvent.emit("request.update.comment", { comment: "ATTRIBUTES_PATCH", request: savedRequest });//Deprecated
requestEvent.emit("request.updated", { comment: "ATTRIBUTES_PATCH", request: savedRequest, patch: { attributes: data } });
requestEvent.emit("request.attributes.update", savedRequest);
res.json(savedRequest);
});
});
});
router.post('/:requestid/notes', async function (req, res) {
let request_id = req.params.requestid
var note = {};
note.text = req.body.text;
note.createdBy = req.user.id;
let project_user = req.projectuser;
if (project_user.role === RoleConstants.AGENT) {
let request = await Request.findOne({ request_id: request_id }).catch((err) => {
winston.error("Error finding request ", err);
return res.status(500).send({ success: false, error: "Error finding request with id " + request_id });
})
if (!request) {
winston.warn("Request with id " + request_id + " not found.");
return res.status(404).send({ success: false, error: "Request with id " + request_id + " not found."});
}
// Check if the user is a participant
if (!request.participantsAgents.includes(req.user.id)) {
winston.verbose("Trying to add a note from a non participating agent");
return res.status(403).send({ success: false, error: "You are not participating in the conversation"})
}
}
return Request.findOneAndUpdate({ request_id: request_id, id_project: req.projectid }, { $push: { notes: note } }, { new: true, upsert: false })
.populate('lead')
.populate('department')
.populate('participatingBots')
.populate('participatingAgents')
.populate({ path: 'requester', populate: { path: 'id_user' } })
.exec(function (err, updatedRequest) {
if (err) {
winston.error('Error adding request note.', err);
return res.status(500).send({ success: false, msg: 'Error adding request object.' });
}
requestEvent.emit("request.update", updatedRequest);
requestEvent.emit("request.update.comment", { comment: "NOTE_ADD", request: updatedRequest });//Deprecated
requestEvent.emit("request.updated", { comment: "NOTE_ADD", request: updatedRequest, patch: { notes: note } });
return res.json(updatedRequest);
});
});
router.delete('/:requestid/notes/:noteid', async function (req, res) {
let request_id = req.params.requestid
let note_id = req.params.noteid;
let project_user = req.projectuser;
if (project_user.role === RoleConstants.AGENT) {
let request = await Request.findOne({ request_id: request_id }).catch((err) => {
winston.error("Error finding request ", err);
return res.status(500).send({ success: false, error: "Error finding request with id " + request_id });
})
if (!request) {
winston.warn("Request with id " + request_id + " not found.");
return res.status(404).send({ success: false, error: "Request with id " + request_id + " not found."});
}
// Check if the user is a participant
if (!request.participantsAgents.includes(req.user.id)) {
winston.verbose("Trying to delete a note from a non participating agent");
return res.status(403).send({ success: false, error: "You are not participating in the conversation"})
}
}
//cacheinvalidation
return Request.findOneAndUpdate({ request_id: request_id, id_project: req.projectid }, { $pull: { notes: { "_id": note_id } } }, { new: true, upsert: false })
.populate('lead')
.populate('department')
.populate('participatingBots')
.populate('participatingAgents')
.populate({ path: 'requester', populate: { path: 'id_user' } })
.exec(function (err, updatedRequest) {
if (err) {
winston.error('Error adding request note.', err);
return res.status(500).send({ success: false, msg: 'Error adding request object.' });
}
requestEvent.emit("request.update", updatedRequest);
requestEvent.emit("request.update.comment", { comment: "NOTE_DELETE", request: updatedRequest });//Deprecated
// requestEvent.emit("request.updated", {comment:"NOTE_DELETE",request:updatedRequest, patch: {notes:req.params.noteid}});
return res.json(updatedRequest);
});
});
//TODO add cc
router.post('/:requestid/email/send',
async (req, res) => {
let text = req.body.text;
winston.debug("text: " + text);
let request_id = req.params.requestid;
winston.debug("request_id: " + request_id);
let subject = req.body.subject;
winston.debug("subject: " + subject);
winston.debug("req.project", req.project);
let replyto = req.body.replyto;
winston.debug("replyto: " + replyto);
let q = Request.findOne({ request_id: request_id, id_project: req.projectid })
// .select("+snapshot.agents")
.populate('lead')
q.exec(function (err, request) {
if (err) {
winston.error("error getting request by id ", err);
return res.status(500).send({ success: false, msg: 'Error getting object.' });
}
if (!request) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
winston.debug("Sending an email with text : " + text + " to request_id " + request_id);
if (!request.lead.email) {
res.json({ "no queued": true });
}
let newto = request.lead.email
winston.verbose("Sending an email newto " + newto);
//sendEmailDirect(to, text, project, request_id, subject, tokenQueryString, sourcePage, payload)
emailService.sendEmailDirect(newto, text, req.project, request_id, subject, undefined, undefined, undefined, replyto);
res.json({ "queued": true });
});
});
router.post('/:requestid/followers',
[
check('member').notEmpty(),
],
function (req, res) {
winston.info("followers add", req.body);
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
//addParticipantByRequestId(request_id, id_project, member)
return requestService.addFollowerByRequestId(req.params.requestid, req.projectid, req.body.member).then(function (updatedRequest) {
winston.verbose("participant added", updatedRequest);
return res.json(updatedRequest);
});
});
router.put('/:requestid/followers', function (req, res) {
winston.debug("req.body", req.body);
var followers = [];
req.body.forEach(function (follower, index) {
followers.push(follower);
});
winston.debug("var followers", followers);
// setFollowersByRequestId(request_id, id_project, newfollowers)
return requestService.setFollowersByRequestId(req.params.requestid, req.projectid, followers).then(function (updatedRequest) {
winston.debug("followers set", updatedRequest);
return res.json(updatedRequest);
});
});
router.put('/:requestid/tag', async (req, res) => {
let id_project = req.projectid;
let request_id = req.params.requestid;
let tags_list = req.body;
winston.debug("(Request) /tag tags_list: ", tags_list)
if (tags_list.length == 0) {
winston.warn("(Request) /tag no tag specified")
return res.status(400).send({ success: false, message: "No tag specified" })
}
let request = await Request.findOne({ id_project: id_project, request_id: request_id }).catch((err) => {
winston.error("(Request) /tag error getting request ", err);
return res.status(500).send({ success: false, error: "Error getting request with request id " + request_id});
})
if (!request) {
winston.warn("(Request) /tag request not found with request_id " + request_id);
return res.status(404).send({ success: false, error: "Request not found with request id " + request_id});
}
let current_tags = request.tags;
let adding_tags = [];
tags_list.forEach(t => {
// Check if tag already exists in the conversation. If true, skip the adding.
if(!current_tags.some(tag => tag.tag === t.tag)) {
current_tags.push(t);
adding_tags.push(t);
}
})
let update = {
tags: current_tags
}
Request.findOneAndUpdate({ id_project: id_project, request_id: request_id }, update, { new: true }).then( async (updatedRequest) => {
if (!updatedRequest) {
winston.warn("(Request) /tag The request was deleted while adding tags for request " + request_id);
return res.status(404).send({ success: false, error: "The request was deleted while adding tags for request " + request_id })
}
winston.debug("(Request) /tag Request updated successfully ", updatedRequest);
const populatedRequest =
await updatedRequest
.populate('lead')
.populate('department')
.populate('participatingBots')
.populate('participatingAgents')
.populate({ path: 'requester', populate: { path: 'id_user' } })
.execPopulate();
requestEvent.emit("request.update", populatedRequest)
res.status(200).send(updatedRequest)
if (process.env.NODE_ENV !== 'test') {
scheduleTags(id_project, adding_tags);
}
}).catch((err) => {
winston.error("(Request) /tag error finding and update request ", err);
return res.status(500).send({ success: false, error: "Error updating request with id " + request_id })
})
})
router.delete('/:requestid/followers/:followerid', function (req, res) {
winston.debug(req.body);
//removeFollowerByRequestId(request_id, id_project, member)
return requestService.removeFollowerByRequestId(req.params.requestid, req.projectid, req.params.followerid).then(function (updatedRequest) {
winston.verbose("follower removed", updatedRequest);
return res.json(updatedRequest);
});
});
router.delete('/:requestid/tag/:tag_id', async (req, res) => {
let id_project = req.projectid;
let request_id = req.params.requestid;
let tag_id = req.params.tag_id;
Request.findOneAndUpdate({ id_project: id_project, request_id: request_id }, { $pull: { tags: { _id: tag_id } } }, { new: true }).then( async (updatedRequest) => {
if (!updatedRequest) {
winston.warn("(Request) /removetag request not found with id: " + request_id)
return res.status(404).send({ success: false, error: "Request not found with id " + request_id})
}
winston.debug("(Request) /removetag updatedRequest: ", updatedRequest)
const populatedRequest =
await updatedRequest
.populate('lead')
.populate('department')
.populate('participatingBots')
.populate('participatingAgents')
.populate({ path: 'requester', populate: { path: 'id_user' } })
.execPopulate();
requestEvent.emit("request.update", populatedRequest)
res.status(200).send(updatedRequest);
}).catch((err) => {
winston.error("(Request) /removetag error updating request: ", err)
res.status(500).send({ success: false, error: err })
})
})
// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created
router.delete('/:requestid', function (req, res) {
var projectuser = req.projectuser;
if (projectuser.role != "owner") {
return res.status(403).send({ success: false, msg: 'Unauthorized.' });
}
Message.deleteMany({ recipient: req.params.requestid }, function (err) {
if (err) {
return res.status(500).send({ success: false, msg: 'Error deleting messages.' });
}
winston.verbose('Messages deleted for the recipient: ' + req.params.requestid);
});
Request.findOneAndDelete({ request_id: req.params.requestid }, function (err, request) {
if (err) {
winston.error('--- > ERROR ', err);
return res.status(500).send({ success: false, msg: 'Error deleting object.' });
}
if (!request) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
winston.verbose('Request deleted with request_id: ' + req.params.requestid);
requestEvent.emit('request.delete', request);
res.json(request);
});
// Request.remove({ request_id: req.params.requestid }, function (err, request) {
// if (err) {
// winston.error('--- > ERROR ', err);
// return res.status(500).send({ success: false, msg: 'Error deleting object.' });
// }
// if (!request) {
// return res.status(404).send({ success: false, msg: 'Object not found.' });
// }
// winston.verbose('Request deleted with request_id: ' + req.params.requestid);
// requestEvent.emit('request.delete', request);
// res.json(request);
// });
});
router.delete('/id/:id', function (req, res) {
var projectuser = req.projectuser;
if (projectuser.role != "owner") {
return res.status(403).send({ success: false, msg: 'Unauthorized.' });
}
Request.remove({ _id: req.params.id }, function (err, request) {
if (err) {
winston.error('--- > ERROR ', err);
return res.status(500).send({ success: false, msg: 'Error deleting object.' });
}
if (!request) {
return res.status(404).send({ success: false, msg: 'Object not found.' });
}
winston.verbose('Request deleted with id: ' + req.params.id);
requestEvent.emit('request.delete', request);
res.json(request);
});
});
router.get('/', function (req, res, next) {
const startExecTime = new Date();
winston.debug("req projectid", req.projectid);
winston.debug("req.query.sort", req.query.sort);
winston.debug('REQUEST ROUTE - QUERY ', req.query)
const DEFAULT_LIMIT = 40;
var page = 0;
var limit = DEFAULT_LIMIT; // Number of request per page
var page = 0;
var skip = 0;
let statusArray = [];
var projectuser = req.projectuser;
if (req.query.limit) {
limit = parseInt(req.query.limit);
}
if (limit > 100) {
limit = DEFAULT_LIMIT;
}
if (req.query.page) {
page = req.query.page;
}
skip = page * limit;
// Default query
var query = { "id_project": req.projectid, "status": { $lt: 1000, $nin: [50, 150] }, preflight: false };
if (req.user instanceof Subscription) {
// All request
} else if (projectuser && (projectuser.role == "owner" || projectuser.role == "admin")) {
// All request
// Per uni mostrare solo quelle proprie quindi solo participants
if (req.query.mine) {
query["$or"] = [{ "snapshot.agents.id_user": req.user.id }, { "participants": req.user.id }];
}
} else {
query["$or"] = [{ "snapshot.agents.id_user": req.user.id }, { "participants": req.user.id }];
}
if (req.query.dept_id) {
query.department = req.query.dept_id;
}
if (req.query.requester_email) {
query["snapshot.lead.email"] = req.query.requester_email;
}
if (req.query.full_text) {
query.$text = { "$search": req.query.full_text };
}
var history_search = false;
// Multiple status management
if (req.query.status) {
if (req.query.status === 'all') {
delete query.status;
} else {
let statusArray = req.query.status.split(',').map(Number);
statusArray = statusArray.map(status => { return isNaN(status) ? null : status }).filter(status => status !== null)
if (statusArray.length > 0) {
query.status = {
$in: statusArray
}
} else {
delete query.status;
}
}
if (statusArray.length > 0) {
query.status = {
$in: statusArray
}
}
}
if (req.query.lead) {
query.lead = req.query.lead;
}
// USERS & BOTS
if (req.query.participant) {
query.participants = req.query.participant;
}
if (req.query.hasbot != undefined) {
query.hasBot = req.query.hasbot;
}
if (req.query.tags) {
query["tags.tag"] = req.query.tags;
}
if (req.query.location) {
query.location = req.query.location;
}
if (req.query.ticket_id) {
query.ticket_id = req.query.ticket_id;
}
if (req.query.preflight && (req.query.preflight === 'true' || req.query.preflight === true)) {
//query.preflight = req.query.preflight;
delete query.preflight;
}
// if (req.query.request_id) {
// console.log('req.query.request_id', req.query.request_id);
// query.request_id = req.query.request_id;
// }
/**
**! *** DATE RANGE USECASE 1 ***
* in the tiledesk dashboard's HISTORY PAGE
* WHEN THE TRIAL IS EXIPIRED OR THE SUBSCIPTION IS NOT ACTIVE
* THE SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS ARE DISABLED AND
* ARE DISPLAYED ONLY THE REQUESTS OF THE LAST 14 DAYS
*/
//fixato. secondo me qui manca un parentesi tonda per gli or
if (history_search === true && req.project && req.project.profile && ((req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false))) {
var startdate = moment().subtract(14, "days").format("YYYY-MM-DD");
var enddate = moment().format("YYYY-MM-DD");
winston.debug('»»» REQUEST ROUTE - startdate ', startdate);
winston.debug('»»» REQUEST ROUTE - enddate ', enddate);
var enddatePlusOneDay = moment(new Date()).add(1, 'days').toDate()
winston.debug('»»» REQUEST ROUTE - enddate + 1 days: ', enddatePlusOneDay);
query.createdAt = { $gte: new Date(Date.parse(startdate)).toISOString(), $lte: new Date(enddatePlusOneDay).toISOString() }
winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);
}
/**
**! *** DATE RANGE USECASE 2 ***
* in the tiledesk dashboard's HISTORY PAGE
* WHEN THE USER SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS
*/
if (req.query.start_date && req.query.end_date) {
winston.debug('REQUEST ROUTE - REQ QUERY start_date ', req.query.start_date);
winston.debug('REQUEST ROUTE - REQ QUERY end_date ', req.query.end_date);
/**
* USING TIMESTAMP in MS */
// var formattedStartDate = new Date(+req.query.start_date);
// var formattedEndDate = new Date(+req.query.end_date);
// query.createdAt = { $gte: formattedStartDate, $lte: formattedEndDate }
/**
* USING MOMENT */
var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');
var endDate = moment(req.query.end_date, 'DD/MM/YYYY').format('YYYY-MM-DD');
winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED START DATE ', startDate);
winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE ', endDate);
// ADD ONE DAY TO THE END DAY
var date = new Date(endDate);
var newdate = new Date(date);
var endDate_plusOneDay = newdate.setDate(newdate.getDate() + 1);
winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE + 1 DAY ', endDate_plusOneDay);
query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString(), $lte: new Date(endDate_plusOneDay).toISOString() }
winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);
} else if (req.query.start_date && !req.query.end_date) {
winston.debug('REQUEST ROUTE - REQ QUERY END DATE IS EMPTY (so search only for start date)');
var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');
var range = { $gte: new Date(Date.parse(startDate)).toISOString() };
if (req.query.filterRangeField) {
query[req.query.filterRangeField] = range;
} else {
query.createdAt = range;
}
winston.debug('REQUEST ROUTE - QUERY CREATED AT (only for start date)', query.createdAt);
}
if (req.query.snap_department_routing) {
query["snapshot.department.routing"] = req.query.snap_department_routing;
}
if (req.query.snap_department_default) {
query["snapshot.department.default"] = req.query.snap_department_default;
}
if (req.query.snap_department_id_bot) {
query["snapshot.department.id_bot"] = req.query.snap_department_id_bot;
}
if (req.query.snap_department_id_bot_exists) {
query["snapshot.department.id_bot"] = { "$exists": req.query.snap_department_id_bot_exists }
}
if (req.query.snap_lead_lead_id) {
query["snapshot.lead.lead_id"] = req.query.snap_lead_lead_id;
}
if (req.query.snap_lead_email) {
query["snapshot.lead.email"] = req.query.snap_lead_email;
}
if (req.query.smartAssignment) {
query.smartAssignment = req.query.smartAssignment;
}
if (req.query.channel) {
if (req.query.channel === "offline") {
query["channel.name"] = { "$in": ["email", "form"] }
} else if (req.query.channel === "online") {
query["channel.name"] = { "$nin": ["email", "form"] }
} else {
query["channel.name"] = req.query.channel
}
}
if (req.query.priority) {
query.priority = req.query.priority;
}
var direction = -1; //-1 descending , 1 ascending
if (req.query.direction) {
direction = req.query.direction;
}
var sortField = "createdAt";
if (req.query.sort) {
sortField = req.query.sort;
}
var sortQuery = {};
sortQuery[sortField] = direction;
// VOICE FILTERS - Start
if (req.query.caller) {
query["attributes.caller_phone"] = req.query.caller;
}
if (req.query.called) {
query["attributes.called_phone"] = req.query.called;
}
if (req.query.call_id) {
query["attributes.call_id"] = req.query.call_id;
}
// VOICE FILTERS - End
if (req.query.duration && req.query.duration_op) {
let duration = Number(req.query.duration) * 60 * 1000;
if (req.query.duration_op === 'gt') {
query.duration = { $gte: duration }
} else if (req.query.duration_op === 'lt') {
query.duration = { $lte: duration }
} else {
winston.verbose("Duration operator can be 'gt' or 'lt'. Skip duration_op " + req.query.duration_op)
}
}
if (req.query.abandonded && (req.query.abandoned === true || req.query.abandoned === 'true')) {
query["attributes.fully_abandoned"] = true
}
if (req.query.draft && (req.query.draft === 'false' || req.query.draft === false)) {
query.draft = { $in: [false, null] }
}
var projection = undefined;
if (req.query.full_text) {
if (req.query.no_textscore != "true" && req.query.no_textscore != true) {
winston.verbose('fulltext projection on');
projection = { score: { $meta: "textScore" } };
}
}
winston.verbose('REQUEST ROUTE - REQUEST FIND QUERY ', query);
var q1 = Request.find(query, projection).
skip(skip).limit(limit);
if (req.query.no_populate != "true" && req.query.no_populate != true) {
q1.populate('department').
populate('participatingBots'). //nico già nn gli usa
populate('participatingAgents'). //nico già nn gli usa
populate('lead').
populate({ path: 'requester', populate: { path: 'id_user' } }); //toglilo perche nico lo prende già da snapshot
}
if (req.query.full_text) {
if (req.query.no_textscore != "true" && req.query.no_textscore != true) {
q1.sort({ score: { $meta: "textScore" } }) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score
}
} else {
q1.sort(sortQuery);
}
q1.exec();
// TODO if ?onlycount=true do not perform find query but only
// set q1 to undefined; to skip query
var q2 = Request.countDocuments(query).exec();
if (req.query.no_count && req.query.no_count == "true") {
winston.info('REQUEST ROUTE - no_count ');
q2 = 0;
}
var promises = [ q1, q2 ];
Promise.all(promises).then(function (results) {
var objectToReturn = {
perPage: limit,
count: results[1],
requests: results[0]
};
winston.debug('REQUEST ROUTE - objectToReturn ');
winston.debug('REQUEST ROUTE - objectToReturn ', objectToReturn);
const endExecTime = new Date();
winston.verbose('REQUEST ROUTE - exec time: ' + (endExecTime - startExecTime));
return res.json(objectToReturn);
}).catch(function (err) {
winston.error('REQUEST ROUTE - REQUEST FIND ERR ', err);
return res.status(500).send({ success: false, msg: 'Error getting requests.', err: err });
});
});
// router.get('/', function (req, res, next) {
// const startExecTime = new Date();
// winston.debug("req projectid", req.projectid);
// winston.debug("req.query.sort", req.query.sort);
// winston.debug('REQUEST ROUTE - QUERY ', req.query)
// const DEFAULT_LIMIT = 40;