Skip to content

Commit 8c56c91

Browse files
Adds query parser and middleware for get task requests (#1677)
* feat : adds rql query parser and tests * feat : adds middleware for get task requests * chore : fix lint issue * chore : updates sinon import * chore : updates name of schema * refactor: moves types to a separate file * Adds model function for pagination, filters and sorting in task requests (#1678) * feat : adds model and util function for paginated task requests * chore : updates sinon import * chore : fixes lint issues * chore : fixes the test name * refactor: changes utils function to typescript and adds types for task request and user
1 parent dbdfb8e commit 8c56c91

File tree

15 files changed

+802
-24
lines changed

15 files changed

+802
-24
lines changed

constants/taskRequests.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,45 @@ const TASK_REQUEST_STATUS = {
44
PENDING: "PENDING",
55
DENIED: "DENIED",
66
};
7-
7+
const TASK_REQUEST_ERROR_MESSAGE = {
8+
INVALID_PREV: "Invalid 'prev' value",
9+
INVALID_NEXT: "Invalid 'next' value",
10+
};
811
const TASK_REQUEST_TYPE = {
912
ASSIGNMENT: "ASSIGNMENT",
1013
CREATION: "CREATION",
1114
};
12-
15+
const TASK_REQUEST_FILTER_KEYS = {
16+
status: "status",
17+
"request-type": "requestType",
18+
};
19+
const TASK_REQUEST_FILTER_VALUES = {
20+
pending: "PENDING",
21+
approved: "APPROVED",
22+
denied: "DENIED",
23+
assignment: "ASSIGNMENT",
24+
creation: "CREATION",
25+
};
26+
const TASK_REQUEST_SORT_KEYS = {
27+
created: "createdAt",
28+
requestors: "usersCount",
29+
};
30+
const TASK_REQUEST_SORT_VALUES = {
31+
asc: "asc",
32+
desc: "desc",
33+
};
1334
const MIGRATION_TYPE = {
1435
ADD_NEW_FIELDS: "add-new-fields",
1536
REMOVE_OLD_FIELDS: "remove-redundant-fields",
1637
};
17-
module.exports = { TASK_REQUEST_STATUS, TASK_REQUEST_TYPE, MIGRATION_TYPE };
38+
39+
module.exports = {
40+
TASK_REQUEST_STATUS,
41+
TASK_REQUEST_TYPE,
42+
TASK_REQUEST_FILTER_KEYS,
43+
TASK_REQUEST_FILTER_VALUES,
44+
TASK_REQUEST_SORT_KEYS,
45+
TASK_REQUEST_ERROR_MESSAGE,
46+
TASK_REQUEST_SORT_VALUES,
47+
MIGRATION_TYPE
48+
};

controllers/tasksRequests.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ const usersUtils = require("../utils/users");
1010
const fetchTaskRequests = async (_, res) => {
1111
try {
1212
const { dev } = _.query;
13-
const data = await taskRequestsModel.fetchTaskRequests(dev === "true");
14-
15-
if (data.length > 0) {
16-
return res.status(200).json({
17-
message: "Task requests returned successfully",
18-
data,
19-
});
13+
let data;
14+
if (dev === "true") {
15+
data = await taskRequestsModel.fetchPaginatedTaskRequests(_.query);
16+
if (data.error) {
17+
return res.status(data.statusCode).json(data);
18+
}
19+
} else {
20+
data = {};
21+
data.data = await taskRequestsModel.fetchTaskRequests(true);
2022
}
2123

22-
return res.status(404).json({
23-
message: "Task requests not found",
24-
data,
24+
return res.status(200).json({
25+
message: "Task requests returned successfully",
26+
...data,
2527
});
2628
} catch (err) {
2729
logger.error("Error while fetching task requests", err);

middlewares/validators/task-requests.js

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const joi = require("joi");
2-
const { TASK_REQUEST_TYPE } = require("../../constants/taskRequests");
2+
const { RQLQueryParser } = require("../../utils/RQLParser");
33
const githubOrg = config.get("githubApi.org");
44
const githubBaseUrl = config.get("githubApi.baseUrl");
55
const githubIssuerUrlPattern = new RegExp(`^${githubBaseUrl}/repos/${githubOrg}/.+/issues/\\d+$`);
6+
const { TASK_REQUEST_STATUS, TASK_REQUEST_TYPE } = require("../../constants/taskRequests");
67

78
const postTaskRequests = async (req, res, next) => {
89
const taskAssignmentSchema = joi
@@ -40,6 +41,61 @@ const postTaskRequests = async (req, res, next) => {
4041
}
4142
};
4243

44+
const getTaskRequests = async (req, res, next) => {
45+
const queryParamsSchema = joi
46+
.object()
47+
.keys({
48+
dev: joi.bool().optional().sensitive(),
49+
prev: joi.string().optional(),
50+
next: joi.string().optional(),
51+
size: joi.number().integer().positive().min(1).max(100).optional(),
52+
q: joi.string().optional(),
53+
})
54+
.without("prev", "next")
55+
.with("prev", "size")
56+
.with("next", "size");
57+
58+
const filtersSchema = joi.object().keys({
59+
status: joi
60+
.array()
61+
.items(
62+
joi.object().keys({
63+
value: joi.string().valid(...Object.values(TASK_REQUEST_STATUS).map((value) => value.toLowerCase())),
64+
operator: joi.string().optional(),
65+
})
66+
)
67+
.optional(),
68+
"request-type": joi
69+
.array()
70+
.items(
71+
joi.object().keys({
72+
value: joi.string().valid(...Object.values(TASK_REQUEST_TYPE).map((value) => value.toLowerCase())),
73+
operator: joi.string().optional(),
74+
})
75+
)
76+
.optional(),
77+
});
78+
79+
const sortSchema = joi.object().keys({
80+
created: joi.string().valid("asc", "desc").optional(),
81+
requestors: joi.string().valid("asc", "desc").optional(),
82+
});
83+
try {
84+
const { q: queryString } = req.query;
85+
const rqlQueryParser = new RQLQueryParser(queryString);
86+
87+
await Promise.all([
88+
filtersSchema.validateAsync(rqlQueryParser.getFilterQueries()),
89+
sortSchema.validateAsync(rqlQueryParser.getSortQueries()),
90+
queryParamsSchema.validateAsync(req.query),
91+
]);
92+
next();
93+
} catch (error) {
94+
logger.error(`Error validating get task requests payload : ${error}`);
95+
res.boom.badRequest(error?.details?.[0]?.message || error?.message);
96+
}
97+
};
4398
module.exports = {
99+
getTaskRequests,
44100
postTaskRequests,
45101
};

models/taskRequests.js

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
const { TASK_REQUEST_STATUS, TASK_REQUEST_TYPE } = require("../constants/taskRequests");
1+
const {
2+
TASK_REQUEST_STATUS,
3+
TASK_REQUEST_TYPE,
4+
TASK_REQUEST_FILTER_KEYS,
5+
TASK_REQUEST_FILTER_VALUES,
6+
TASK_REQUEST_SORT_KEYS,
7+
TASK_REQUEST_SORT_VALUES,
8+
TASK_REQUEST_ERROR_MESSAGE,
9+
} = require("../constants/taskRequests");
210
const { TASK_TYPE, TASK_STATUS, DEFAULT_TASK_PRIORITY } = require("../constants/tasks");
11+
const { Operators } = require("../typeDefinitions/rqlParser");
12+
const { RQLQueryParser } = require("../utils/RQLParser");
313
const firestore = require("../utils/firestore");
14+
const { buildTaskRequests, generateLink, transformTaskRequests } = require("../utils/task-requests");
415
const taskRequestsCollection = firestore.collection("taskRequests");
516
const tasksModel = require("./tasks");
617
const userModel = require("./users");
@@ -62,6 +73,92 @@ const fetchTaskRequests = async (dev) => {
6273
return taskRequests;
6374
};
6475

76+
const fetchPaginatedTaskRequests = async (queries = {}) => {
77+
try {
78+
let taskRequestsSnapshot = taskRequestsCollection;
79+
80+
let { next, prev, size, q: queryString } = queries;
81+
if (size) size = parseInt(size);
82+
83+
const rqlQueryParser = new RQLQueryParser(queryString);
84+
85+
Object.entries(rqlQueryParser.getFilterQueries()).forEach(([key, value]) => {
86+
const valuesList = value.map(
87+
(query) => query.operator === Operators.INCLUDE && TASK_REQUEST_FILTER_VALUES[query.value]
88+
);
89+
taskRequestsSnapshot = taskRequestsSnapshot.where(TASK_REQUEST_FILTER_KEYS[key], "in", valuesList);
90+
});
91+
92+
const sortQueries = rqlQueryParser.getSortQueries();
93+
const sortQueryEntries = Object.entries(sortQueries);
94+
95+
if (sortQueryEntries.length) {
96+
sortQueryEntries.forEach(([key, value]) => {
97+
taskRequestsSnapshot = taskRequestsSnapshot.orderBy(
98+
TASK_REQUEST_SORT_KEYS[key],
99+
TASK_REQUEST_SORT_VALUES[value]
100+
);
101+
});
102+
} else {
103+
taskRequestsSnapshot = taskRequestsSnapshot.orderBy(TASK_REQUEST_SORT_KEYS.created, "desc");
104+
}
105+
106+
if (next) {
107+
const data = await taskRequestsCollection.doc(next).get();
108+
if (!data.data()) {
109+
return {
110+
statusCode: 400,
111+
error: "Bad Request",
112+
message: `${TASK_REQUEST_ERROR_MESSAGE.INVALID_NEXT}: ${next}`,
113+
};
114+
}
115+
taskRequestsSnapshot = taskRequestsSnapshot.startAfter(data).limit(size);
116+
} else if (prev) {
117+
const data = await taskRequestsCollection.doc(prev).get();
118+
if (!data.data()) {
119+
return {
120+
statusCode: 400,
121+
error: "Bad Request",
122+
message: `${TASK_REQUEST_ERROR_MESSAGE.INVALID_PREV}: ${prev}`,
123+
};
124+
}
125+
taskRequestsSnapshot = taskRequestsSnapshot.endBefore(data).limitToLast(size);
126+
} else if (size) {
127+
taskRequestsSnapshot = taskRequestsSnapshot.limit(size);
128+
}
129+
130+
taskRequestsSnapshot = await taskRequestsSnapshot.get();
131+
const taskRequestsList = buildTaskRequests(taskRequestsSnapshot);
132+
await transformTaskRequests(taskRequestsList);
133+
134+
const resultDataLength = taskRequestsSnapshot.docs.length;
135+
const isNextLinkRequired = size && resultDataLength === size;
136+
const lastVisibleDoc = isNextLinkRequired && taskRequestsSnapshot.docs[resultDataLength - 1];
137+
const firstDoc = taskRequestsSnapshot.docs[0];
138+
const nextPageParams = {
139+
...queries,
140+
next: lastVisibleDoc?.id,
141+
};
142+
delete nextPageParams.prev;
143+
const prevPageParams = {
144+
...queries,
145+
prev: firstDoc?.id,
146+
};
147+
delete prevPageParams.next;
148+
const nextLink = lastVisibleDoc ? generateLink(nextPageParams) : "";
149+
const prevLink = next || prev ? generateLink(prevPageParams) : "";
150+
151+
return {
152+
data: taskRequestsList,
153+
next: nextLink,
154+
prev: prevLink,
155+
};
156+
} catch (err) {
157+
logger.error("error getting task requests", err);
158+
throw err;
159+
}
160+
};
161+
65162
/**
66163
* Fetches task request by id
67164
*
@@ -406,6 +503,7 @@ module.exports = {
406503
fetchTaskRequestById,
407504
addOrUpdate,
408505
approveTaskRequest,
506+
fetchPaginatedTaskRequests,
409507
addNewFields,
410508
removeOldField,
411509
};

routes/taskRequests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const taskRequests = require("../controllers/tasksRequests");
77
const { validateUser } = require("../middlewares/taskRequests");
88
const validators = require("../middlewares/validators/task-requests");
99

10-
router.get("/", authenticate, authorizeRoles([SUPERUSER]), taskRequests.fetchTaskRequests);
10+
router.get("/", authenticate, authorizeRoles([SUPERUSER]), validators.getTaskRequests, taskRequests.fetchTaskRequests);
1111
router.get("/:id", authenticate, authorizeRoles([SUPERUSER]), taskRequests.fetchTaskRequestById);
1212
router.post("/addOrUpdate", authenticate, validateUser, taskRequests.addOrUpdate);
1313
router.patch("/approve", authenticate, authorizeRoles([SUPERUSER]), validateUser, taskRequests.approveTaskRequest);

services/dataAccessLayer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ const levelSpecificAccess = (user, level = ACCESS_LEVEL.PUBLIC, role = null) =>
109109

110110
/**
111111
* Fetch users based on document key and value
112-
* @param documentKey {String} - Model field path.
112+
* @param documentKey {string | FieldPath} - Model field path.
113113
* @param value {String | Array} - Single field value or list of values to be matched.
114114
*/
115115
const fetchUsersForKeyValues = async (documentKey, value, removeSensitiveInfo = true) => {

test/integration/taskRequests.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe("Task Requests", function () {
6969
await userStatusModel.updateUserStatus(userId, idleUserStatus);
7070
});
7171

72-
it("should fetch 404 when taskRequests are empty", function (done) {
72+
it("should have status 200 when taskRequests are empty", function (done) {
7373
chai
7474
.request(app)
7575
.get("/taskRequests")
@@ -79,7 +79,7 @@ describe("Task Requests", function () {
7979
return done(err);
8080
}
8181

82-
expect(res).to.have.status(404);
82+
expect(res).to.have.status(200);
8383
return done();
8484
});
8585
});

0 commit comments

Comments
 (0)