Skip to content

Commit 750411e

Browse files
authored
Merge pull request #1757 from Real-Dev-Squad/develop
Dev to main Sync
2 parents 3126e86 + fd92f8b commit 750411e

28 files changed

+1065
-3
lines changed

config/development.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
port: port,
1111
enableFileLogs: false,
1212
enableConsoleLogs: true,
13+
discordNewComersChannelId: "709080951824842783",
1314

1415
services: {
1516
rdsApi: {

config/test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
enableConsoleLogs: true,
1212
discordUnverifiedRoleId: "1234567890",
1313
discordDeveloperRoleId: "9876543210",
14+
discordNewComersChannelId: "709080951824842783",
1415
discordMavenRoleId: "1212121212",
1516
githubOauth: {
1617
clientId: "clientId",

controllers/discordactions.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,75 @@ const setRoleToUsersWith31DaysPlusOnboarding = async (req, res) => {
403403
}
404404
};
405405

406+
const generateInviteForUser = async (req, res) => {
407+
try {
408+
const { userId } = req.query;
409+
const userIdForInvite = userId || req.userData.id;
410+
411+
const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite);
412+
413+
if (!modelResponse.notFound) {
414+
return res.status(409).json({
415+
message: "User invite is already present!",
416+
});
417+
}
418+
419+
const channelId = config.get("discordNewComersChannelId");
420+
const authToken = jwt.sign({}, config.get("rdsServerlessBot.rdsServerLessPrivateKey"), {
421+
algorithm: "RS256",
422+
expiresIn: config.get("rdsServerlessBot.ttl"),
423+
});
424+
425+
const inviteOptions = {
426+
channelId: channelId,
427+
};
428+
const response = await fetch(`${DISCORD_BASE_URL}/invite`, {
429+
method: "POST",
430+
body: JSON.stringify(inviteOptions),
431+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` },
432+
});
433+
const discordInviteResponse = await response.json();
434+
435+
const inviteCode = discordInviteResponse.data.code;
436+
const inviteLink = `discord.gg/${inviteCode}`;
437+
438+
await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink });
439+
440+
return res.status(201).json({
441+
message: "invite generated successfully",
442+
inviteLink,
443+
});
444+
} catch (err) {
445+
logger.error(`Error in generating invite for user: ${err}`);
446+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
447+
}
448+
};
449+
450+
const getUserDiscordInvite = async (req, res) => {
451+
try {
452+
const { userId } = req.query;
453+
const isSuperUser = req.userData.roles.super_user;
454+
455+
if (userId && !isSuperUser) return res.boom.forbidden("User should be super user to get link for other users");
456+
457+
const userIdForInvite = userId || req.userData.id;
458+
459+
const invite = await discordRolesModel.getUserDiscordInvite(userIdForInvite);
460+
461+
if (invite.notFound) {
462+
return res.boom.notFound("User invite doesn't exist");
463+
}
464+
465+
return res.json({
466+
message: "Invite returned successfully",
467+
inviteLink: invite?.inviteLink,
468+
});
469+
} catch (err) {
470+
logger.error(`Error in fetching user invite: ${err}`);
471+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
472+
}
473+
};
474+
406475
module.exports = {
407476
getGroupsRoleId,
408477
createGroupRole,
@@ -416,4 +485,6 @@ module.exports = {
416485
updateUsersNicknameStatus,
417486
syncDiscordGroupRolesInFirestore,
418487
setRoleToUsersWith31DaysPlusOnboarding,
488+
getUserDiscordInvite,
489+
generateInviteForUser,
419490
};

controllers/fcmToken.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const { saveFcmToken } = require("../models/fcmToken");
2+
const { Conflict } = require("http-errors");
3+
4+
/**
5+
* Route used to get the health status of teh server
6+
*
7+
* @param req {Object} - Express request object
8+
* @param res {Object} - Express response object
9+
*/
10+
const fcmTokenController = async (req, res) => {
11+
try {
12+
const { fcmToken } = req.body;
13+
14+
const fcmTokenId = await saveFcmToken({ userId: req.userData.id, fcmToken });
15+
if (fcmTokenId) res.status(200).json({ status: 200, message: "Device registered successfully" });
16+
} catch (error) {
17+
if (error instanceof Conflict) {
18+
return res.status(409).json({
19+
message: error.message,
20+
});
21+
}
22+
res.status(500).send("Something went wrong, please contact admin");
23+
}
24+
return res.status(500).send("Internal server error");
25+
};
26+
27+
module.exports = {
28+
fcmTokenController,
29+
};

controllers/notify.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const admin = require("firebase-admin");
2+
const { getFcmTokenFromUserId } = require("../services/getFcmTokenFromUserId");
3+
const { getUserIdsFromRoleId } = require("../services/getUserIdsFromRoleId");
4+
5+
/**
6+
* Route used to get the health status of teh server
7+
*
8+
* @param req {Object} - Express request object
9+
* @param res {Object} - Express response object
10+
*/
11+
const notifyController = async (req, res) => {
12+
const { title, body, userId, groupRoleId } = req.body;
13+
let fcmTokens = [];
14+
if (userId) {
15+
const fcmTokensFromUserId = await getFcmTokenFromUserId(userId);
16+
fcmTokens = [...fcmTokens, ...fcmTokensFromUserId];
17+
}
18+
19+
let userIdsFromRoleId = [];
20+
let fcmTokensFromUserId;
21+
if (groupRoleId) {
22+
try {
23+
userIdsFromRoleId = await getUserIdsFromRoleId(groupRoleId);
24+
} catch (error) {
25+
logger.error("error ", error);
26+
throw error;
27+
}
28+
29+
const fcmTokensPromiseArray = userIdsFromRoleId.map(async (userId) => {
30+
try {
31+
fcmTokensFromUserId = await getFcmTokenFromUserId(userId);
32+
} catch (error) {
33+
logger.error("error ", error);
34+
throw error;
35+
}
36+
fcmTokens = [...fcmTokens, ...fcmTokensFromUserId];
37+
});
38+
try {
39+
await Promise.all(fcmTokensPromiseArray);
40+
} catch (error) {
41+
logger.error("error", error);
42+
throw error;
43+
}
44+
}
45+
46+
const setOfFcmTokens = new Set(fcmTokens);
47+
48+
const message = {
49+
notification: {
50+
title: title || "Notification Title",
51+
body: body || "Notification Body",
52+
},
53+
data: {
54+
key1: "value1",
55+
key2: "value2",
56+
},
57+
tokens: Array.from(setOfFcmTokens),
58+
};
59+
function calculateMessageSize(message) {
60+
const byteArray = new TextEncoder().encode(message);
61+
62+
const byteLength = byteArray.length;
63+
64+
const kilobytes = byteLength / 1024;
65+
66+
return kilobytes;
67+
}
68+
if (calculateMessageSize(message) >= 2) {
69+
res.error(401).send("Message length exceeds");
70+
}
71+
admin
72+
.messaging()
73+
.sendMulticast(message)
74+
.then(() => res.status(200).json({ status: 200, message: "User notified successfully" }))
75+
.catch((error) => {
76+
logger.error("Error sending message:", error);
77+
res.status(500).json({ status: 500, message: "Internal server error" });
78+
});
79+
};
80+
81+
module.exports = {
82+
notifyController,
83+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NextFunction } from "express";
2+
import { CustomRequest, CustomResponse } from "../types/global";
3+
4+
const checkCanGenerateDiscordLink = async (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
5+
const { discordId, roles, id: userId, profileStatus } = req.userData;
6+
const isSuperUser = roles.super_user;
7+
const userIdInQuery = req.query.userId;
8+
9+
if (userIdInQuery && userIdInQuery !== userId && !isSuperUser) {
10+
return res.boom.forbidden("User should be super user to generate link for other users");
11+
}
12+
13+
if (discordId) {
14+
return res.boom.forbidden("Only users who have never joined discord can generate invite link");
15+
}
16+
17+
if (roles.archived) {
18+
return res.boom.forbidden("Archived users cannot generate invite");
19+
}
20+
21+
if (!roles.maven && !roles.designer && !roles.product_manager && profileStatus !== "VERIFIED") {
22+
return res.boom.forbidden("Only selected roles can generate discord link directly");
23+
}
24+
25+
return next();
26+
};
27+
28+
module.exports = checkCanGenerateDiscordLink;

middlewares/validators/fcmToken.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const joi = require("joi");
2+
3+
export const fcmTokenValidator = async (req, res, next) => {
4+
const schema = joi.object().strict().keys({
5+
fcmToken: joi.string().required(),
6+
});
7+
try {
8+
await schema.validateAsync(req.body);
9+
next();
10+
} catch (error) {
11+
logger.error(`Bad request body : ${error}`);
12+
res.boom.badRequest(error.details[0].message);
13+
}
14+
};

middlewares/validators/notify.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const joi = require("joi");
2+
3+
export const notifyValidator = async (req, res, next) => {
4+
const MAX_TITLE_LENGTH = 512;
5+
const MAX_BODY_LENGTH = 1536;
6+
7+
const schema = joi
8+
.object()
9+
.strict()
10+
.keys({
11+
title: joi.string().required().max(MAX_TITLE_LENGTH).required(),
12+
body: joi.string().required().max(MAX_BODY_LENGTH).required(),
13+
userId: joi.string(),
14+
groupRoleId: joi.string(),
15+
})
16+
.xor("userId", "groupRoleId");
17+
try {
18+
await schema.validateAsync(req.body);
19+
next();
20+
} catch (error) {
21+
logger.error(`Bad request body : ${error}`);
22+
res.boom.badRequest(error.details[0].message);
23+
}
24+
};

models/discordactions.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { generateDiscordProfileImageUrl } = require("../utils/discord-actions");
22
const firestore = require("../utils/firestore");
33
const discordRoleModel = firestore.collection("discord-roles");
44
const memberRoleModel = firestore.collection("member-group-roles");
5+
const discordInvitesModel = firestore.collection("discord-invites");
56
const admin = require("firebase-admin");
67
const { findSubscribedGroupIds } = require("../utils/helper");
78
const { retrieveUsers } = require("../services/dataAccessLayer");
@@ -830,6 +831,31 @@ const updateUsersWith31DaysPlusOnboarding = async () => {
830831
}
831832
};
832833

834+
const addInviteToInviteModel = async (inviteObject) => {
835+
try {
836+
const invite = await discordInvitesModel.add(inviteObject);
837+
return invite.id;
838+
} catch (err) {
839+
logger.error("Error in adding invite", err);
840+
throw err;
841+
}
842+
};
843+
844+
const getUserDiscordInvite = async (userId) => {
845+
try {
846+
const invite = await discordInvitesModel.where("userId", "==", userId).get();
847+
const [inviteDoc] = invite.docs;
848+
if (inviteDoc) {
849+
return { id: inviteDoc.id, ...inviteDoc.data(), notFound: false };
850+
} else {
851+
return { notFound: true };
852+
}
853+
} catch (err) {
854+
logger.log("error in getting user invite", err);
855+
throw err;
856+
}
857+
};
858+
833859
module.exports = {
834860
createNewRole,
835861
removeMemberGroup,
@@ -847,4 +873,6 @@ module.exports = {
847873
updateUsersNicknameStatus,
848874
updateIdle7dUsersOnDiscord,
849875
updateUsersWith31DaysPlusOnboarding,
876+
getUserDiscordInvite,
877+
addInviteToInviteModel,
850878
};

models/fcmToken.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const firestore = require("../utils/firestore");
2+
const { Conflict } = require("http-errors");
3+
4+
const fcmTokenModel = firestore.collection("fcmToken");
5+
6+
const saveFcmToken = async (fcmTokenData) => {
7+
try {
8+
const fcmTokenSnapshot = await fcmTokenModel.where("userId", "==", fcmTokenData.userId).limit(1).get();
9+
10+
if (fcmTokenSnapshot.empty) {
11+
const fcmToken = await fcmTokenModel.add({
12+
userId: fcmTokenData.userId,
13+
fcmTokens: [fcmTokenData.fcmToken],
14+
});
15+
return fcmToken.id;
16+
} else {
17+
let fcmTokenObj = {};
18+
fcmTokenSnapshot.forEach((fcmToken) => {
19+
fcmTokenObj = {
20+
id: fcmToken.id,
21+
...fcmToken.data(),
22+
};
23+
});
24+
if (!fcmTokenObj.fcmTokens.includes(fcmTokenData.fcmToken)) {
25+
fcmTokenObj.fcmTokens.push(fcmTokenData.fcmToken);
26+
await fcmTokenModel.doc(fcmTokenObj.id).update({
27+
fcmTokens: fcmTokenObj.fcmTokens,
28+
});
29+
return fcmTokenObj.id;
30+
} else {
31+
throw new Conflict("Device Already Registered");
32+
}
33+
}
34+
} catch (err) {
35+
logger.error("Error in adding fcm token", err);
36+
throw err;
37+
}
38+
};
39+
40+
module.exports = { saveFcmToken };

0 commit comments

Comments
 (0)