@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.
@@ -1,9 +1,11 @@
1
1
  var express = require('express');
2
2
  var router = express.Router();
3
+ var csvExpress = require('csv-express');
3
4
  var Message = require("../models/message");
4
5
  var Request = require("../models/request");
5
6
  var User = require("../models/user");
6
7
  var winston = require('../config/winston');
8
+ var transcriptTz = require('../utils/transcriptTimezone');
7
9
 
8
10
  var fonts = {
9
11
  Roboto: {
@@ -18,339 +20,403 @@ var PdfPrinter = require('pdfmake');
18
20
  var printer = new PdfPrinter(fonts);
19
21
  // var fs = require('fs');
20
22
 
23
+ router.get('/:requestid/test/error', async (req, res) => {
24
+
25
+ return res.status(500).render('error', {
26
+ title: 'Tiledesk',
27
+ error: "An error occurred while getting the request"
28
+ });
21
29
 
30
+ });
22
31
 
32
+ router.get('/:requestid/messages', function(req, res) {
23
33
 
34
+ winston.debug(req.params);
35
+ winston.debug("here");
36
+ return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
37
+ if (err) {
38
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
39
+ }
24
40
 
41
+ if(!messages){
42
+ return res.status(404).send({success: false, msg: 'Object not found.'});
43
+ }
25
44
 
26
- router.get('/:requestid/messages', function(req, res) {
27
-
28
- winston.debug(req.params);
29
- winston.debug("here");
30
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
31
- if (err) {
32
- return res.status(500).send({success: false, msg: 'Error getting object.'});
33
- }
34
-
35
- if(!messages){
36
- return res.status(404).send({success: false, msg: 'Object not found.'});
37
- }
45
+ return res.json(messages);
46
+ });
38
47
 
39
- return res.json(messages);
40
- });
48
+ });
41
49
 
42
- });
43
50
 
