Skip to content

Commit 5163ccb

Browse files
Notify Service Feature Request (#1667)
1 parent 824ecf1 commit 5163ccb

File tree

17 files changed

+662
-0
lines changed

17 files changed

+662
-0
lines changed

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+
};

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/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 };

routes/fcmToken.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const express = require("express");
2+
const router = express.Router();
3+
4+
const authenticate = require("../middlewares/authenticate");
5+
const { fcmTokenController } = require("../controllers/fcmToken");
6+
const { fcmTokenValidator } = require("../middlewares/validators/fcmToken");
7+
8+
router.post("/", authenticate, fcmTokenValidator, fcmTokenController);
9+
module.exports = router;

routes/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ app.use("/issues", require("./issues.js"));
3131
app.use("/progresses", require("./progresses.js"));
3232
app.use("/monitor", require("./monitor.js"));
3333
app.use("/staging", require("./staging.js"));
34+
app.use("/v1/fcm-tokens", require("./fcmToken.js"));
35+
app.use("/v1/notifications", require("./notify.js"));
3436
app.use("/goals", require("./goals.js"));
3537
module.exports = app;

routes/notify.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const express = require("express");
2+
const router = express.Router();
3+
4+
const authenticate = require("../middlewares/authenticate");
5+
const { notifyController } = require("../controllers/notify");
6+
const { notifyValidator } = require("../middlewares/validators/notify");
7+
8+
router.post("/", authenticate, notifyValidator, notifyController);
9+
module.exports = router;

services/getFcmTokenFromUserId.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const firestore = require("../utils/firestore");
2+
3+
const fcmTokenModel = firestore.collection("fcmToken");
4+
5+
export const getFcmTokenFromUserId = async (userId) => {
6+
if (!userId) return [];
7+
const fcmTokenSnapshot = await fcmTokenModel.where("userId", "==", userId).limit(1).get();
8+
if (!fcmTokenSnapshot.empty) {
9+
return fcmTokenSnapshot.docs[0].data().fcmTokens;
10+
}
11+
return [];
12+
};

services/getUserIdsFromRoleId.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const firestore = require("../utils/firestore");
2+
const memberRoleModel = firestore.collection("member-group-roles");
3+
4+
export const getUserIdsFromRoleId = async (roleId) => {
5+
let userIds = [];
6+
try {
7+
const querySnapshot = await memberRoleModel.where("roleid", "==", roleId).get();
8+
if (querySnapshot.empty) {
9+
return [];
10+
}
11+
if (!querySnapshot.empty) {
12+
userIds = querySnapshot.docs.map((doc) => doc.data().userid);
13+
}
14+
return userIds;
15+
} catch (error) {
16+
logger.error("error", error);
17+
throw error;
18+
}
19+
};

0 commit comments

Comments
 (0)