@tiledesk/tiledesk-server 2.14.7 → 2.14.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@
5
5
  🚀 IN PRODUCTION 🚀
6
6
  (https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
7
7
 
8
+ # 2.14.9
9
+ - Improved the requests search function and added the ability to specify a timezone when searching
10
+
11
+ # 2.14.8
12
+ - Added extraction of namespace_id from request body in scrape status route
13
+
8
14
  # 2.14.7
9
15
  - Added embedding configuration to namespace import route
10
16
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiledesk/tiledesk-server",
3
3
  "description": "The Tiledesk server module",
4
- "version": "2.14.7",
4
+ "version": "2.14.9",
5
5
  "scripts": {
6
6
  "start": "node ./bin/www",
7
7
  "pretest": "mongodb-runner start",
package/routes/kb.js CHANGED
@@ -247,6 +247,7 @@ router.post('/scrape/status', async (req, res) => {
247
247
  let project_id = req.projectid;
248
248
  // (EXAMPLE) body: { id, namespace }
249
249
  let data = req.body;
250
+ let namespace_id = data.namespace;
250
251
  winston.debug("/scrapeStatus req.body: ", req.body);
251
252
 
252
253
  let returnObject = false;
package/routes/request.js CHANGED
@@ -33,6 +33,7 @@ const RoleConstants = require('../models/roleConstants');
33
33
  const eventService = require('../pubmodules/events/eventService');
34
34
  const { Scheduler } = require('../services/Scheduler');
35
35
  const faq_kb = require('../models/faq_kb');
36
+ const datesUtil = require('../utils/datesUtil');
36
37
  //const JobManager = require('../utils/jobs-worker-queue-manager-v2/JobManagerV2');
37
38
 
38
39
  // var messageService = require('../services/messageService');
@@ -1178,6 +1179,7 @@ router.get('/', function (req, res, next) {
1178
1179
  var skip = 0;
1179
1180
  let statusArray = [];
1180
1181
  var projectuser = req.projectuser;
1182
+ let filterRangeField = req.query.filterRangeField || 'createdAt';
1181
1183
 
1182
1184
  if (req.query.limit) {
1183
1185
  limit = parseInt(req.query.limit);
@@ -1278,6 +1280,10 @@ router.get('/', function (req, res, next) {
1278
1280
  // query.request_id = req.query.request_id;
1279
1281
  // }
1280
1282
 
1283
+ let timezone = req.query.timezone || 'Europe/Rome';
1284
+ let queryDateRange = false;
1285
+ let queryStartDate;
1286
+ let queryEndDate;
1281
1287
  /**
1282
1288
  **! *** DATE RANGE USECASE 1 ***
1283
1289
  * in the tiledesk dashboard's HISTORY PAGE
@@ -1287,18 +1293,9 @@ router.get('/', function (req, res, next) {
1287
1293
  */
1288
1294
  //fixato. secondo me qui manca un parentesi tonda per gli or
1289
1295
  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))) {
1290
-
1291
- var startdate = moment().subtract(14, "days").format("YYYY-MM-DD");
1292
- var enddate = moment().format("YYYY-MM-DD");
1293
-
1294
- winston.debug('»»» REQUEST ROUTE - startdate ', startdate);
1295
- winston.debug('»»» REQUEST ROUTE - enddate ', enddate);
1296
-
1297
- var enddatePlusOneDay = moment(new Date()).add(1, 'days').toDate()
1298
- winston.debug('»»» REQUEST ROUTE - enddate + 1 days: ', enddatePlusOneDay);
1299
-
1300
- query.createdAt = { $gte: new Date(Date.parse(startdate)).toISOString(), $lte: new Date(enddatePlusOneDay).toISOString() }
1301
- winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);
1296
+ queryDateRange = true;
1297
+ queryStartDate = moment().subtract(14, "days").format("YYYY/MM/DD");
1298
+ queryEndDate = null;
1302
1299
  }
1303
1300
 
1304
1301
  /**
@@ -1306,45 +1303,26 @@ router.get('/', function (req, res, next) {
1306
1303
  * in the tiledesk dashboard's HISTORY PAGE
1307
1304
  * WHEN THE USER SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS
1308
1305
  */
1309
- if (req.query.start_date && req.query.end_date) {
1310
- winston.debug('REQUEST ROUTE - REQ QUERY start_date ', req.query.start_date);
1311
- winston.debug('REQUEST ROUTE - REQ QUERY end_date ', req.query.end_date);
1312
-
1313
- /**
1314
- * USING TIMESTAMP in MS */
1315
- // var formattedStartDate = new Date(+req.query.start_date);
1316
- // var formattedEndDate = new Date(+req.query.end_date);
1317
- // query.createdAt = { $gte: formattedStartDate, $lte: formattedEndDate }
1318
-
1319
- /**
1320
- * USING MOMENT */
1321
- var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');
1322
- var endDate = moment(req.query.end_date, 'DD/MM/YYYY').format('YYYY-MM-DD');
1323
1306
 
1324
- winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED START DATE ', startDate);
1325
- winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE ', endDate);
1326
-
1327
- // ADD ONE DAY TO THE END DAY
1328
- var date = new Date(endDate);
1329
- var newdate = new Date(date);
1330
- var endDate_plusOneDay = newdate.setDate(newdate.getDate() + 1);
1331
- winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE + 1 DAY ', endDate_plusOneDay);
1332
-
1333
- query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString(), $lte: new Date(endDate_plusOneDay).toISOString() }
1334
- winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);
1335
-
1336
- } else if (req.query.start_date && !req.query.end_date) {
1337
- winston.debug('REQUEST ROUTE - REQ QUERY END DATE IS EMPTY (so search only for start date)');
1338
- var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');
1307
+ if (req.query.start_date || req.query.end_date) {
1308
+ queryDateRange = true;
1309
+ queryStartDate = req.query.start_date;
1310
+ queryEndDate = req.query.end_date;
1311
+ }
1312
+ else if (req.query.start_date_time || req.query.end_date_time) {
1313
+ queryDateRange = true;
1314
+ queryStartDate = req.query.start_date_time;
1315
+ queryEndDate = req.query.end_date_time;
1316
+ }
1339
1317
 
1340
- var range = { $gte: new Date(Date.parse(startDate)).toISOString() };
1341
- if (req.query.filterRangeField) {
1342
- query[req.query.filterRangeField] = range;
1343
- } else {
1344
- query.createdAt = range;
1318
+ if (queryDateRange) {
1319
+ try {
1320
+ let rangeQuery = datesUtil.createDateRangeQuery(queryStartDate, queryEndDate, timezone, filterRangeField);
1321
+ Object.assign(query, rangeQuery);
1322
+ } catch (error) {
1323
+ winston.error('Error creating date range query: ', error);
1324
+ return res.status(500).send({ success: false, error: error?.message });
1345
1325
  }
1346
-
1347
- winston.debug('REQUEST ROUTE - QUERY CREATED AT (only for start date)', query.createdAt);
1348
1326
  }
1349
1327
 
1350
1328
  if (req.query.snap_department_routing) {
@@ -2395,4 +2373,4 @@ async function scheduleTags(id_project, tags) {
2395
2373
  }
2396
2374
 
2397
2375
 
2398
- module.exports = router;
2376
+ module.exports = router;
@@ -0,0 +1,161 @@
1
+ const { expect } = require('chai');
2
+ const datesUtil = require('../utils/datesUtil');
3
+ const moment = require('moment-timezone');
4
+
5
+ describe('datesUtil', () => {
6
+
7
+ it('test1', () => {
8
+ const startDate = '21/01/2026';
9
+ const endDate = '21/01/2026';
10
+ const timezone = 'Europe/Rome';
11
+
12
+ const res = datesUtil.createDateRangeQuery(startDate, endDate, timezone);
13
+
14
+ expect(res).to.have.property('createdAt');
15
+ expect(res.createdAt).to.have.property('$gte');
16
+ expect(res.createdAt).to.have.property('$lt');
17
+
18
+ let startDateUTC = new Date("2026-01-20T23:00:00.000Z").toISOString();
19
+ let endDateUTC = new Date("2026-01-21T23:00:00.000Z").toISOString();
20
+
21
+ expect(res.createdAt.$gte.toISOString()).to.equal(startDateUTC);
22
+ expect(res.createdAt.$lt.toISOString()).to.equal(endDateUTC);
23
+ })
24
+
25
+ it('test2', () => {
26
+ const startDate = '20/01/2026, 12:25';
27
+ const endDate = '20/01/2026, 15:25 ';
28
+ const timezone = 'Europe/Rome';
29
+
30
+ const res = datesUtil.createDateRangeQuery(startDate, endDate, timezone);
31
+
32
+ console.log(res);
33
+
34
+ expect(res).to.have.property('createdAt');
35
+ expect(res.createdAt).to.have.property('$gte');
36
+ expect(res.createdAt).to.have.property('$lte');
37
+
38
+ let startDateUTC = new Date("2026-01-20T11:25:00.000Z").toISOString();
39
+ let endDateUTC = new Date("2026-01-20T14:25:00.000Z").toISOString();
40
+
41
+ expect(res.createdAt.$gte.toISOString()).to.equal(startDateUTC);
42
+ expect(res.createdAt.$lte.toISOString()).to.equal(endDateUTC);
43
+ })
44
+ // describe('parseDate', () => {
45
+ // it('should parse DD/MM/YYYY format and apply timezone', () => {
46
+ // const dateStr = '28/02/2023';
47
+ // const timezone = 'Europe/Rome';
48
+ // const m = datesUtil.parseDate(dateStr, timezone);
49
+ // expect(m).to.be.an.instanceof(moment);
50
+ // expect(m.tz()).to.equal(timezone);
51
+ // expect(m.format('DD/MM/YYYY')).to.equal('28/02/2023');
52
+ // });
53
+
54
+ // it('should parse DD/MM/YYYY HH:mm:ss format and apply timezone', () => {
55
+ // const dateStr = '01/03/2023 13:14:15';
56
+ // const timezone = 'America/New_York';
57
+ // const m = datesUtil.parseDate(dateStr, timezone);
58
+
59
+ // expect(m.format('DD/MM/YYYY HH:mm:ss')).to.equal('01/03/2023 07:14:15');
60
+ // expect(m.tz()).to.equal(timezone);
61
+ // });
62
+
63
+ // it('should throw error for invalid timezone', () => {
64
+ // expect(() => datesUtil.parseDate('01/03/2023', 'Invalid/Zone')).to.throw('Invalid timezone');
65
+ // });
66
+
67
+ // it('should throw error for unparseable date', () => {
68
+ // expect(() => datesUtil.parseDate('notadate', 'Europe/Rome')).to.throw('Unable to parse the date');
69
+ // });
70
+ // });
71
+
72
+
73
+ // describe('hasTimeComponent', () => {
74
+ // it('should return true for string with time', () => {
75
+ // expect(datesUtil.hasTimeComponent('20/02/2021 23:31:01')).to.be.true;
76
+ // expect(datesUtil.hasTimeComponent('20/02/2021 02:31')).to.be.true;
77
+ // });
78
+ // it('should return false for string without time', () => {
79
+ // expect(datesUtil.hasTimeComponent('20/02/2021')).to.be.false;
80
+ // expect(datesUtil.hasTimeComponent('01/03/2022')).to.be.false;
81
+ // });
82
+ // });
83
+
84
+ // describe('convertLocalDatesToUTC', () => {
85
+ // it('should convert start and end dates and output correct UTC ISO strings', () => {
86
+ // const timezone = 'Europe/Rome';
87
+ // const startDate = '01/06/2021';
88
+ // const endDate = '05/06/2021';
89
+ // const res = datesUtil.convertLocalDatesToUTC(startDate, endDate, timezone);
90
+
91
+ // // startDateUTC should be 2021-06-01T00:00:00.000Z in UTC
92
+ // expect(res.startDateUTC).to.match(/^2021-05-31T22:00:00/);
93
+ // // endDateUTC should be 2021-06-06T00:00:00.000Z in UTC (start of *next* day)
94
+ // expect(res.endDateUTC).to.match(/^2021-06-05T22:00:00/);
95
+ // });
96
+
97
+ // it('should convert when time is included in the dates', () => {
98
+ // const timezone = 'America/New_York';
99
+ // const startDate = '20/07/2022 09:30:00';
100
+ // const endDate = '21/07/2022 17:45:10';
101
+ // const res = datesUtil.convertLocalDatesToUTC(startDate, endDate, timezone);
102
+ // // The result.startDateUTC and endDateUTC should be the same as the input local time mapped to UTC
103
+ // expect(new Date(res.startDateUTC).toISOString()).to.equal(res.startDateUTC);
104
+ // expect(new Date(res.endDateUTC).toISOString()).to.equal(res.endDateUTC);
105
+ // });
106
+
107
+ // it('should throw if both startDate and endDate are missing', () => {
108
+ // expect(() => datesUtil.convertLocalDatesToUTC(null, null, 'Europe/Rome')).to.throw('At least one of startDate or endDate must be provided');
109
+ // });
110
+ // });
111
+
112
+ // describe('createDateRangeQuery', () => {
113
+ // it('should generate MongoDB query with $gte and $lt for date only', () => {
114
+ // const tz = 'Europe/Rome';
115
+ // const res = datesUtil.createDateRangeQuery('02/02/2022', '05/02/2022', tz, 'createdAt');
116
+ // expect(res).to.have.property('createdAt');
117
+ // expect(res.createdAt).to.have.property('$gte');
118
+ // expect(res.createdAt).to.have.property('$lt');
119
+ // expect(res.createdAt).to.not.have.property('$lte');
120
+ // });
121
+
122
+ // it('should use $lte operator for endDate with time component', () => {
123
+ // const tz = 'Europe/Rome';
124
+ // const r = datesUtil.createDateRangeQuery('02/02/2022 09:00:00', '05/02/2022 18:23:10', tz, 'createdAt');
125
+ // expect(r.createdAt).to.have.property('$gte');
126
+ // expect(r.createdAt).to.have.property('$lte');
127
+ // expect(r.createdAt).to.not.have.property('$lt');
128
+ // });
129
+
130
+ // it('should allow custom fieldName', () => {
131
+ // const tz = 'Europe/Rome';
132
+ // const result = datesUtil.createDateRangeQuery('10/08/2023', '15/08/2023', tz, 'customField');
133
+ // expect(result).to.have.property('customField');
134
+ // expect(result.customField).to.have.property('$gte');
135
+ // expect(result.customField).to.have.property('$lt');
136
+ // });
137
+
138
+ // it('should allow only startDate', () => {
139
+ // const tz = 'Europe/Rome';
140
+ // const result = datesUtil.createDateRangeQuery('10/09/2023', undefined, tz);
141
+ // expect(result).to.have.property('createdAt');
142
+ // expect(result.createdAt).to.have.property('$gte');
143
+ // expect(result.createdAt).to.not.have.property('$lt');
144
+ // expect(result.createdAt).to.not.have.property('$lte');
145
+ // });
146
+
147
+ // it('should allow only endDate', () => {
148
+ // const tz = 'Europe/Rome';
149
+ // const result = datesUtil.createDateRangeQuery(null, '10/09/2023', tz);
150
+ // expect(result).to.have.property('createdAt');
151
+ // expect(result.createdAt).to.have.property('$lt');
152
+ // expect(result.createdAt).to.not.have.property('$gte');
153
+ // expect(result.createdAt).to.not.have.property('$lte');
154
+ // });
155
+
156
+ // it('should throw error for invalid timezone', () => {
157
+ // expect(() => datesUtil.createDateRangeQuery('01/01/2023', '02/01/2023', 'Invalid/Timezone')).to.throw('Invalid timezone');
158
+ // });
159
+ // });
160
+
161
+ });
@@ -0,0 +1,173 @@
1
+ const moment = require('moment-timezone');
2
+
3
+ class DatesUtil {
4
+
5
+ /**
6
+ * Helper function to parse a date with optional time
7
+ * Supports formats: DD/MM/YYYY, DD/MM/YYYY HH:mm, DD/MM/YYYY HH:mm:ss, DD/MM/YYYY, HH:mm:ss
8
+ *
9
+ * @param {string} dateString - Date string with or without time
10
+ * @param {string} timezone - Timezone
11
+ * @returns {moment.Moment} Parsed moment object
12
+ */
13
+ parseDate(dateString, timezone) {
14
+ if (!dateString || typeof dateString !== 'string') {
15
+ throw new Error(`The date must be a string: ${dateString}`);
16
+ }
17
+
18
+ if (!timezone || typeof timezone !== 'string') {
19
+ throw new Error(`The timezone must be a string: ${timezone}`);
20
+ }
21
+
22
+ // Validate timezone: check if it's a valid IANA timezone
23
+ if (!moment.tz.zone(timezone)) {
24
+ throw new Error(`Invalid timezone: ${timezone}. Please use a valid IANA timezone (e.g., "Europe/Rome", "America/New_York")`);
25
+ }
26
+
27
+ const trimmedDate = dateString.trim();
28
+
29
+ // List of possible formats to try
30
+ const formats = [
31
+ 'DD/MM/YYYY HH:mm:ss',
32
+ 'DD/MM/YYYY, HH:mm:ss',
33
+ 'DD/MM/YYYY HH:mm',
34
+ 'DD/MM/YYYY, HH:mm',
35
+ 'DD/MM/YYYY',
36
+ 'YYYY/MM/DD HH:mm:ss',
37
+ 'YYYY/MM/DD, HH:mm:ss',
38
+ 'YYYY/MM/DD HH:mm',
39
+ 'YYYY/MM/DD, HH:mm',
40
+ 'YYYY/MM/DD'
41
+ ];
42
+
43
+ // Try every format until one valid is found
44
+ for (const format of formats) {
45
+ // Parse the date in the specified format (without timezone)
46
+ //const parsed = moment(trimmedDate, format, true); // strict mode
47
+ const parsed = moment.tz(trimmedDate, format, timezone);
48
+ if (parsed && parsed.isValid && parsed.isValid()) {
49
+ // Apply the timezone to the parsed date
50
+ const dateWithTimezone = parsed.tz(timezone);
51
+
52
+ // Verify that the timezone was applied correctly
53
+ if (!dateWithTimezone || !dateWithTimezone.isValid || !dateWithTimezone.isValid()) {
54
+ throw new Error(`Unable to apply timezone ${timezone} to date: ${dateString}`);
55
+ }
56
+
57
+ return dateWithTimezone;
58
+ }
59
+ }
60
+
61
+ throw new Error(`Unable to parse the date: ${dateString} with timezone: ${timezone}`);
62
+ }
63
+
64
+ /**
65
+ * Checks if a date string contains time information
66
+ *
67
+ * @param {string} dateString - Date string
68
+ * @returns {boolean} True if contains time information
69
+ */
70
+ hasTimeComponent(dateString) {
71
+ // Checks if contains time information (HH:mm or HH:mm:ss)
72
+ return /[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?/.test(dateString);
73
+ }
74
+
75
+ /**
76
+ * Converts local dates to UTC for database queries
77
+ *
78
+ * @param {string|null|undefined} startDate - Start date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss (e.g. "20/01/2026" or "20/01/2026 14:30:00"). Optional.
79
+ * @param {string|null|undefined} endDate - End date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss (e.g. "20/01/2026" or "20/01/2026 23:59:59"). Optional.
80
+ * @param {string} timezone - User timezone (e.g. "Europe/Rome", "America/New_York", "Asia/Tokyo")
81
+ * @returns {Object} Object with startDateUTC and/or endDateUTC in ISO string format for MongoDB
82
+ */
83
+ convertLocalDatesToUTC(startDate, endDate, timezone) {
84
+ if (!timezone || typeof timezone !== 'string') {
85
+ throw new Error(`The timezone must be a string: ${timezone}`);
86
+ }
87
+
88
+ // Validate timezone: check if it's a valid IANA timezone
89
+ if (!moment.tz.zone(timezone)) {
90
+ throw new Error(`Invalid timezone: ${timezone}. Please use a valid IANA timezone (e.g., "Europe/Rome", "America/New_York")`);
91
+ }
92
+
93
+ const result = {
94
+ startDateUTC: null,
95
+ endDateUTC: null,
96
+ startDate: null,
97
+ endDate: null
98
+ };
99
+
100
+ // Parse startDate if provided
101
+ if (startDate) {
102
+ const startMoment = this.parseDate(startDate, timezone);
103
+ // If startDate does not contain time, use the start of the day
104
+ // Otherwise use the specified time
105
+ const startParsed = this.hasTimeComponent(startDate)
106
+ ? startMoment
107
+ : startMoment.startOf('day');
108
+
109
+ result.startDateUTC = startParsed.utc().toISOString();
110
+ result.startDate = startParsed.utc().toDate();
111
+ }
112
+
113
+ // Parse endDate if provided
114
+ if (endDate) {
115
+ const endMoment = this.parseDate(endDate, timezone);
116
+ // If endDate does not contain time, use the start of the next day
117
+ // Otherwise use the specified time
118
+ const endParsed = this.hasTimeComponent(endDate)
119
+ ? endMoment
120
+ : endMoment.clone().add(1, 'day').startOf('day');
121
+
122
+ result.endDateUTC = endParsed.utc().toISOString();
123
+ result.endDate = endParsed.utc().toDate();
124
+ }
125
+
126
+ // At least one date must be provided
127
+ if (!startDate && !endDate) {
128
+ throw new Error('At least one of startDate or endDate must be provided');
129
+ }
130
+
131
+ return result;
132
+ }
133
+
134
+ /**
135
+ * Creates a MongoDB query object with the converted dates in UTC
136
+ *
137
+ * @param {string|null|undefined} startDate - Start date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss. Optional (from this date onwards).
138
+ * @param {string|null|undefined} endDate - End date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss. Optional (until this date).
139
+ * @param {string} timezone - User timezone
140
+ * @param {string} fieldName - Field name in the database (default: "createdAt")
141
+ * @returns {Object} MongoDB query object with $gte (if startDate provided), $lt/$lte (if endDate provided), or both
142
+ */
143
+ createDateRangeQuery(startDate, endDate, timezone, fieldName = 'createdAt') {
144
+ const { startDate: start, endDate: end } = this.convertLocalDatesToUTC(startDate, endDate, timezone);
145
+
146
+ const query = {};
147
+ const dateQuery = {};
148
+
149
+ // Add start date condition (from this date onwards)
150
+ if (start) {
151
+ dateQuery.$gte = start;
152
+ }
153
+
154
+ // Add end date condition (until this date)
155
+ if (end) {
156
+ // If endDate contains time, use $lte to include up to that time
157
+ // Otherwise use $lt to exclude the start of the next day
158
+ const endOperator = endDate && this.hasTimeComponent(endDate) ? '$lte' : '$lt';
159
+ dateQuery[endOperator] = end;
160
+ }
161
+
162
+ // Only add the date query if at least one condition was set
163
+ if (Object.keys(dateQuery).length > 0) {
164
+ query[fieldName] = dateQuery;
165
+ }
166
+
167
+ return query;
168
+ }
169
+
170
+ }
171
+
172
+ const datesUtil = new DatesUtil();
173
+ module.exports = datesUtil;