51
+ router.get('/:requestid/messages.html', async (req, res) => {
44
52
 
45
- router.get('/:requestid/messages.html', function(req, res) {
53
+ winston.debug(req.params);
54
+ winston.debug("here");
46
55
 
47
- winston.debug(req.params);
48
- winston.debug("here");
49
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
50
- if (err) {
51
- return res.status(500).send({success: false, msg: 'Error getting object.'});
52
- }
56
+ try {
57
+ let messages = await Message.find({ "recipient": req.params.requestid }).sort({createdAt: 'asc'}).exec();
53
58
 
54
- if(!messages){
55
- return res.status(404).send({success: false, msg: 'Object not found.'});
56
- }
59
+ let filteredMessages = await filterMessages(messages);
57
60
 
58
- return res.render('messages',
59
- { title: 'Tiledesk',
60
- messages: messages,
61
- brandName: process.env.BRAND_NAME || null,
62
- brandLogo: process.env.BRAND_LOGO || null
63
- });
64
- });
61
+ let idProject = filteredMessages[0] && filteredMessages[0].id_project ? String(filteredMessages[0].id_project) : undefined;
62
+ let tz = await transcriptTz.resolveTranscriptTimezone(req, idProject);
63
+ let messagesForView = attachTranscriptDisplayTimes(filteredMessages, tz);
65
64
 
66
- });
65
+ return res.render('messages',
66
+ {
67
+ title: 'Tiledesk',
68
+ messages: messagesForView,
69
+ brandName: process.env.BRAND_NAME || null,
70
+ brandLogo: process.env.BRAND_LOGO || null
71
+ });
67
72
 
73
+ } catch (error) {
74
+ winston.error("Error getting messages: ", error);
75
+
76
+ return res.status(500).render('error', {
77
+ title: 'Tiledesk',
78
+ error: "An error occurred while getting the messages"
79
+ });
68
80
 
69
- router.get('/:requestid/messages.csv', function(req, res) {
81
+ }
70
82
 
71
- winston.debug(req.params);
72
- winston.debug("here");
73
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).lean().exec(function(err, messages) {
74
- if (err) {
75
- return res.status(500).send({success: false, msg: 'Error getting object.'});
76
- }
83
+ });
77
84
 
78
- if(!messages){
79
- return res.status(404).send({success: false, msg: 'Object not found.'});
80
- }
81
85
 
82
- messages.forEach(function(element) {
83
86
 
84
- var channel_name = "";
85
- if (element.channel && element.channel.name) {
86
- channel_name = element.channel.name;
87
- }
88
- delete element.channel;
89
- element.channel_name = channel_name;
90
87
 
91
- delete element.attributes;
92
- });
88
+ router.get('/:requestid/messages.csv', async function(req, res) {
93
89
 
94
- res.setHeader('Content-Type', 'applictext/csv');
95
- res.setHeader('Content-Disposition', 'attachment; filename=transcript.csv');
90
+ var requestid = req.params.requestid;
91
+ var csvFilename = requestid.replace(/["\\\r\n]/g, '_') + '.csv';
96
92
 
97
- return res.csv(messages, true);
98
- });
93
+ try {
94
+ let messages = await Message.find({"recipient": requestid}).sort({createdAt: 'asc'}).lean().exec();
99
95
 
100
- });
96
+ let filteredMessages = await filterMessages(messages);
101
97
 
98
+ let tz = await transcriptTz.resolveTranscriptTimezone(req, firstMessageProjectId(filteredMessages));
102
99
 
103
- router.get('/:requestid/messages.txt', function(req, res) {
104
-
105
- winston.debug(req.params);
106
- winston.debug("here");
107
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
108
- if (err) {
109
- return res.status(500).send({success: false, msg: 'Error getting object.'});
110
- }
111
-
112
- if(!messages){
113
- return res.status(404).send({success: false, msg: 'Object not found.'});
114
- }
115
-
116
-
117
- var text = "Chat transcript:\n" //+ req.project.name;
118
-
119
- messages.forEach(function(element) {
120
- text = text + "[ " + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ "] " + element.senderFullname + ": " + element.text + "\n";
121
- });
122
-
123
-
124
- res.set({"Content-Disposition":"attachment; filename=\"transcript.txt\""});
125
- res.send(text);
100
+ let rows = filteredMessages.map(function(m) {
101
+ return {
102
+ createdAt: transcriptTz.formatTranscriptInstant(m.createdAt, tz),
103
+ senderFullname: m.senderFullname != null ? String(m.senderFullname) : '',
104
+ text: m.text != null ? String(m.text) : ''
105
+ };
126
106
  });
127
107
 
128
- });
108
+ res.setHeader('Content-Type', 'text/csv; charset=utf-8');
109
+ res.setHeader('Content-Disposition', 'attachment; filename="' + csvFilename + '"');
129
110
 
111
+ if (rows.length === 0) {
112
+ return res.send(['createdAt', 'senderFullname', 'text'].join(csvExpress.separator) + '\r\n');
113
+ }
130
114
 
115
+ return res.csv(rows, true);
131
116
 
132
- router.get('/:requestid/messages.pdf', function(req, res) {
117
+ } catch (err) {
118
+ winston.error('public-request messages.csv', err);
119
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
120
+ }
133
121
 
122
+ });
134
123
 
135
- winston.debug(req.params);
136
- winston.debug("here");
137
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
138
- if (err) {
139
- return res.status(500).send({success: false, msg: 'Error getting object.'});
140
- }
141
124
 
125
+ router.get('/:requestid/messages.txt', function(req, res) {
142
126
 
143
- if(!messages){
127
+ winston.debug(req.params);
128
+ winston.debug("here");
129
+ Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec()
130
+ .then(function (messages) {
131
+ if (!messages) {
144
132
  return res.status(404).send({success: false, msg: 'Object not found.'});
145
133
  }
134
+ return transcriptTz.resolveTranscriptTimezone(req, firstMessageProjectId(messages))
135
+ .then(function (tz) {
136
+ var text = "Chat transcript:\n";
137
+ messages.forEach(function(element) {
138
+ text = text + "[ " + transcriptTz.formatTranscriptInstant(element.createdAt, tz) + "] " + element.senderFullname + ": " + element.text + "\n";
139
+ });
140
+ res.set({"Content-Disposition":"attachment; filename=\"transcript.txt\""});
141
+ res.send(text);
142
+ });
143
+ })
144
+ .catch(function (err) {
145
+ winston.error('public-request messages.txt', err);
146
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
147
+ });
146
148
 
149
+ });
147
150
 
148
- var docDefinition = {
149
- content: [
150
- { text: 'Chat Transcript', style: 'header' },
151
- {
152
- ul: [
153
- // 'item 1',
154
- // 'item 2',
155
- // 'item 3'
156
- ]
157
- },
158
-
159
- ],
160
- styles: {
161
- header: {
162
- bold: true,
163
- fontSize: 15
164
- }
165
- },
166
- defaultStyle: {
167
- fontSize: 12
168
- }
169
- };
170
151
 
171
-
172
152
 
173
- messages.forEach(function(element) {
174
- docDefinition.content[1].ul.push("[ " + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ "] " + element.senderFullname + ": " + element.text );
175
- });
153
+ router.get('/:requestid/messages.pdf', async function(req, res) {
176
154
 
177
- console.log(docDefinition);
178
-
179
- var pdfDoc = printer.createPdfKitDocument(docDefinition);
180
- // pdfDoc.pipe(fs.createWriteStream('lists.pdf'));
155
+ var requestid = req.params.requestid;
156
+ var pdfFilename = requestid.replace(/["\\\r\n]/g, '_') + '.pdf';
157
+
158
+ winston.debug(req.params);
159
+ winston.debug("here");
160
+
161
+ try {
162
+ let messages = await Message.find({"recipient": requestid}).sort({createdAt: 'asc'}).exec();
163
+
164
+ let filteredMessages = await filterMessages(messages);
165
+
166
+ let tz = await transcriptTz.resolveTranscriptTimezone(req, firstMessageProjectId(filteredMessages));
167
+ let docDefinition = buildTranscriptPdfDocDefinition(filteredMessages, tz);
168
+ let pdfDoc = printer.createPdfKitDocument(docDefinition);
181
169
 
182
170
  res.setHeader('Content-Type', 'application/pdf');
183
- res.setHeader('Content-Disposition', 'attachment; filename=transcript.pdf');
184
-
171
+ res.setHeader('Content-Disposition', 'attachment; filename="' + pdfFilename + '"');
185
172
 
186
173
  pdfDoc.pipe(res);
187
174
  pdfDoc.end();
175
+ } catch (err) {
176
+ winston.error('public-request messages.pdf', err);
177
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
178
+ }
188
179
 
189
-
190
-
191
- });
192
-
193
- });
180
+ });
194
181
 
