Skip to content

Commit 558dd2d

Browse files
committed
Merge branch 'develop' of https://github.com/Real-Dev-Squad/website-backend into develop
2 parents f01f146 + 7939b2c commit 558dd2d

File tree

21 files changed

+923
-55
lines changed

21 files changed

+923
-55
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ name: Tests
66
on:
77
pull_request:
88
branches:
9-
- '**'
9+
- "**"
1010

1111
jobs:
1212
build:
13-
1413
runs-on: ubuntu-latest
1514

1615
strategy:
1716
matrix:
18-
node-version: [14.x]
17+
node-version: [18.x]
1918

2019
steps:
2120
- uses: actions/checkout@v3

constants/firebase.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const BATCH_SIZE_IN_CLAUSE = 30; // since only 30 comparision values are allowed in 'in' clause
2+
3+
module.exports = {
4+
BATCH_SIZE_IN_CLAUSE,
5+
};

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, getUsernamesFromPRs } = 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 filteredUsernames = getUsernamesFromPRs(allPRs);
77+
78+
const { filterdUsersWithDetails } = await userQuery.fetchUsers(filteredUsernames);
79+
80+
return res.json({
81+
message: "Users returned successfully!",
82+
users: filterdUsersWithDetails,
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
};

firebase.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
{
22
"emulators": {
33
"firestore": {
4+
"host": "0.0.0.0",
45
"port": 8081
56
},
67
"ui": {
8+
"host": "0.0.0.0",
79
"enabled": true,
810
"port": 4000
911
}

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: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ 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");
11+
const { BATCH_SIZE_IN_CLAUSE } = require("../constants/firebase");
912
const userModel = firestore.collection("users");
1013
const joinModel = firestore.collection("applicants");
1114
const itemModel = firestore.collection("itemTags");
12-
15+
const userStatusModel = firestore.collection("usersStatus");
16+
const { ITEM_TAG, USER_STATE } = ALLOWED_FILTER_PARAMS;
1317
/**
1418
* Adds or updates the user data
1519
*
@@ -126,7 +130,7 @@ const getSuggestedUsers = async (skill) => {
126130
* @param query { search, next, prev, size, page }: Filter for users
127131
* @return {Promise<userModel|Array>}
128132
*/
129-
const fetchUsers = async (query) => {
133+
const fetchPaginatedUsers = async (query) => {
130134
try {
131135
// INFO: default user size set to 100
132136
// INFO: https://github.com/Real-Dev-Squad/website-backend/pull/873#discussion_r1064229932
@@ -175,6 +179,45 @@ const fetchUsers = async (query) => {
175179
}
176180
};
177181

182+
const fetchUsers = async (usernames = []) => {
183+
try {
184+
const dbQuery = userModel;
185+
const filterdUsersWithDetails = [];
186+
187+
const groups = [];
188+
for (let i = 0; i < usernames.length; i += BATCH_SIZE_IN_CLAUSE) {
189+
groups.push(usernames.slice(i, i + BATCH_SIZE_IN_CLAUSE));
190+
}
191+
192+
// For each group, write a separate query
193+
const promises = groups.map((group) => {
194+
return dbQuery.where("github_id", "in", group).get();
195+
});
196+
197+
const snapshots = await Promise.all(promises);
198+
199+
snapshots.forEach((snapshot) => {
200+
snapshot.forEach((doc) => {
201+
filterdUsersWithDetails.push({
202+
id: doc.id,
203+
...doc.data(),
204+
phone: undefined,
205+
email: undefined,
206+
tokens: undefined,
207+
chaincode: undefined,
208+
});
209+
});
210+
});
211+
212+
return {
213+
filterdUsersWithDetails,
214+
};
215+
} catch (err) {
216+
logger.error("Error retrieving user data", err);
217+
throw err;
218+
}
219+
};
220+
178221
/**
179222
* Fetches the user data from the the provided username or userId
180223
*
@@ -292,9 +335,67 @@ const fetchUserSkills = async (id) => {
292335
}
293336
};
294337

338+
/**
339+
* Fetches user data based on the filter query
340+
*
341+
* @param {Object} query - Object with query parameters
342+
* @param {Array} query.levelId - Array of levelIds to filter the users on
343+
* @param {Array} query.levelName - Array of levelNames to filter the users on
344+
* @param {Array} query.levelNumber - Array of levelNumbers to filter the users on
345+
* @param {Array} query.tagId - Array of tagIds to filter the users on
346+
* @param {Array} query.state - Array of states to filter the users on
347+
* @return {Promise<Array>} - Array of user documents that match the filter criteria
348+
*/
349+
350+
const getUsersBasedOnFilter = async (query) => {
351+
const allQueryKeys = Object.keys(query);
352+
const doesTagQueryExist = arraysHaveCommonItem(ITEM_TAG, allQueryKeys);
353+
const doesStateQueryExist = arraysHaveCommonItem(USER_STATE, allQueryKeys);
354+
355+
const calls = {
356+
item: itemModel,
357+
state: userStatusModel,
358+
};
359+
calls.item = calls.item.where("itemType", "==", "USER").where("tagType", "==", "SKILL");
360+
361+
Object.entries(query).forEach(([key, value]) => {
362+
const isTagKey = ITEM_TAG.includes(key);
363+
const isStateKey = USER_STATE.includes(key);
364+
const isValueArray = Array.isArray(value);
365+
366+
if (isTagKey) {
367+
calls.item = isValueArray ? calls.item.where(key, "in", value) : calls.item.where(key, "==", value);
368+
} else if (isStateKey) {
369+
calls.state = isValueArray
370+
? calls.state.where("currentStatus.state", "in", value)
371+
: calls.state.where("currentStatus.state", "==", value);
372+
}
373+
});
374+
375+
const tagItems = doesTagQueryExist ? (await calls.item.get()).docs.map((doc) => ({ id: doc.id, ...doc.data() })) : [];
376+
const stateItems = doesStateQueryExist
377+
? (await calls.state.get()).docs.map((doc) => ({ id: doc.id, ...doc.data() }))
378+
: [];
379+
let finalItems = [];
380+
381+
if (doesTagQueryExist && doesStateQueryExist) {
382+
const stateItemIds = new Set(stateItems.map((item) => item.userId));
383+
finalItems = tagItems.filter((item) => stateItemIds.has(item.itemId)).map((item) => item.itemId);
384+
} else if (doesStateQueryExist) {
385+
finalItems = stateItems.map((item) => item.userId);
386+
} else if (doesTagQueryExist) {
387+
finalItems = tagItems.map((item) => item.itemId);
388+
}
389+
390+
finalItems = [...new Set(finalItems)];
391+
const userRefs = finalItems.map((itemId) => userModel.doc(itemId));
392+
const userDocs = (await firestore.getAll(...userRefs)).map((doc) => ({ id: doc.id, ...doc.data() }));
393+
return userDocs;
394+
};
395+
295396
module.exports = {
296397
addOrUpdate,
297-
fetchUsers,
398+
fetchPaginatedUsers,
298399
fetchUser,
299400
setIncompleteUserDetails,
300401
initializeUser,
@@ -304,4 +405,6 @@ module.exports = {
304405
getJoinData,
305406
getSuggestedUsers,
306407
fetchUserSkills,
408+
fetchUsers,
409+
getUsersBasedOnFilter,
307410
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@
6060
"sinon": "^15.0.0"
6161
},
6262
"engines": {
63-
"node": "14.x"
63+
"node": "18.x"
6464
},
6565
"pre-commit": [
6666
"lint"
6767
],
6868
"volta": {
69-
"node": "14.21.1",
69+
"node": "18.15.0",
7070
"yarn": "1.22.19"
7171
}
7272
}

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)