-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgetTasks.js
More file actions
101 lines (88 loc) · 2.88 KB
/
getTasks.js
File metadata and controls
101 lines (88 loc) · 2.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
'use strict';
const Archetype = require('archetype');
const GetTasksParams = new Archetype({
start: { $type: Date },
end: { $type: Date },
status: { $type: 'string' },
name: { $type: 'string' },
skip: { $type: Number, $default: 0 },
limit: { $type: Number, $default: 100 }
}).compile('GetTasksParams');
const ALL_STATUSES = ['pending', 'in_progress', 'succeeded', 'failed', 'cancelled', 'unknown'];
/** Max documents per request to avoid excessive memory and response size. */
const MAX_LIMIT = 2000;
/** Escape special regex characters so the name is matched literally. */
function escapeRegex(str) {
return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function buildMatch(params) {
const { start, end, status, name } = params;
const match = {};
if (start != null && end != null) {
match.scheduledAt = { $gte: start, $lt: end };
} else if (start != null) {
match.scheduledAt = { $gte: start };
}
const statusVal = typeof status === 'string' ? status.trim() : status;
if (statusVal != null && statusVal !== '') {
match.status = statusVal;
} else {
match.status = { $in: ALL_STATUSES };
}
if (name != null && name !== '') {
const nameStr = typeof name === 'string' ? name.trim() : String(name);
match.name = { $regex: escapeRegex(nameStr), $options: 'i' };
}
return match;
}
/** Projection done in aggregation: only fields needed by frontend, payload → parameters, _id → id. */
const TASK_PROJECT_STAGE = {
_id: 1,
id: '$_id',
name: 1,
status: 1,
scheduledAt: 1,
createdAt: 1,
startedAt: 1,
completedAt: 1,
error: 1,
parameters: '$payload'
};
function ensureDate(value) {
if (value == null) return value;
if (value instanceof Date) return value;
if (typeof value === 'string' || typeof value === 'number') {
const d = new Date(value);
if (!Number.isNaN(d.getTime())) return d;
}
return value;
}
module.exports = ({ db }) => async function getTasks(params) {
params = new GetTasksParams(params);
params.start = ensureDate(params.start);
params.end = ensureDate(params.end);
if (typeof params.status === 'string') params.status = params.status.trim();
if (typeof params.name === 'string') params.name = params.name.trim();
const skip = Math.max(0, Number(params.skip) || 0);
const limit = Math.min(MAX_LIMIT, Math.max(0, Number(params.limit) || 100));
const { Task } = db.models;
const match = buildMatch(params);
const pipeline = [
{ $match: match },
{
$facet: {
tasks: [
{ $sort: { scheduledAt: -1 } },
{ $skip: skip },
{ $limit: limit },
{ $project: TASK_PROJECT_STAGE }
],
count: [{ $count: 'total' }]
}
}
];
const [result] = await Task.aggregate(pipeline);
const tasks = result.tasks || [];
const numDocs = (result.count && result.count[0] && result.count[0].total) || 0;
return { tasks, numDocs };
};