Skip to content

Commit 3d868d9

Browse files
committed
Merge branch 'develop' of https://github.com/Real-Dev-Squad/website-backend into feat/cpllapsed-task
2 parents 7e485f6 + b95dd3a commit 3d868d9

File tree

17 files changed

+901
-47
lines changed

17 files changed

+901
-47
lines changed

constants/users.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,9 @@ const USER_STATUS = {
1010
ACTIVE: "active",
1111
};
1212

13-
module.exports = { profileStatus, USER_STATUS };
13+
const ALLOWED_FILTER_PARAMS = {
14+
ITEM_TAG: ["levelId", "levelName", "levelValue", "tagId"],
15+
USER_STATE: ["state"],
16+
};
17+
18+
module.exports = { profileStatus, USER_STATUS, ALLOWED_FILTER_PARAMS };

controllers/pullRequests.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const githubService = require("../services/githubService");
22
const { SOMETHING_WENT_WRONG } = require("../constants/errorMessages");
3+
const { ORDER_TYPE } = require("../utils/pullRequests");
34

45
/**
56
* Collects all pull requests and sends only required data for each pull request
@@ -38,8 +39,9 @@ const getUserPRs = async (req, res) => {
3839
*/
3940
const getStalePRs = async (req, res) => {
4041
try {
42+
const order = ORDER_TYPE.ASC;
4143
const { size, page } = req.query;
42-
const { data } = await githubService.fetchStalePRs(size, page);
44+
const { data } = await githubService.fetchOpenPRs({ perPage: size, page, resultOptions: { order } });
4345

4446
if (data.total_count) {
4547
const allPRs = githubService.extractPRdetails(data);
@@ -67,8 +69,9 @@ const getStalePRs = async (req, res) => {
6769
*/
6870
const getOpenPRs = async (req, res) => {
6971
try {
72+
const order = ORDER_TYPE.DESC;
7073
const { size, page } = req.query;
71-
const { data } = await githubService.fetchOpenPRs(size, page);
74+
const { data } = await githubService.fetchOpenPRs({ perPage: size, page, resultOptions: { order } });
7275

7376
if (data.total_count) {
7477
const allPRs = githubService.extractPRdetails(data);

controllers/users.js

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ const { logType } = require("../constants/logs");
88
const { fetch } = require("../utils/fetch");
99
const logger = require("../utils/logger");
1010
const obfuscate = require("../utils/obfuscate");
11-
const { getPaginationLink } = require("../utils/users");
11+
const { getPaginationLink, getFilteredUsers } = require("../utils/users");
12+
const { getQualifiers } = require("../utils/helper");
1213
const { SOMETHING_WENT_WRONG, INTERNAL_SERVER_ERROR } = require("../constants/errorMessages");
14+
const { getFilteredPRsOrIssues } = require("../utils/pullRequests");
1315

1416
const verifyUser = async (req, res) => {
1517
const userId = req.userData.id;
@@ -65,7 +67,23 @@ const getUserById = async (req, res) => {
6567

6668
const getUsers = async (req, res) => {
6769
try {
68-
const { allUsers, nextId, prevId } = await userQuery.fetchUsers(req.query);
70+
const query = req.query?.query ?? "";
71+
const qualifiers = getQualifiers(query);
72+
73+
if (qualifiers?.filterBy) {
74+
const allPRs = await getFilteredPRsOrIssues(qualifiers);
75+
76+
const { allUsers } = await userQuery.fetchAllUsers();
77+
78+
const filteredUsers = getFilteredUsers(allPRs, allUsers);
79+
80+
return res.json({
81+
message: "Users returned successfully!",
82+
users: filteredUsers,
83+
});
84+
}
85+
86+
const { allUsers, nextId, prevId } = await userQuery.fetchPaginatedUsers(req.query);
6987

7088
return res.json({
7189
message: "Users returned successfully!",
@@ -419,6 +437,31 @@ const addDefaultArchivedRole = async (req, res) => {
419437
}
420438
};
421439

440+
/**
441+
* Returns the lists of users who match the specified query params
442+
*
443+
* @param req {Object} - Express request object
444+
* @param res {Object} - Express response object
445+
*/
446+
447+
const filterUsers = async (req, res) => {
448+
try {
449+
if (!Object.keys(req.query).length) {
450+
return res.boom.badRequest("filter for item not provided");
451+
}
452+
const allUsers = await userQuery.getUsersBasedOnFilter(req.query);
453+
454+
return res.json({
455+
message: "Users found successfully!",
456+
users: allUsers,
457+
count: allUsers.length,
458+
});
459+
} catch (error) {
460+
logger.error(`Error while fetching all users: ${error}`);
461+
return res.boom.serverUnavailable("Something went wrong please contact admin");
462+
}
463+
};
464+
422465
module.exports = {
423466
verifyUser,
424467
generateChaincode,
@@ -437,4 +480,5 @@ module.exports = {
437480
getUserIntro,
438481
addDefaultArchivedRole,
439482
getUserSkills,
483+
filterUsers,
440484
};

middlewares/validators/user.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ async function getUsers(req, res, next) {
137137
.messages({
138138
"string.empty": "prev value cannot be empty",
139139
}),
140+
query: joi.string().optional(),
140141
});
141142
try {
142143
await schema.validateAsync(req.query);
@@ -147,9 +148,45 @@ async function getUsers(req, res, next) {
147148
}
148149
}
149150

151+
/**
152+
* Validator function for query params for the filter route
153+
*
154+
* @param req {Object} - Express request object
155+
* @param res {Object} - Express response object
156+
* @param next {Object} - Express middleware function
157+
*/
158+
async function validateUserQueryParams(req, res, next) {
159+
const schema = joi
160+
.object()
161+
.strict()
162+
.min(1)
163+
.keys({
164+
levelId: joi.array().items(joi.string()).single().optional(),
165+
levelName: joi.array().items(joi.string()).single().optional(),
166+
levelValue: joi.array().items(joi.number()).single().optional(),
167+
tagId: joi.array().items(joi.string()).single().optional(),
168+
state: joi
169+
.alternatives()
170+
.try(
171+
joi.string().valid("IDLE", "OOO", "ACTIVE"),
172+
joi.array().items(joi.string().valid("IDLE", "OOO", "ACTIVE"))
173+
)
174+
.optional(),
175+
});
176+
177+
try {
178+
await schema.validateAsync(req.query);
179+
next();
180+
} catch (error) {
181+
logger.error(`Error validating query params : ${error}`);
182+
res.boom.badRequest(error.details[0].message);
183+
}
184+
}
185+
150186
module.exports = {
151187
updateUser,
152188
updateProfileURL,
153189
validateJoinData,
154190
getUsers,
191+
validateUserQueryParams,
155192
};

models/users.js

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ const walletConstants = require("../constants/wallets");
66

77
const firestore = require("../utils/firestore");
88
const { fetchWallet, createWallet } = require("../models/wallets");
9+
const { arraysHaveCommonItem } = require("../utils/array");
10+
const { ALLOWED_FILTER_PARAMS } = require("../constants/users");
911
const userModel = firestore.collection("users");
1012
const joinModel = firestore.collection("applicants");
1113
const itemModel = firestore.collection("itemTags");
12-
14+
const userStatusModel = firestore.collection("usersStatus");
15+
const { ITEM_TAG, USER_STATE } = ALLOWED_FILTER_PARAMS;
1316
/**
1417
* Adds or updates the user data
1518
*
@@ -126,7 +129,7 @@ const getSuggestedUsers = async (skill) => {
126129
* @param query { search, next, prev, size, page }: Filter for users
127130
* @return {Promise<userModel|Array>}
128131
*/
129-
const fetchUsers = async (query) => {
132+
const fetchPaginatedUsers = async (query) => {
130133
try {
131134
// INFO: default user size set to 100
132135
// INFO: https://github.com/Real-Dev-Squad/website-backend/pull/873#discussion_r1064229932
@@ -175,6 +178,33 @@ const fetchUsers = async (query) => {
175178
}
176179
};
177180

181+
const fetchAllUsers = async () => {
182+
try {
183+
const dbQuery = userModel;
184+
185+
const snapshot = await dbQuery.get();
186+
187+
const allUsers = [];
188+
189+
snapshot.forEach((doc) => {
190+
allUsers.push({
191+
id: doc.id,
192+
...doc.data(),
193+
phone: undefined,
194+
email: undefined,
195+
tokens: undefined,
196+
chaincode: undefined,
197+
});
198+
});
199+
return {
200+
allUsers,
201+
};
202+
} catch (err) {
203+
logger.error("Error retrieving user data", err);
204+
throw err;
205+
}
206+
};
207+
178208
/**
179209
* Fetches the user data from the the provided username or userId
180210
*
@@ -292,9 +322,67 @@ const fetchUserSkills = async (id) => {
292322
}
293323
};
294324

325+
/**
326+
* Fetches user data based on the filter query
327+
*
328+
* @param {Object} query - Object with query parameters
329+
* @param {Array} query.levelId - Array of levelIds to filter the users on
330+
* @param {Array} query.levelName - Array of levelNames to filter the users on
331+
* @param {Array} query.levelNumber - Array of levelNumbers to filter the users on
332+
* @param {Array} query.tagId - Array of tagIds to filter the users on
333+
* @param {Array} query.state - Array of states to filter the users on
334+
* @return {Promise<Array>} - Array of user documents that match the filter criteria
335+
*/
336+
337+
const getUsersBasedOnFilter = async (query) => {
338+
const allQueryKeys = Object.keys(query);
339+
const doesTagQueryExist = arraysHaveCommonItem(ITEM_TAG, allQueryKeys);
340+
const doesStateQueryExist = arraysHaveCommonItem(USER_STATE, allQueryKeys);
341+
342+
const calls = {
343+
item: itemModel,
344+
state: userStatusModel,
345+
};
346+
calls.item = calls.item.where("itemType", "==", "USER").where("tagType", "==", "SKILL");
347+
348+
Object.entries(query).forEach(([key, value]) => {
349+
const isTagKey = ITEM_TAG.includes(key);
350+
const isStateKey = USER_STATE.includes(key);
351+
const isValueArray = Array.isArray(value);
352+
353+
if (isTagKey) {
354+
calls.item = isValueArray ? calls.item.where(key, "in", value) : calls.item.where(key, "==", value);
355+
} else if (isStateKey) {
356+
calls.state = isValueArray
357+
? calls.state.where("currentStatus.state", "in", value)
358+
: calls.state.where("currentStatus.state", "==", value);
359+
}
360+
});
361+
362+
const tagItems = doesTagQueryExist ? (await calls.item.get()).docs.map((doc) => ({ id: doc.id, ...doc.data() })) : [];
363+
const stateItems = doesStateQueryExist
364+
? (await calls.state.get()).docs.map((doc) => ({ id: doc.id, ...doc.data() }))
365+
: [];
366+
let finalItems = [];
367+
368+
if (doesTagQueryExist && doesStateQueryExist) {
369+
const stateItemIds = new Set(stateItems.map((item) => item.userId));
370+
finalItems = tagItems.filter((item) => stateItemIds.has(item.itemId)).map((item) => item.itemId);
371+
} else if (doesStateQueryExist) {
372+
finalItems = stateItems.map((item) => item.userId);
373+
} else if (doesTagQueryExist) {
374+
finalItems = tagItems.map((item) => item.itemId);
375+
}
376+
377+
finalItems = [...new Set(finalItems)];
378+
const userRefs = finalItems.map((itemId) => userModel.doc(itemId));
379+
const userDocs = (await firestore.getAll(...userRefs)).map((doc) => ({ id: doc.id, ...doc.data() }));
380+
return userDocs;
381+
};
382+
295383
module.exports = {
296384
addOrUpdate,
297-
fetchUsers,
385+
fetchPaginatedUsers,
298386
fetchUser,
299387
setIncompleteUserDetails,
300388
initializeUser,
@@ -304,4 +392,6 @@ module.exports = {
304392
getJoinData,
305393
getSuggestedUsers,
306394
fetchUserSkills,
395+
fetchAllUsers,
396+
getUsersBasedOnFilter,
307397
};

routes/users.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ router.get("/", authenticate, userValidator.getUsers, users.getUsers);
1515
router.get("/self", authenticate, users.getSelfDetails);
1616
router.get("/isUsernameAvailable/:username", authenticate, users.getUsernameAvailabilty);
1717
router.get("/chaincode", authenticate, users.generateChaincode);
18+
router.get("/search", userValidator.validateUserQueryParams, users.filterUsers);
1819
router.get("/:username", users.getUser);
1920
router.get("/:userId/intro", authenticate, authorizeRoles([SUPERUSER]), users.getUserIntro);
2021
router.put("/self/intro", authenticate, userValidator.validateJoinData, users.addUserIntro);

0 commit comments

Comments
 (0)