@tiledesk/tiledesk-server 2.18.5 → 2.19.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 +24 -0
- package/app.js +2 -1
- package/event/kbEvent.js +12 -0
- package/event/webhookEvent.js +9 -0
- package/jobs.js +12 -1
- package/lib/analyticsClient.js +60 -0
- package/package.json +2 -2
- package/pubmodules/analytics-publisher/index.js +437 -0
- package/pubmodules/pubModulesManager.js +14 -0
- package/routes/answered.js +73 -0
- package/routes/kb.js +20 -1
- package/routes/project_user.js +51 -20
- package/routes/public-request.js +305 -239
- package/routes/request.js +11 -1
- package/routes/unanswered.js +71 -0
- package/routes/urlPreview.js +57 -0
- package/routes/webhook.js +2 -0
- package/services/Scheduler.js +13 -0
- package/services/aiManager.js +30 -0
- package/services/urlPreviewService.js +50 -0
- package/utils/jobs-worker-queue-manager/JobManagerV2.js +23 -12
- package/utils/jobs-worker-queue-manager/queueManagerClassV2.js +270 -270
- package/utils/transcriptTimezone.js +101 -0
- package/views/messages-layout.jade +130 -0
- package/views/messages.jade +23 -22
- package/views/messages_old.jade +11 -4
- package/.env.sample +0 -141
package/routes/answered.js
CHANGED
|
@@ -2,6 +2,7 @@ const express = require('express');
|
|
|
2
2
|
const router = express.Router();
|
|
3
3
|
const { Namespace, AnsweredQuestion } = require('../models/kb_setting');
|
|
4
4
|
const winston = require('../config/winston');
|
|
5
|
+
var fastCsv = require('fast-csv');
|
|
5
6
|
|
|
6
7
|
// Add a new unanswerd question
|
|
7
8
|
router.post('/', async (req, res) => {
|
|
@@ -210,6 +211,78 @@ router.get('/count/:namespace', async (req, res) => {
|
|
|
210
211
|
}
|
|
211
212
|
})
|
|
212
213
|
|
|
214
|
+
router.get('/:namespace/export', async (req, res) => {
|
|
215
|
+
try {
|
|
216
|
+
const { namespace } = req.params;
|
|
217
|
+
const id_project = req.projectid;
|
|
218
|
+
const mode = String(req.query.mode || 'csv').toLowerCase();
|
|
219
|
+
|
|
220
|
+
if (mode !== 'csv' && mode !== 'json') {
|
|
221
|
+
return res.status(400).json({
|
|
222
|
+
success: false,
|
|
223
|
+
error: 'Invalid format. Use mode=json or mode=csv'
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const isValidNamespace = await validateNamespace(id_project, namespace);
|
|
228
|
+
if (!isValidNamespace) {
|
|
229
|
+
return res.status(403).json({
|
|
230
|
+
success: false,
|
|
231
|
+
error: "Not allowed. The namespace does not belong to the current project."
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const questions = await AnsweredQuestion.find({ id_project, namespace })
|
|
236
|
+
.sort({ createdAt: -1 })
|
|
237
|
+
.lean();
|
|
238
|
+
|
|
239
|
+
const safeFilename = String(namespace).replace(/[^\w.-]+/g, '_') || 'export';
|
|
240
|
+
|
|
241
|
+
if (mode === 'json') {
|
|
242
|
+
const questionsJson = questions.map((q) => {
|
|
243
|
+
const { __v, updatedAt, ...doc } = q;
|
|
244
|
+
return doc;
|
|
245
|
+
});
|
|
246
|
+
const payload = {
|
|
247
|
+
namespace,
|
|
248
|
+
exportedAt: new Date().toISOString(),
|
|
249
|
+
count: questionsJson.length,
|
|
250
|
+
questions: questionsJson
|
|
251
|
+
};
|
|
252
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
253
|
+
res.setHeader('Content-Disposition', `attachment; filename="answered-${safeFilename}.json"`);
|
|
254
|
+
return res.send(JSON.stringify(payload, null, 2));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
258
|
+
res.setHeader('Content-Disposition', `attachment; filename="answered-${safeFilename}.csv"`);
|
|
259
|
+
|
|
260
|
+
const csvStream = fastCsv.format({ headers: true });
|
|
261
|
+
csvStream.pipe(res);
|
|
262
|
+
|
|
263
|
+
for (const q of questions) {
|
|
264
|
+
csvStream.write({
|
|
265
|
+
id: String(q._id),
|
|
266
|
+
namespace: q.namespace,
|
|
267
|
+
question: q.question,
|
|
268
|
+
answer: q.answer,
|
|
269
|
+
tokens: q.tokens != null ? q.tokens : '',
|
|
270
|
+
request_id: q.request_id || '',
|
|
271
|
+
createdAt: q.createdAt ? new Date(q.createdAt).toISOString() : ''
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
csvStream.end();
|
|
275
|
+
} catch (error) {
|
|
276
|
+
winston.error('Error exporting answered questions:', error);
|
|
277
|
+
if (!res.headersSent) {
|
|
278
|
+
res.status(500).json({
|
|
279
|
+
success: false,
|
|
280
|
+
error: "Error exporting answered questions"
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
213
286
|
// Helper function to validate namespace
|
|
214
287
|
async function validateNamespace(id_project, namespace_id) {
|
|
215
288
|
try {
|
package/routes/kb.js
CHANGED
|
@@ -21,6 +21,7 @@ const Sitemapper = require('sitemapper');
|
|
|
21
21
|
const aiService = require('../services/aiService');
|
|
22
22
|
const aiManager = require('../services/aiManager');
|
|
23
23
|
const integrationService = require('../services/integrationService');
|
|
24
|
+
const kbEvent = require('../event/kbEvent');
|
|
24
25
|
|
|
25
26
|
const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;
|
|
26
27
|
const JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tiledesk-trainer';
|
|
@@ -41,7 +42,6 @@ if (MAX_UPLOAD_FILE_SIZE) {
|
|
|
41
42
|
}
|
|
42
43
|
var upload = multer({limits: uploadlimits});
|
|
43
44
|
|
|
44
|
-
|
|
45
45
|
let jobManager = new JobManager(AMQP_MANAGER_URL, {
|
|
46
46
|
debug: false,
|
|
47
47
|
topic: JOB_TOPIC_EXCHANGE,
|
|
@@ -835,6 +835,7 @@ router.delete('/deleteall', async (req, res) => {
|
|
|
835
835
|
|
|
836
836
|
let project_id = req.projectid;
|
|
837
837
|
let data = req.body;
|
|
838
|
+
let namespace_id = data.namespace;
|
|
838
839
|
winston.debug('/delete all data: ', data);
|
|
839
840
|
|
|
840
841
|
let namespace;
|
|
@@ -851,6 +852,7 @@ router.delete('/deleteall', async (req, res) => {
|
|
|
851
852
|
|
|
852
853
|
aiService.deleteNamespace(data).then((resp) => {
|
|
853
854
|
winston.debug("delete namespace resp: ", resp.data);
|
|
855
|
+
kbEvent.emit('kb.contents.delete', { req, namespace_id, project_id });
|
|
854
856
|
res.status(200).send(resp.data);
|
|
855
857
|
}).catch((err) => {
|
|
856
858
|
winston.error("delete namespace err: ", err);
|
|
@@ -1133,6 +1135,8 @@ router.post('/namespace', async (req, res) => {
|
|
|
1133
1135
|
return res.status(500).send({ success: false, error: err });
|
|
1134
1136
|
}
|
|
1135
1137
|
|
|
1138
|
+
kbEvent.emit('kb.namespace.create', { req, savedNamespace, body, namespace_id, project_id });
|
|
1139
|
+
|
|
1136
1140
|
let namespaceObj = savedNamespace.toObject();
|
|
1137
1141
|
delete namespaceObj._id;
|
|
1138
1142
|
delete namespaceObj.__v;
|
|
@@ -1349,6 +1353,8 @@ router.put('/namespace/:id', async (req, res) => {
|
|
|
1349
1353
|
return res.status(500).send({ success: false, error: err });
|
|
1350
1354
|
}
|
|
1351
1355
|
|
|
1356
|
+
kbEvent.emit('kb.namespace.update', { updatedNamespace, namespace_id });
|
|
1357
|
+
|
|
1352
1358
|
let namespaceObj = updatedNamespace.toObject();
|
|
1353
1359
|
delete namespaceObj._id;
|
|
1354
1360
|
delete namespaceObj.__v;
|
|
@@ -1388,6 +1394,8 @@ router.delete('/namespace/:id', async (req, res) => {
|
|
|
1388
1394
|
})
|
|
1389
1395
|
winston.debug("delete all contents response: ", deleteResponse);
|
|
1390
1396
|
|
|
1397
|
+
kbEvent.emit('kb.contents.delete', { req, namespace_id, project_id, deletedCount: deleteResponse?.deletedCount });
|
|
1398
|
+
|
|
1391
1399
|
return res.status(200).send({ success: true, message: "All contents deleted successfully" })
|
|
1392
1400
|
|
|
1393
1401
|
}).catch((err) => {
|
|
@@ -2195,6 +2203,17 @@ router.delete('/:kb_id', async (req, res) => {
|
|
|
2195
2203
|
return res.status(errorCode).send({ success: false, error: err.error });
|
|
2196
2204
|
}
|
|
2197
2205
|
|
|
2206
|
+
if (kb.type === 'sitemap') {
|
|
2207
|
+
try {
|
|
2208
|
+
await aiManager.deleteSitemap(kb, namespace);
|
|
2209
|
+
console.log("Scheduled jobs for deleting sitemap");
|
|
2210
|
+
//return res.status(200).send({ success: true, message: "Scheduled jobs for deleting sitemap" });
|
|
2211
|
+
} catch (err) {
|
|
2212
|
+
winston.error("Error deleting sitemap: ", err);
|
|
2213
|
+
return res.status(500).send({ success: false, error: err });
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2198
2217
|
let data = {
|
|
2199
2218
|
id: kb_id,
|
|
2200
2219
|
namespace: namespace_id
|
package/routes/project_user.js
CHANGED
|
@@ -17,6 +17,7 @@ require('../middleware/passport')(passport);
|
|
|
17
17
|
var validtoken = require('../middleware/valid-token')
|
|
18
18
|
var roleChecker = require('../middleware/has-role');
|
|
19
19
|
const puEvent = require('../event/projectUserEvent');
|
|
20
|
+
const { track } = require('../lib/analyticsClient');
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
router.post('/invite', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {
|
|
@@ -110,7 +111,7 @@ router.post('/invite', [passport.authenticate(['basic', 'jwt'], { session: false
|
|
|
110
111
|
|
|
111
112
|
emailService.sendYouHaveBeenInvited(email, req.user.firstname, req.user.lastname, req.project.name, id_project, user.firstname, user.lastname, req.body.role)
|
|
112
113
|
|
|
113
|
-
updatedPuser.populate({path:'id_user', select:{'firstname':1, 'lastname':1}},function (err, updatedPuserPopulated){
|
|
114
|
+
updatedPuser.populate({path:'id_user', select:{'firstname':1, 'lastname':1, 'email': 1}},function (err, updatedPuserPopulated){
|
|
114
115
|
var pu = updatedPuserPopulated.toJSON();
|
|
115
116
|
pu.isBusy = ProjectUserUtil.isBusy(savedProject_userPopulated, req.project.settings && req.project.settings.max_agent_assigned_chat);
|
|
116
117
|
var eventData = {req:req, updatedPuserPopulated: pu};
|
|
@@ -141,7 +142,7 @@ router.post('/invite', [passport.authenticate(['basic', 'jwt'], { session: false
|
|
|
141
142
|
|
|
142
143
|
emailService.sendYouHaveBeenInvited(email, req.user.firstname, req.user.lastname, req.project.name, id_project, user.firstname, user.lastname, req.body.role)
|
|
143
144
|
|
|
144
|
-
savedProject_user.populate({path:'id_user', select:{'firstname':1, 'lastname':1}},function (err, savedProject_userPopulated){
|
|
145
|
+
savedProject_user.populate({path:'id_user', select:{'firstname':1, 'lastname':1, 'email': 1}},function (err, savedProject_userPopulated){
|
|
145
146
|
var pu = savedProject_userPopulated.toJSON();
|
|
146
147
|
pu.isBusy = ProjectUserUtil.isBusy(savedProject_userPopulated, req.project.settings && req.project.settings.max_agent_assigned_chat);
|
|
147
148
|
var eventData = {req:req, savedProject_userPopulated: pu};
|
|
@@ -218,6 +219,8 @@ router.put('/', [passport.authenticate(['basic', 'jwt'], { session: false }), va
|
|
|
218
219
|
update.tags = req.body.tags;
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
const previousUserAvailable = req.projectuser.user_available;
|
|
223
|
+
|
|
221
224
|
Project_user.findByIdAndUpdate(req.projectuser.id, update, { new: true, upsert: true }, function (err, updatedProject_user) {
|
|
222
225
|
if (err) {
|
|
223
226
|
winston.error("Error gettting project_user for update", err);
|
|
@@ -227,7 +230,7 @@ router.put('/', [passport.authenticate(['basic', 'jwt'], { session: false }), va
|
|
|
227
230
|
updatedProject_user.populate({ path:'id_user', select: { 'firstname': 1, 'lastname': 1 }}, function (err, updatedProject_userPopulated) {
|
|
228
231
|
var pu = updatedProject_userPopulated.toJSON();
|
|
229
232
|
pu.isBusy = ProjectUserUtil.isBusy(updatedProject_userPopulated, req.project.settings && req.project.settings.max_agent_assigned_chat);
|
|
230
|
-
authEvent.emit('project_user.update', {updatedProject_userPopulated:pu, req: req});
|
|
233
|
+
authEvent.emit('project_user.update', {updatedProject_userPopulated:pu, req: req, previousUserAvailable: previousUserAvailable});
|
|
231
234
|
});
|
|
232
235
|
|
|
233
236
|
res.json(updatedProject_user);
|
|
@@ -283,24 +286,34 @@ router.put('/:project_userid', [passport.authenticate(['basic', 'jwt'], { sessio
|
|
|
283
286
|
|
|
284
287
|
winston.debug("project_userid update", update);
|
|
285
288
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
289
|
+
function _doUpdateProjectUser(previousUserAvailable) {
|
|
290
|
+
Project_user.findByIdAndUpdate(req.params.project_userid, update, { new: true, upsert: true }, function (err, updatedProject_user) {
|
|
291
|
+
if (err) {
|
|
292
|
+
winston.error("Error gettting project_user for update", err);
|
|
293
|
+
return res.status(500).send({ success: false, msg: 'Error updating object.' });
|
|
294
|
+
}
|
|
295
|
+
updatedProject_user.populate({path:'id_user', select:{'firstname':1, 'lastname':1}},function (err, updatedProject_userPopulated){
|
|
296
|
+
if (err) {
|
|
297
|
+
winston.error("Error gettting updatedProject_userPopulated for update", err);
|
|
298
|
+
}
|
|
299
|
+
var pu = updatedProject_userPopulated.toJSON();
|
|
300
|
+
pu.isBusy = ProjectUserUtil.isBusy(updatedProject_user, req.project.settings && req.project.settings.max_agent_assigned_chat);
|
|
301
|
+
|
|
302
|
+
authEvent.emit('project_user.update', {updatedProject_userPopulated:pu, req: req, previousUserAvailable: previousUserAvailable});
|
|
303
|
+
});
|
|
304
|
+
|
|
301
305
|
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
res.json(updatedProject_user);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (update.user_available !== undefined && process.env.ANALYTICS_INGEST_URL) {
|
|
311
|
+
Project_user.findById(req.params.project_userid, 'user_available').lean().exec(function(err, currentPu) {
|
|
312
|
+
_doUpdateProjectUser(currentPu ? currentPu.user_available : null);
|
|
313
|
+
});
|
|
314
|
+
} else {
|
|
315
|
+
_doUpdateProjectUser(null);
|
|
316
|
+
}
|
|
304
317
|
});
|
|
305
318
|
|
|
306
319
|
// TODO fai servizio di patch degli attributi come request
|
|
@@ -372,6 +385,24 @@ router.delete('/:project_userid', [passport.authenticate(['basic', 'jwt'], { ses
|
|
|
372
385
|
|
|
373
386
|
winston.debug("Disabled project_user", project_user);
|
|
374
387
|
|
|
388
|
+
// Populate id_user to obtain user_email for the analytics event.
|
|
389
|
+
// user_email is required as a valid email string by the contract
|
|
390
|
+
// (project-user-deactivated.ts) — skip the event when unavailable.
|
|
391
|
+
project_user.populate({ path: 'id_user', select: { 'email': 1 } }, function(populateErr, populatedPu) {
|
|
392
|
+
var puToUse = populatedPu || project_user;
|
|
393
|
+
var userObj = puToUse.id_user;
|
|
394
|
+
var email = (userObj && userObj.email) || null;
|
|
395
|
+
|
|
396
|
+
if (email) {
|
|
397
|
+
track('project_user.deactivated', puToUse.id_project, {
|
|
398
|
+
id_user: puToUse.uuid_user || puToUse._id.toString(),
|
|
399
|
+
user_email: email,
|
|
400
|
+
deactivated_by: (req.user && req.user.id) || null,
|
|
401
|
+
reason: null,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
375
406
|
// Event 'project_user.delete' not working - Check it and improve it to manage disable project user
|
|
376
407
|
return res.status(200).send(project_user);
|
|
377
408
|
});
|