Skip to content

Commit 4aaa98b

Browse files
Merge pull request #978 from Real-Dev-Squad/feat/filter-users-based-on-state-and-skills
Feature - Filter Users based on their state (OOO,ACTIVE,IDLE) and skills
2 parents 1c45b33 + c2425a5 commit 4aaa98b

File tree

6 files changed

+147
-2
lines changed

6 files changed

+147
-2
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/users.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,31 @@ const addDefaultArchivedRole = async (req, res) => {
419419
}
420420
};
421421

422+
/**
423+
* Returns the lists of users who match the specified query params
424+
*
425+
* @param req {Object} - Express request object
426+
* @param res {Object} - Express response object
427+
*/
428+
429+
const filterUsers = async (req, res) => {
430+
try {
431+
if (!Object.keys(req.query).length) {
432+
return res.boom.badRequest("filter for item not provided");
433+
}
434+
const allUsers = await userQuery.getUsersBasedOnFilter(req.query);
435+
436+
return res.json({
437+
message: "Users found successfully!",
438+
users: allUsers,
439+
count: allUsers.length,
440+
});
441+
} catch (error) {
442+
logger.error(`Error while fetching all users: ${error}`);
443+
return res.boom.serverUnavailable("Something went wrong please contact admin");
444+
}
445+
};
446+
422447
module.exports = {
423448
verifyUser,
424449
generateChaincode,
@@ -437,4 +462,5 @@ module.exports = {
437462
getUserIntro,
438463
addDefaultArchivedRole,
439464
getUserSkills,
465+
filterUsers,
440466
};

middlewares/validators/user.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,45 @@ async function getUsers(req, res, next) {
147147
}
148148
}
149149

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

models/users.js

Lines changed: 63 additions & 1 deletion
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
*
@@ -292,6 +295,64 @@ const fetchUserSkills = async (id) => {
292295
}
293296
};
294297

298+
/**
299+
* Fetches user data based on the filter query
300+
*
301+
* @param {Object} query - Object with query parameters
302+
* @param {Array} query.levelId - Array of levelIds to filter the users on
303+
* @param {Array} query.levelName - Array of levelNames to filter the users on
304+
* @param {Array} query.levelNumber - Array of levelNumbers to filter the users on
305+
* @param {Array} query.tagId - Array of tagIds to filter the users on
306+
* @param {Array} query.state - Array of states to filter the users on
307+
* @return {Promise<Array>} - Array of user documents that match the filter criteria
308+
*/
309+
310+
const getUsersBasedOnFilter = async (query) => {
311+
const allQueryKeys = Object.keys(query);
312+
const doesTagQueryExist = arraysHaveCommonItem(ITEM_TAG, allQueryKeys);
313+
const doesStateQueryExist = arraysHaveCommonItem(USER_STATE, allQueryKeys);
314+
315+
const calls = {
316+
item: itemModel,
317+
state: userStatusModel,
318+
};
319+
calls.item = calls.item.where("itemType", "==", "USER").where("tagType", "==", "SKILL");
320+
321+
Object.entries(query).forEach(([key, value]) => {
322+
const isTagKey = ITEM_TAG.includes(key);
323+
const isStateKey = USER_STATE.includes(key);
324+
const isValueArray = Array.isArray(value);
325+
326+
if (isTagKey) {
327+
calls.item = isValueArray ? calls.item.where(key, "in", value) : calls.item.where(key, "==", value);
328+
} else if (isStateKey) {
329+
calls.state = isValueArray
330+
? calls.state.where("currentStatus.state", "in", value)
331+
: calls.state.where("currentStatus.state", "==", value);
332+
}
333+
});
334+
335+
const tagItems = doesTagQueryExist ? (await calls.item.get()).docs.map((doc) => ({ id: doc.id, ...doc.data() })) : [];
336+
const stateItems = doesStateQueryExist
337+
? (await calls.state.get()).docs.map((doc) => ({ id: doc.id, ...doc.data() }))
338+
: [];
339+
let finalItems = [];
340+
341+
if (doesTagQueryExist && doesStateQueryExist) {
342+
const stateItemIds = new Set(stateItems.map((item) => item.userId));
343+
finalItems = tagItems.filter((item) => stateItemIds.has(item.itemId)).map((item) => item.itemId);
344+
} else if (doesStateQueryExist) {
345+
finalItems = stateItems.map((item) => item.userId);
346+
} else if (doesTagQueryExist) {
347+
finalItems = tagItems.map((item) => item.itemId);
348+
}
349+
350+
finalItems = [...new Set(finalItems)];
351+
const userRefs = finalItems.map((itemId) => userModel.doc(itemId));
352+
const userDocs = (await firestore.getAll(...userRefs)).map((doc) => ({ id: doc.id, ...doc.data() }));
353+
return userDocs;
354+
};
355+
295356
module.exports = {
296357
addOrUpdate,
297358
fetchUsers,
@@ -304,4 +365,5 @@ module.exports = {
304365
getJoinData,
305366
getSuggestedUsers,
306367
fetchUserSkills,
368+
getUsersBasedOnFilter,
307369
};

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);

utils/array.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ function chunks(array, size = 1) {
1818
return result;
1919
}
2020

21+
/**
22+
* Checks if two arrays have any common items
23+
* @param array1 {Array<string, number>} - first array
24+
* @param array2 {Array<string, number>} - second array
25+
* @returns {boolean} - true if the arrays have at least one common item, false otherwise
26+
*/
27+
28+
function arraysHaveCommonItem(array1, array2) {
29+
if (!array1?.length || !array2?.length) {
30+
return false;
31+
}
32+
return array1.some((value) => array2.includes(value));
33+
}
34+
2135
module.exports = {
2236
chunks,
37+
arraysHaveCommonItem,
2338
};

0 commit comments

Comments
 (0)