From 44328adf09324623e9a418e58596384a0c3f50bd Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Sat, 19 Oct 2024 02:29:03 +0530 Subject: [PATCH 01/11] feat: add purpose field in generate discord invite for user with feature flag --- controllers/discordactions.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index fdca8f83b..049246b00 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -409,8 +409,9 @@ const setRoleToUsersWith31DaysPlusOnboarding = async (req, res) => { const generateInviteForUser = async (req, res) => { try { - const { userId } = req.query; + const { userId, dev } = req.query; const userIdForInvite = userId || req.userData.id; + let inviteLink = ""; const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite); @@ -437,14 +438,25 @@ const generateInviteForUser = async (req, res) => { const discordInviteResponse = await response.json(); const inviteCode = discordInviteResponse.data.code; - const inviteLink = `discord.gg/${inviteCode}`; + inviteLink = `discord.gg/${inviteCode}`; - await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink }); + if (dev) { + const purpose = req.body.purpose; + await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink, purpose }); - return res.status(201).json({ - message: "invite generated successfully", - inviteLink, - }); + return res.status(201).json({ + message: "invite generated successfully", + inviteLink, + purpose, + }); + } else { + await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink }); + + return res.status(201).json({ + message: "invite generated successfully", + inviteLink, + }); + } } catch (err) { logger.error(`Error in generating invite for user: ${err}`); return res.boom.badImplementation(INTERNAL_SERVER_ERROR); From ddfc54851cc8f14d81db09a90c799e91601233a2 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Wed, 23 Oct 2024 00:51:20 +0530 Subject: [PATCH 02/11] feat: add functionality to be able to create n times discord invites by superuser --- controllers/discordactions.js | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index 049246b00..e9ea7ca64 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -8,6 +8,7 @@ const { fetchAllUsers, fetchUser } = require("../models/users"); const { generateCloudFlareHeaders } = require("../utils/discord-actions"); const discordDeveloperRoleId = config.get("discordDeveloperRoleId"); const discordMavenRoleId = config.get("discordMavenRoleId"); +const crypto = require("crypto"); const { setUserDiscordNickname, getDiscordMembers } = discordServices; @@ -407,18 +408,30 @@ const setRoleToUsersWith31DaysPlusOnboarding = async (req, res) => { } }; +const generateRandomId = () => { + return crypto.randomBytes(16).toString("hex"); +}; + const generateInviteForUser = async (req, res) => { try { const { userId, dev } = req.query; const userIdForInvite = userId || req.userData.id; let inviteLink = ""; + let randomId = ""; - const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite); - - if (!modelResponse.notFound) { - return res.status(409).json({ - message: "User invite is already present!", - }); + if (!dev) { + const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite); + if (!modelResponse.notFound) { + return res.status(409).json({ + message: "User invite is already present!", + }); + } + } else { + const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite); + if (!modelResponse.notFound) { + // Generate a random ID + randomId = generateRandomId(); + } } const channelId = config.get("discordNewComersChannelId"); @@ -442,12 +455,13 @@ const generateInviteForUser = async (req, res) => { if (dev) { const purpose = req.body.purpose; - await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink, purpose }); + await discordRolesModel.addInviteToInviteModel({ userId: randomId || userIdForInvite, inviteLink, purpose }); return res.status(201).json({ message: "invite generated successfully", inviteLink, purpose, + userId, }); } else { await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink }); @@ -455,6 +469,7 @@ const generateInviteForUser = async (req, res) => { return res.status(201).json({ message: "invite generated successfully", inviteLink, + userId, }); } } catch (err) { From becf40e15ee5234a1d435321e5bf14557e4668f9 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Sat, 26 Oct 2024 02:19:32 +0530 Subject: [PATCH 03/11] feat: add unit tests for generateInviteForUser --- test/unit/models/discordactions.test.js | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/unit/models/discordactions.test.js b/test/unit/models/discordactions.test.js index 5b773b13e..82f2e8204 100644 --- a/test/unit/models/discordactions.test.js +++ b/test/unit/models/discordactions.test.js @@ -1,3 +1,4 @@ +import { generateDiscordInviteLink } from "../../../utils/discord-actions"; const chai = require("chai"); const expect = chai.expect; const sinon = require("sinon"); @@ -802,6 +803,54 @@ describe("discordactions", function () { }); }); + describe("generateInviteForUser", function () { + let fetchStub; + + beforeEach(async function () { + fetchStub = sinon.stub(global, "fetch"); + }); + + afterEach(function () { + fetchStub.restore(); + }); + + it("should return the invite link and purpose", async function () { + const inviteLink = "discord.gg/xyz"; + const discordInviteLink = { + data: { + code: "xyz", + }, + }; + fetchStub.resolves({ + ok: true, + json: () => discordInviteLink, + }); + + const result = await generateDiscordInviteLink(); + expect(result).to.be.equal(inviteLink); + + const inviteObject = { userId: "123456", inviteLink: "discord.gg/xyz", purpose: "testing" }; + await addInviteToInviteModel(inviteObject); + + const invite = await getUserDiscordInvite("123456"); + expect(invite).to.have.property("id"); + expect(invite.notFound).to.be.equal(false); + expect(invite.userId).to.be.equal("123456"); + expect(invite.inviteLink).to.be.equal("discord.gg/xyz"); + expect(invite.purpose).to.be.equal("testing"); + }); + + it("should resolve with an error", async function () { + const error = new Error("Error"); + fetchStub.rejects(error); + try { + await generateDiscordInviteLink(); + } catch (err) { + expect(err).to.be.equal(error); + } + }); + }); + describe("groupUpdateLastJoinDate", function () { beforeEach(function () { sinon.stub(discordRoleModel, "doc").returns({ From 9f7dc737829b18305208f1151f03f842109b55b7 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Sat, 26 Oct 2024 02:46:26 +0530 Subject: [PATCH 04/11] feat: add joi validation for generateInviteForUser function body --- middlewares/validators/discordactions.js | 16 ++++++++++ routes/discordactions.js | 9 +++++- .../discordactions-validators.test.js | 31 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/middlewares/validators/discordactions.js b/middlewares/validators/discordactions.js index 96db3b09b..ad4724371 100644 --- a/middlewares/validators/discordactions.js +++ b/middlewares/validators/discordactions.js @@ -40,8 +40,24 @@ const validateUpdateUsersNicknameStatusBody = async (req, res, next) => { } }; +export const validateGenerateInviteForUserBody = async (req, res, next) => { + const schema = Joi.object().strict().keys({ + purpose: Joi.string().trim(), + }); + + try { + await schema.validateAsync(req.body, { abortEarly: false }); + next(); + } catch (error) { + const errorMessages = error.details.map((detail) => detail.message); + logger.error(`Error while validating invite creation payload: ${errorMessages}`); + res.boom.badRequest(errorMessages); + } +}; + module.exports = { validateGroupRoleBody, validateMemberRoleBody, validateUpdateUsersNicknameStatusBody, + validateGenerateInviteForUserBody, }; diff --git a/routes/discordactions.js b/routes/discordactions.js index 745a306df..08ad03966 100644 --- a/routes/discordactions.js +++ b/routes/discordactions.js @@ -20,6 +20,7 @@ const { validateGroupRoleBody, validateMemberRoleBody, validateUpdateUsersNicknameStatusBody, + validateGenerateInviteForUserBody, } = require("../middlewares/validators/discordactions"); const checkIsVerifiedDiscord = require("../middlewares/verifydiscord"); const checkCanGenerateDiscordLink = require("../middlewares/checkCanGenerateDiscordLink"); @@ -36,7 +37,13 @@ router.post("/groups", authenticate, checkIsVerifiedDiscord, validateGroupRoleBo router.get("/groups", authenticate, checkIsVerifiedDiscord, getAllGroupRoles); router.post("/roles", authenticate, checkIsVerifiedDiscord, validateMemberRoleBody, addGroupRoleToMember); router.get("/invite", authenticate, getUserDiscordInvite); -router.post("/invite", authenticate, checkCanGenerateDiscordLink, generateInviteForUser); +router.post( + "/invite", + authenticate, + checkCanGenerateDiscordLink, + generateInviteForUser, + validateGenerateInviteForUserBody +); router.delete("/roles", authenticate, checkIsVerifiedDiscord, deleteRole); router.get("/roles", authenticate, checkIsVerifiedDiscord, getGroupsRoleId); router.patch( diff --git a/test/unit/middlewares/discordactions-validators.test.js b/test/unit/middlewares/discordactions-validators.test.js index b96804bc5..212030117 100644 --- a/test/unit/middlewares/discordactions-validators.test.js +++ b/test/unit/middlewares/discordactions-validators.test.js @@ -3,6 +3,7 @@ const { validateGroupRoleBody, validateMemberRoleBody, validateUpdateUsersNicknameStatusBody, + validateGenerateInviteForUserBody, } = require("../../../middlewares/validators/discordactions"); const { expect } = require("chai"); @@ -127,4 +128,34 @@ describe("Middleware | Validators | discord actions", function () { expect(nextSpy.callCount).to.be.equal(0); }); }); + + describe("validateGenerateInviteForUserBody", function () { + it("lets the request pass to the next function", async function () { + const res = {}; + const req = { + body: { + purpose: "testing", + }, + }; + const nextSpy = Sinon.spy(); + await validateGenerateInviteForUserBody(req, res, nextSpy); + expect(nextSpy.calledOnce).to.be.equal(true); + }); + + it("stops the propogation to the next function", async function () { + const res = { + boom: { + badRequest: () => {}, + }, + }; + const nextSpy = Sinon.spy(); + const req = { + body: {}, + }; + await validateMemberRoleBody(req, res, nextSpy).catch((err) => { + expect(err).to.be.an.instanceOf(Error); + }); + expect(nextSpy.callCount).to.be.equal(0); + }); + }); }); From ef00ad8d5d6afc2e7fb47420812254e066683b8b Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Wed, 30 Oct 2024 22:53:08 +0530 Subject: [PATCH 05/11] fix: add suggested changes --- controllers/discordactions.js | 2 -- middlewares/validators/discordactions.js | 2 +- routes/discordactions.js | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index e9ea7ca64..f9da2cb01 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -461,7 +461,6 @@ const generateInviteForUser = async (req, res) => { message: "invite generated successfully", inviteLink, purpose, - userId, }); } else { await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink }); @@ -469,7 +468,6 @@ const generateInviteForUser = async (req, res) => { return res.status(201).json({ message: "invite generated successfully", inviteLink, - userId, }); } } catch (err) { diff --git a/middlewares/validators/discordactions.js b/middlewares/validators/discordactions.js index ad4724371..88cf25695 100644 --- a/middlewares/validators/discordactions.js +++ b/middlewares/validators/discordactions.js @@ -46,7 +46,7 @@ export const validateGenerateInviteForUserBody = async (req, res, next) => { }); try { - await schema.validateAsync(req.body, { abortEarly: false }); + await schema.validateAsync(req.body); next(); } catch (error) { const errorMessages = error.details.map((detail) => detail.message); diff --git a/routes/discordactions.js b/routes/discordactions.js index 08ad03966..608679cff 100644 --- a/routes/discordactions.js +++ b/routes/discordactions.js @@ -41,8 +41,8 @@ router.post( "/invite", authenticate, checkCanGenerateDiscordLink, - generateInviteForUser, - validateGenerateInviteForUserBody + validateGenerateInviteForUserBody, + generateInviteForUser ); router.delete("/roles", authenticate, checkIsVerifiedDiscord, deleteRole); router.get("/roles", authenticate, checkIsVerifiedDiscord, getGroupsRoleId); From a3b15c406c5cfe0dcff58cf02567741ecb2a81f6 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Wed, 30 Oct 2024 23:45:25 +0530 Subject: [PATCH 06/11] feat(e2e): add integrations test for discord-actions invite api --- test/integration/discordactions.test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/integration/discordactions.test.js b/test/integration/discordactions.test.js index 9b361990c..ea9c18b32 100644 --- a/test/integration/discordactions.test.js +++ b/test/integration/discordactions.test.js @@ -817,6 +817,27 @@ describe("Discord actions", function () { expect(res.body.message).to.be.equal("User should be super user to generate link for other users"); }); + it("Should return the invite and the purpose for other user if the dev=true is provided in the query and the user is super user", async function () { + fetchStub.returns( + Promise.resolve({ + status: 201, + json: () => Promise.resolve({ data: { code: "xyz" } }), + }) + ); + + const res = await chai + .request(app) + .post(`/discord-actions/invite?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .send({ + purpose: "testing", + }); + expect(res).to.have.status(201); + expect(res.body.message).to.be.equal("invite generated successfully"); + expect(res.body.inviteLink).to.be.equal("discord.gg/xyz"); + expect(res.body.purpose).to.be.equal("testing"); + }); + // eslint-disable-next-line mocha/no-skipped-tests it.skip("should return 403 if the user has discord id in their user object, which means user is already in discord", async function () { const res = await chai From 84d0960d71bbe208a151a158e47279c42458937e Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Thu, 31 Oct 2024 00:16:50 +0530 Subject: [PATCH 07/11] chore: remove creation of random id while storing discord invite to db collection --- controllers/discordactions.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index f9da2cb01..2cda6dc9d 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -8,7 +8,6 @@ const { fetchAllUsers, fetchUser } = require("../models/users"); const { generateCloudFlareHeaders } = require("../utils/discord-actions"); const discordDeveloperRoleId = config.get("discordDeveloperRoleId"); const discordMavenRoleId = config.get("discordMavenRoleId"); -const crypto = require("crypto"); const { setUserDiscordNickname, getDiscordMembers } = discordServices; @@ -408,16 +407,11 @@ const setRoleToUsersWith31DaysPlusOnboarding = async (req, res) => { } }; -const generateRandomId = () => { - return crypto.randomBytes(16).toString("hex"); -}; - const generateInviteForUser = async (req, res) => { try { const { userId, dev } = req.query; const userIdForInvite = userId || req.userData.id; let inviteLink = ""; - let randomId = ""; if (!dev) { const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite); @@ -426,12 +420,6 @@ const generateInviteForUser = async (req, res) => { message: "User invite is already present!", }); } - } else { - const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite); - if (!modelResponse.notFound) { - // Generate a random ID - randomId = generateRandomId(); - } } const channelId = config.get("discordNewComersChannelId"); @@ -455,7 +443,7 @@ const generateInviteForUser = async (req, res) => { if (dev) { const purpose = req.body.purpose; - await discordRolesModel.addInviteToInviteModel({ userId: randomId || userIdForInvite, inviteLink, purpose }); + await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink, purpose }); return res.status(201).json({ message: "invite generated successfully", From 3cc24446f3350e684793cc4fd276a02a25438993 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Thu, 31 Oct 2024 00:23:45 +0530 Subject: [PATCH 08/11] refactor: getUserDiscordInvite query to fetch purpose when dev is true --- controllers/discordactions.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index 2cda6dc9d..d4e34a8e8 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -466,7 +466,7 @@ const generateInviteForUser = async (req, res) => { const getUserDiscordInvite = async (req, res) => { try { - const { userId } = req.query; + const { userId, dev } = req.query; const isSuperUser = req.userData.roles.super_user; if (userId && !isSuperUser) return res.boom.forbidden("User should be super user to get link for other users"); @@ -479,10 +479,18 @@ const getUserDiscordInvite = async (req, res) => { return res.boom.notFound("User invite doesn't exist"); } - return res.json({ - message: "Invite returned successfully", - inviteLink: invite?.inviteLink, - }); + if (dev) { + return res.json({ + message: "Invite returned successfully", + inviteLink: invite?.inviteLink, + purpose: invite?.purpose, + }); + } else { + return res.json({ + message: "Invite returned successfully", + inviteLink: invite?.inviteLink, + }); + } } catch (err) { logger.error(`Error in fetching user invite: ${err}`); return res.boom.badImplementation(INTERNAL_SERVER_ERROR); From 470399b40198d20b00c954f7494aa1da2df64cf4 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Mon, 25 Nov 2024 19:05:26 +0530 Subject: [PATCH 09/11] refactor: remove non needed else block --- controllers/discordactions.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index 7cb6f472a..f800f857f 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -505,14 +505,14 @@ const generateInviteForUser = async (req, res) => { inviteLink, purpose, }); - } else { - await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink }); - - return res.status(201).json({ - message: "invite generated successfully", - inviteLink, - }); } + + await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink }); + + return res.status(201).json({ + message: "invite generated successfully", + inviteLink, + }); } catch (err) { logger.error(`Error in generating invite for user: ${err}`); return res.boom.badImplementation(INTERNAL_SERVER_ERROR); From 13f5a7362fd39ba6b842b699a7e77a2aa563a481 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Tue, 26 Nov 2024 18:33:31 +0530 Subject: [PATCH 10/11] refactor: add status code in fetch invite api --- controllers/discordactions.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index f800f857f..9ffe73f1b 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -535,17 +535,16 @@ const getUserDiscordInvite = async (req, res) => { } if (dev) { - return res.json({ + return res.status(200).json({ message: "Invite returned successfully", inviteLink: invite?.inviteLink, purpose: invite?.purpose, }); - } else { - return res.json({ - message: "Invite returned successfully", - inviteLink: invite?.inviteLink, - }); } + return res.status(200).json({ + message: "Invite returned successfully", + inviteLink: invite?.inviteLink, + }); } catch (err) { logger.error(`Error in fetching user invite: ${err}`); return res.boom.badImplementation(INTERNAL_SERVER_ERROR); From f63f5640f8a83b44db7491b05d83b9b34ea522d8 Mon Sep 17 00:00:00 2001 From: Kuldeep Gupta Date: Tue, 26 Nov 2024 18:47:08 +0530 Subject: [PATCH 11/11] fix: dev condition to only work in truthy case --- controllers/discordactions.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index 9ffe73f1b..7b4e4fb6e 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -467,8 +467,9 @@ const generateInviteForUser = async (req, res) => { const { userId, dev } = req.query; const userIdForInvite = userId || req.userData.id; let inviteLink = ""; + const isDev = dev === "true"; - if (!dev) { + if (!isDev) { const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite); if (!modelResponse.notFound) { return res.status(409).json({ @@ -496,7 +497,7 @@ const generateInviteForUser = async (req, res) => { const inviteCode = discordInviteResponse.data.code; inviteLink = `discord.gg/${inviteCode}`; - if (dev) { + if (isDev) { const purpose = req.body.purpose; await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink, purpose }); @@ -523,6 +524,7 @@ const getUserDiscordInvite = async (req, res) => { try { const { userId, dev } = req.query; const isSuperUser = req.userData.roles.super_user; + const isDev = dev === "true"; if (userId && !isSuperUser) return res.boom.forbidden("User should be super user to get link for other users"); @@ -534,7 +536,7 @@ const getUserDiscordInvite = async (req, res) => { return res.boom.notFound("User invite doesn't exist"); } - if (dev) { + if (isDev) { return res.status(200).json({ message: "Invite returned successfully", inviteLink: invite?.inviteLink,