Skip to content

Commit afee8e6

Browse files
feat: lazy load discord groups page (#2291)
* feat: added pagination for lazy loading in the /groups route to load the discord groups asynchrounously instead of all at once * feat: Implement pagination for lazy loading in - Added feature-flag-based () lazy loading to . - Introduced support for , , and __TEXT __DATA __OBJC others dec hex query parameters for pagination. - Validated query parameters and returned structured pagination metadata (, __TEXT __DATA __OBJC others dec hex, , ). - Updated response structure to include enriched group membership information. - Modified test cases in : - Added tests for cursor-based lazy loading behavior. - Handled scenarios with and without cursors. - Verified error handling for database query failures. * fix: test cases * fix: correct paginated group roles endpoint and dynamic link generation - Fixed incorrect and link generation in the endpoint. - Ensured dynamically includes the correct path (). - Dynamically constructed and links using and . - Refactored function: - Simplified conditional logic for feature flag. - Added error handling for invalid page and size parameters. - Removed hardcoding of URLs in response links to make them adaptive to the environment. - Improved logging for easier debugging when issues arise. - Added handling to ensure old behavior (non-dev mode) works as expected. * chore remove test from here as moving tests in a different PR * fix: failing tests
1 parent 2ac202e commit afee8e6

File tree

4 files changed

+87
-10
lines changed

4 files changed

+87
-10
lines changed

controllers/discordactions.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,26 +119,52 @@ const deleteGroupRole = async (req, res) => {
119119
};
120120

121121
/**
122-
* Gets all group-roles
122+
* Fetches all group roles or provides paginated results when ?dev=true is passed.
123123
*
124-
* @param res {Object} - Express response object
124+
* @param {Object} req - Express request object.
125+
* @param {Object} res - Express response object.
125126
*/
126-
127-
const getAllGroupRoles = async (req, res) => {
127+
const getPaginatedAllGroupRoles = async (req, res) => {
128128
try {
129-
const { groups } = await discordRolesModel.getAllGroupRoles();
129+
const { page = 0, size = 10, dev } = req.query;
130+
const limit = parseInt(size, 10) || 10;
131+
const offset = parseInt(page, 10) * limit;
132+
133+
if (limit < 1 || limit > 100) {
134+
return res.boom.badRequest("Invalid size. Must be between 1 and 100.");
135+
}
136+
130137
const discordId = req.userData?.discordId;
138+
if (dev === "true") {
139+
const { roles, total } = await discordRolesModel.getPaginatedGroupRolesByPage({ offset, limit });
140+
const groupsWithMembershipInfo = await discordRolesModel.enrichGroupDataWithMembershipInfo(discordId, roles);
141+
142+
const nextPage = offset + limit < total ? parseInt(page, 10) + 1 : null;
143+
const prevPage = page > 0 ? parseInt(page, 10) - 1 : null;
144+
145+
const baseUrl = `${req.baseUrl}${req.path}`;
146+
const next = nextPage !== null ? `${baseUrl}?page=${nextPage}&size=${limit}&dev=true` : null;
147+
const prev = prevPage !== null ? `${baseUrl}?page=${prevPage}&size=${limit}&dev=true` : null;
148+
149+
return res.json({
150+
message: "Roles fetched successfully!",
151+
groups: groupsWithMembershipInfo,
152+
links: { next, prev },
153+
});
154+
}
155+
156+
const { groups } = await discordRolesModel.getAllGroupRoles();
131157
const groupsWithMembershipInfo = await discordRolesModel.enrichGroupDataWithMembershipInfo(discordId, groups);
158+
132159
return res.json({
133160
message: "Roles fetched successfully!",
134161
groups: groupsWithMembershipInfo,
135162
});
136163
} catch (err) {
137-
logger.error(`Error while getting roles: ${err}`);
164+
logger.error(`Error while fetching paginated group roles: ${err}`);
138165
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
139166
}
140167
};
141-
142168
const getGroupsRoleId = async (req, res) => {
143169
try {
144170
const { discordId } = req.userData;
@@ -534,7 +560,7 @@ const getUserDiscordInvite = async (req, res) => {
534560
module.exports = {
535561
getGroupsRoleId,
536562
createGroupRole,
537-
getAllGroupRoles,
563+
getPaginatedAllGroupRoles,
538564
addGroupRoleToMember,
539565
deleteRole,
540566
updateDiscordImageForVerification,

middlewares/validators/discordactions.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,31 @@ const validateUpdateUsersNicknameStatusBody = async (req, res, next) => {
3939
res.boom.badRequest(error);
4040
}
4141
};
42+
/**
43+
* Middleware: Validates lazy loading parameters in the request.
44+
*
45+
* @param {Object} req - Express request object.
46+
* @param {Object} res - Express response object.
47+
* @param {Function} next - Express next middleware function.
48+
*/
49+
const validateLazyLoadingParams = async (req, res, next) => {
50+
const schema = Joi.object({
51+
page: Joi.number().integer().min(0).optional(),
52+
size: Joi.number().integer().min(1).max(100).optional(),
53+
dev: Joi.string().valid("true").optional(),
54+
});
55+
56+
try {
57+
req.query = await schema.validateAsync(req.query);
58+
next();
59+
} catch (error) {
60+
res.boom.badRequest(error.message);
61+
}
62+
};
4263

4364
module.exports = {
4465
validateGroupRoleBody,
4566
validateMemberRoleBody,
67+
validateLazyLoadingParams,
4668
validateUpdateUsersNicknameStatusBody,
4769
};

models/discordactions.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,33 @@ const deleteRoleFromDatabase = async (roleId, discordId) => {
102102
return false;
103103
};
104104

105+
/**
106+
* Fetches paginated group roles by page and size.
107+
*
108+
* @param {Object} options - Pagination options
109+
* @param {number} options.offset - Number of items to skip
110+
* @param {number} options.limit - Maximum number of roles to fetch
111+
* @returns {Promise<Object>} - Paginated roles and total count
112+
*/
113+
const getPaginatedGroupRolesByPage = async ({ offset, limit }) => {
114+
try {
115+
const snapshot = await discordRoleModel.orderBy("date", "desc").offset(offset).limit(limit).get();
116+
117+
const roles = snapshot.docs.map((doc) => ({
118+
id: doc.id,
119+
...doc.data(),
120+
}));
121+
122+
const totalSnapshot = await discordRoleModel.get();
123+
const total = totalSnapshot.size;
124+
125+
return { roles, total };
126+
} catch (err) {
127+
logger.error(`Error in getPaginatedGroupRolesByPage: ${err.message}`);
128+
throw new Error("Database error while paginating group roles");
129+
}
130+
};
131+
105132
/**
106133
*
107134
* @param roleData { Object }: Data of the new role
@@ -1085,6 +1112,7 @@ module.exports = {
10851112
createNewRole,
10861113
removeMemberGroup,
10871114
getGroupRolesForUser,
1115+
getPaginatedGroupRolesByPage,
10881116
getAllGroupRoles,
10891117
getGroupRoleByName,
10901118
updateGroupRole,

routes/discordactions.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const authenticate = require("../middlewares/authenticate");
33
const {
44
createGroupRole,
55
getGroupsRoleId,
6-
getAllGroupRoles,
76
addGroupRoleToMember,
87
deleteRole,
98
updateDiscordImageForVerification,
@@ -16,11 +15,13 @@ const {
1615
syncDiscordGroupRolesInFirestore,
1716
setRoleToUsersWith31DaysPlusOnboarding,
1817
deleteGroupRole,
18+
getPaginatedAllGroupRoles,
1919
} = require("../controllers/discordactions");
2020
const {
2121
validateGroupRoleBody,
2222
validateMemberRoleBody,
2323
validateUpdateUsersNicknameStatusBody,
24+
validateLazyLoadingParams,
2425
} = require("../middlewares/validators/discordactions");
2526
const checkIsVerifiedDiscord = require("../middlewares/verifydiscord");
2627
const checkCanGenerateDiscordLink = require("../middlewares/checkCanGenerateDiscordLink");
@@ -33,7 +34,7 @@ const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndSe
3334
const router = express.Router();
3435

3536
router.post("/groups", authenticate, checkIsVerifiedDiscord, validateGroupRoleBody, createGroupRole);
36-
router.get("/groups", authenticate, checkIsVerifiedDiscord, getAllGroupRoles);
37+
router.get("/groups", authenticate, checkIsVerifiedDiscord, validateLazyLoadingParams, getPaginatedAllGroupRoles);
3738
router.delete("/groups/:groupId", authenticate, checkIsVerifiedDiscord, authorizeRoles([SUPERUSER]), deleteGroupRole);
3839
router.post("/roles", authenticate, checkIsVerifiedDiscord, validateMemberRoleBody, addGroupRoleToMember);
3940
router.get("/invite", authenticate, getUserDiscordInvite);

0 commit comments

Comments
 (0)