@tiledesk/tiledesk-server 2.14.8 → 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 +3 -0
- package/package.json +1 -1
- package/routes/request.js +27 -49
- package/test/dateUtils.test.js +161 -0
- package/utils/datesUtil.js +173 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
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
|
+
|
|
8
11
|
# 2.14.8
|
|
9
12
|
- Added extraction of namespace_id from request body in scrape status route
|
|
10
13
|
|
package/package.json
CHANGED
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
|
-
|
|
1292
|
-
|
|
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
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
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
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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;
|