195
182
 
196
183
 
197
- router.get('/:requestid/messages-user.html', function(req, res) {
198
-
199
- winston.debug(req.params);
200
- winston.debug("here");
201
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
202
- if (err) {
203
- return res.status(500).send({success: false, msg: 'Error getting object.'});
204
- }
184
+ router.get('/:requestid/messages-user.html', async function(req, res) {
205
185
 
206
- var messages = messages.filter(m => m.sender != "system" );
186
+ winston.debug(req.params);
187
+ winston.debug("here");
188
+ try {
189
+ let messages = await Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec();
190
+ messages = messages.filter(m => m.sender != "system" );
207
191
 
192
+ if (!messages || messages.length === 0) {
193
+ return res.status(404).send({success: false, msg: 'Object not found.'});
194
+ }
208
195
 
209
- //skip info message
210
- if(!messages){
211
- return res.status(404).send({success: false, msg: 'Object not found.'});
212
- }
196
+ var idProject = messages[0] && messages[0].id_project ? String(messages[0].id_project) : undefined;
197
+ var tz = await transcriptTz.resolveTranscriptTimezone(req, idProject);
198
+ var messagesForView = attachTranscriptDisplayTimes(messages, tz);
213
199
 
214
- return res.render('messages', { title: 'Tiledesk', messages: messages});
200
+ return res.render('messages', { title: 'Tiledesk', messages: messagesForView,
201
+ brandName: process.env.BRAND_NAME || null,
202
+ brandLogo: process.env.BRAND_LOGO || null
215
203
  });
204
+ } catch (err) {
205
+ winston.error('public-request messages-user.html', err);
206
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
207
+ }
216
208
 
217
- });
209
+ });
218
210
 
219
211
 
220
212
 
221
- router.get('/:requestid/messages-user.txt', function(req, res) {
222
-
223
- winston.debug(req.params);
224
- winston.debug("here");
225
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
226
- if (err) {
227
- return res.status(500).send({success: false, msg: 'Error getting object.'});
228
- }
213
+ router.get('/:requestid/messages-user.txt', function(req, res) {
229
214
 
230
- if(!messages){
215
+ winston.debug(req.params);
216
+ winston.debug("here");
217
+ Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec()
218
+ .then(function (messages) {
219
+ if (!messages || messages.length === 0) {
231
220
  return res.status(404).send({success: false, msg: 'Object not found.'});
232
221
  }
233
-
234
-
235
- var messages = messages.filter(m => m.sender != "system" );
236
-
237
- var text = "Chat transcript:\n" //+ req.project.name;
238
-
239
- messages.forEach(function(element) {
240
- text = text + "[ " + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ "] " + element.senderFullname + ": " + element.text + "\n";
241
- });
242
-
243
-
244
- res.set({"Content-Disposition":"attachment; filename=\"transcript.txt\""});
245
- res.send(text);
222
+ messages = messages.filter(m => m.sender != "system" );
223
+ if (messages.length === 0) {
224
+ return res.status(404).send({success: false, msg: 'Object not found.'});
225
+ }
226
+ return transcriptTz.resolveTranscriptTimezone(req, firstMessageProjectId(messages))
227
+ .then(function (tz) {
228
+ var text = "Chat transcript:\n";
229
+ messages.forEach(function(element) {
230
+ text = text + "[ " + transcriptTz.formatTranscriptInstant(element.createdAt, tz) + "] " + element.senderFullname + ": " + element.text + "\n";
231
+ });
232
+ res.set({"Content-Disposition":"attachment; filename=\"transcript.txt\""});
233
+ res.send(text);
234
+ });
235
+ })
236
+ .catch(function (err) {
237
+ winston.error('public-request messages-user.txt', err);
238
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
246
239
  });
247
240
 
248
- });
241
+ });
249
242
 
250
243
 
251
- router.get('/:requestid/messages-user.pdf', function(req, res) {
244
+ router.get('/:requestid/messages-user.pdf', async function(req, res) {
252
245
 
246
+ var requestid = req.params.requestid;
247
+ var pdfFilename = requestid.replace(/["\\\r\n]/g, '_') + '.pdf';
253
248
 
254
- winston.debug(req.params);
255
- winston.debug("here");
256
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) {
257
- if (err) {
258
- return res.status(500).send({success: false, msg: 'Error getting object.'});
259
- }
249
+ winston.debug(req.params);
250
+ winston.debug("here");
260
251
 
261
- var messages = messages.filter(m => m.sender != "system" );
252
+ try {
253
+ let messages = await Message.find({"recipient": requestid}).sort({createdAt: 'asc'}).exec();
262
254
 
255
+ if (!messages || messages.length === 0) {
256
+ return res.status(404).send({success: false, msg: 'Object not found.'});
257
+ }
263
258
 
264
- //skip info message
265
- if(!messages){
266
- return res.status(404).send({success: false, msg: 'Object not found.'});
267
- }
259
+ messages = messages.filter(m => m.sender != "system" );
268
260
 
261
+ if (messages.length === 0) {
262
+ return res.status(404).send({success: false, msg: 'Object not found.'});
263
+ }
269
264
 
270
- var docDefinition = {
271
- content: [
272
- { text: 'Chat Transcript', style: 'header' },
273
- {
274
- ul: [
275
- // 'item 1',
276
- // 'item 2',
277
- // 'item 3'
278
- ]
279
- },
280
-
281
- ],
282
- styles: {
283
- header: {
284
- bold: true,
285
- fontSize: 15
286
- }
287
- },
288
- defaultStyle: {
289
- fontSize: 12
290
- }
291
- };
292
-
293
-
294
-
295
- messages.forEach(function(element) {
296
- docDefinition.content[1].ul.push("[ " + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ "] " + element.senderFullname + ": " + element.text );
297
- });
298
-
299
- console.log(docDefinition);
300
-
301
- var pdfDoc = printer.createPdfKitDocument(docDefinition);
302
- // pdfDoc.pipe(fs.createWriteStream('lists.pdf'));
265
+ let tz = await transcriptTz.resolveTranscriptTimezone(req, firstMessageProjectId(messages));
266
+ let docDefinition = buildTranscriptPdfDocDefinition(messages, tz);
267
+ let pdfDoc = printer.createPdfKitDocument(docDefinition);
303
268
 
304
269
  res.setHeader('Content-Type', 'application/pdf');
305
- res.setHeader('Content-Disposition', 'attachment; filename=transcript.pdf');
306
-
270
+ res.setHeader('Content-Disposition', 'attachment; filename="' + pdfFilename + '"');
271
+
307
272
  pdfDoc.pipe(res);
308
273
  pdfDoc.end();
274
+ } catch (err) {
275
+ winston.error('public-request messages-user.pdf', err);
276
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
277
+ }
309
278
 
279
+ });
310
280
 
311
-
312
- });
313
281
 
314
- });
282
+ router.get('/:requestid/messages-user.csv', function(req, res) {
315
283
 
284
+ var requestid = req.params.requestid;
285
+ var csvFilename = requestid.replace(/["\\\r\n]/g, '_') + '.csv';
316
286
 
317
- router.get('/:requestid/messages-user.csv', function(req, res) {
318
-
319
- winston.debug(req.params);
320
- winston.debug("here");
321
- return Message.find({"recipient": req.params.requestid}).sort({createdAt: 'asc'}).lean().exec(function(err, messages) {
322
- if (err) {
323
- return res.status(500).send({success: false, msg: 'Error getting object.'});
287
+ winston.debug(req.params);
288
+ winston.debug("here");
289
+ Message.find({"recipient": requestid}).sort({createdAt: 'asc'}).lean().exec()
290
+ .then(function (messages) {
291
+ if (!messages || messages.length === 0) {
292
+ return res.status(404).send({success: false, msg: 'Object not found.'});
324
293
  }
325
-
326
- var messages = messages.filter(m => m.sender != "system" );
327
-
328
-
329
- //skip info message
330
- if(!messages){
294
+ messages = messages.filter(m => m.sender != "system" );
295
+ if (messages.length === 0) {
331
296
  return res.status(404).send({success: false, msg: 'Object not found.'});
332
297
  }
298
+ return transcriptTz.resolveTranscriptTimezone(req, firstMessageProjectId(messages))
299
+ .then(function (tz) {
300
+ var rows = messages.map(function(m) {
301
+ return {
302
+ createdAt: transcriptTz.formatTranscriptInstant(m.createdAt, tz),
303
+ senderFullname: m.senderFullname != null ? String(m.senderFullname) : '',
304
+ text: m.text != null ? String(m.text) : ''
305
+ };
306
+ });
307
+
308
+ res.setHeader('Content-Type', 'text/csv; charset=utf-8');
309
+ res.setHeader('Content-Disposition', 'attachment; filename="' + csvFilename + '"');
310
+
311
+ if (rows.length === 0) {
312
+ return res.send(['createdAt', 'senderFullname', 'text'].join(csvExpress.separator) + '\r\n');
313
+ }
333
314
 
315
+ return res.csv(rows, true);
316
+ });
317
+ })
318
+ .catch(function (err) {
319
+ winston.error('public-request messages-user.csv', err);
320
+ return res.status(500).send({success: false, msg: 'Error getting object.'});
321
+ });
334
322
 
335
- messages.forEach(function(element) {
323
+ });
336
324
 
337
- var channel_name = "";
338
- if (element.channel && element.channel.name) {
339
- channel_name = element.channel.name;
340
- }
341
- delete element.channel;
342
- element.channel_name = channel_name;
325
+ function filterMessages(messages) {
343
326
 
344
- delete element.attributes;
345
- });
327
+ if (messages[0].text === "welcome" || messages[0].text === "/start") {
328
+ messages = messages.slice(1);
329
+ }
330
+ return messages.filter(m => m.sender !== "system" );
346
331
 
332
+ }
347
333
 
348
- res.setHeader('Content-Type', 'applictext/csv');
349
- res.setHeader('Content-Disposition', 'attachment; filename=transcript.csv');
334
+ function firstMessageProjectId(messages) {
335
+ return messages && messages[0] && messages[0].id_project ? String(messages[0].id_project) : undefined;
336
+ }
350
337
 
351
- return res.csv(messages, true);
338
+ function attachTranscriptDisplayTimes(messages, timeZone) {
339
+ return messages.map(function (m) {
340
+ var o = typeof m.toObject === 'function' ? m.toObject() : m;
341
+ return Object.assign({}, o, {
342
+ transcriptDisplayTime: transcriptTz.formatTranscriptInstant(m.createdAt, timeZone)
343
+ });
344
+ });
345
+ }
346
+
347
+ /** Footer line aligned with views/messages.jade (Powered by …). */
348
+ function transcriptPdfFooterLine() {
349
+ var brand = process.env.BRAND_NAME;
350
+ if (brand) {
351
+ return 'Powered by ' + brand;
352
+ }
353
+ return 'Powered by Tiledesk';
354
+ }
355
+
356
+ /**
357
+ * pdfmake document for a chat transcript: title, message blocks (sender, body, time), footer.
358
+ * Simplified layout inspired by messages.jade / messages-layout.jade.
359
+ */
360
+ function buildTranscriptPdfDocDefinition(messages, tz) {
361
+ var innerWidthPt = 515;
362
+ var content = [
363
+ { text: 'Chat transcript', style: 'pdfTitle', alignment: 'center', margin: [0, 0, 0, 18] }
364
+ ];
365
+
366
+ messages.forEach(function (element, index) {
367
+ if (index > 0) {
368
+ content.push({
369
+ canvas: [
370
+ { type: 'line', x1: 0, y1: 0, x2: innerWidthPt, y2: 0, lineWidth: 0.5, lineColor: '#e2e8f0' }
371
+ ],
372
+ margin: [0, 4, 0, 12]
373
+ });
374
+ }
375
+
376
+ var sender = element.senderFullname || 'Unknown';
377
+ var body = element.text != null ? String(element.text) : '';
378
+ var timeStr = transcriptTz.formatTranscriptInstant(element.createdAt, tz);
379
+
380
+ content.push({
381
+ stack: [
382
+ { text: sender, style: 'pdfSender' },
383
+ { text: body, style: 'pdfBody', margin: [0, 6, 0, 2] },
384
+ {
385
+ columns: [
386
+ { width: '*', text: '' },
387
+ { width: 'auto', text: timeStr, style: 'pdfMeta' }
388
+ ],
389
+ columnGap: 0
390
+ }
391
+ ],
392
+ margin: [0, 0, 0, 2]
352
393
  });
394
+ });
353
395
 
396
+ content.push({
397
+ text: transcriptPdfFooterLine(),
398
+ style: 'pdfFooter',
399
+ alignment: 'center',
400
+ margin: [0, 24, 0, 0]
354
401
  });
355
402
 
403
+ return {
404
+ pageSize: 'A4',
405
+ pageMargins: [40, 44, 40, 44],
406
+ content: content,
407
+ styles: {
408
+ pdfTitle: { fontSize: 20, bold: true, color: '#0f172a' },
409
+ pdfSender: { fontSize: 11, bold: true, color: '#0f172a' },
410
+ pdfBody: { fontSize: 10, color: '#334155' },
411
+ pdfMeta: { fontSize: 8, color: '#64748b', alignment: 'right' },
412
+ pdfFooter: { fontSize: 9, color: '#64748b' }
413
+ },
414
+ defaultStyle: {
415
+ font: 'Roboto',
416
+ fontSize: 10,
417
+ lineHeight: 1.4
418
+ }
419
+ };
420
+ }
421
+
356
422
  module.exports = router;
package/routes/request.js CHANGED
@@ -314,6 +314,11 @@ router.patch('/:requestid', function (req, res) {
314
314
  requestEvent.emit("request.update", request);
315
315
  requestEvent.emit("request.update.comment", { comment: "PATCH", request: request }); //Deprecated
316
316
  requestEvent.emit("request.updated", { comment: "PATCH", request: request, patch: update });
317
+
318
+ if (update.rating != null) {
319
+ requestEvent.emit("request.satisfaction", { request: request, patch: update });
320
+ }
321
+
317
322
  return res.json(request);
318
323
  });
319
324
 
@@ -1009,8 +1014,13 @@ router.put('/:requestid/tag', async (req, res) => {
1009
1014
  .execPopulate();
1010
1015
 
1011
1016
  requestEvent.emit("request.update", populatedRequest)
1017
+
1018
+ if (adding_tags.length > 0) {
1019
+ requestEvent.emit("request.tag.update", { request: populatedRequest, tags: adding_tags });
1020
+ }
1021
+
1012
1022
  res.status(200).send(updatedRequest)
1013
-
1023
+
1014
1024
  if (process.env.NODE_ENV !== 'test') {
1015
1025
  scheduleTags(id_project, adding_tags);
1016
1026
  }