Skip to content

Commit f84a028

Browse files
heyrandhirAbhay5855pushpenduRitikJaiswal75iamitprakash
authored
Dev to Main Sync (#1357)
* Feat : username and twitter id contains @ / special characters in the value in some user data. (#1320) * feat:add validations in the signup api to check that username must be in the specific format and social id must not contain the special character @ to it also modify the error with a proper error message * fix:the error message description * fix:change the error message according to specific social id change * fix:change the regex validation such that now it will accept numbers or characters or combination of both * refactor:change the error message to meaningful text * feat:add tests for invalid username and valid username and invalid social id * fix:match the error message with the failing test case message, update the error messages in the signup api * fix:remove unnecessary validation for linkedin_id, intagram_id we are not sending it during signup also remove the tests for that * fix:remove unnecessary validation for linkedin_id, intagram_id we are not sending it during signup also remove the tests for that * feat:add unit testing for the update user function to check if invalid username and twitter_id is passed or not --------- Co-authored-by: pushpendu <[email protected]> * add option to set privilaged roles in staging backend * remove console logs * modify the condition to check non production env * add support to other roles update in staging (#1354) * add support to other roles * modify condition to forbid in production * add an api to remove privilaged roles to all users (#1355) * add an api to remove privilaged roles to all users * add middleware to allow body * adds metrics for users update route (#1347) * Fixing Task Reassignment Status Update Issue (#1351) * adds the logic to modify the user status of old assignee * Adds integration test * Consolidated Multi-Line Variable Declarations into Single Line --------- Co-authored-by: Abhay Patil <[email protected]> Co-authored-by: pushpendu <[email protected]> Co-authored-by: ritikjaiswal75 <[email protected]> Co-authored-by: Amit Prakash <[email protected]> Co-authored-by: Ritik Jaiswal <[email protected]>
1 parent 739518c commit f84a028

File tree

13 files changed

+391
-13
lines changed

13 files changed

+391
-13
lines changed

controllers/staging.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const { addOrUpdate, getUsersByRole } = require("../models/users");
2+
3+
const updateRoles = async (req, res) => {
4+
try {
5+
const userData = await req.userData;
6+
if (process.env.NODE_ENV === "production") {
7+
return res.status(403).json({
8+
message: "FORBIDDEN | To be used only in staging and development",
9+
});
10+
}
11+
const userId = req.userData.id;
12+
await addOrUpdate(
13+
{
14+
roles: {
15+
...userData.roles,
16+
...req.body,
17+
},
18+
},
19+
userId
20+
);
21+
return res.status(200).json({
22+
message: "Roles Updated successfully",
23+
});
24+
} catch (err) {
25+
logger.error(`Oops an error occured: ${err}`);
26+
return res.status(500).json({
27+
message: "Oops an internal error occured",
28+
});
29+
}
30+
};
31+
32+
const removePrivileges = async (req, res) => {
33+
if (process.env.NODE_ENV === "production") {
34+
return res.status(403).json({
35+
message: "FORBIDDEN | To be used only in staging and development",
36+
});
37+
}
38+
try {
39+
const updateUserPromises = [];
40+
const members = await getUsersByRole("member");
41+
const superUsers = await getUsersByRole("super_user");
42+
43+
members.forEach((member) => {
44+
updateUserPromises.push(
45+
addOrUpdate(
46+
{
47+
roles: {
48+
...member.roles,
49+
member: false,
50+
},
51+
},
52+
member.id
53+
)
54+
);
55+
});
56+
superUsers.forEach((superUser) => {
57+
updateUserPromises.push(
58+
addOrUpdate(
59+
{
60+
roles: {
61+
...superUser.roles,
62+
super_user: false,
63+
},
64+
},
65+
superUser.id
66+
)
67+
);
68+
});
69+
70+
await Promise.all(updateUserPromises);
71+
72+
return res.status(200).json({
73+
message: "Roles Updated successfully",
74+
});
75+
} catch (err) {
76+
logger.error(`Oops an error occurred: ${err}`);
77+
return res.status(500).json({
78+
message: "Oops an internal error occurred",
79+
});
80+
}
81+
};
82+
83+
module.exports = {
84+
updateRoles,
85+
removePrivileges,
86+
};

controllers/tasks.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,12 @@ const updateTask = async (req, res) => {
256256
}
257257
await tasks.updateTask(req.body, req.params.id);
258258
if (isUserStatusEnabled && req.body.assignee) {
259+
// New Assignee Status Update
259260
await updateUserStatusOnTaskUpdate(req.body.assignee);
261+
// Old Assignee Status Update if available
262+
if (task.taskData.assigneeId) {
263+
await updateStatusOnTaskCompletion(task.taskData.assigneeId);
264+
}
260265
}
261266

262267
return res.status(204).send();

controllers/userStatus.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,10 @@ const updateUserStatus = async (req, res) => {
135135
*/
136136
const updateAllUserStatus = async (req, res) => {
137137
try {
138-
await userStatusModel.updateAllUserStatus();
138+
const data = await userStatusModel.updateAllUserStatus();
139139
return res.status(200).json({
140140
message: "All User Status updated successfully.",
141+
data,
141142
});
142143
} catch (err) {
143144
logger.error(`Error while updating the User Data: ${err}`);

middlewares/validators/staging.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const joi = require("joi");
2+
3+
const validateUserRoles = async (req, res, next) => {
4+
const schema = joi.object().strict().keys({
5+
super_user: joi.boolean().optional(),
6+
member: joi.boolean().optional(),
7+
archive: joi.boolean().optional(),
8+
in_discord: joi.boolean().optional(),
9+
});
10+
11+
try {
12+
await schema.validateAsync(req.body);
13+
next();
14+
} catch (err) {
15+
logger.error(`Error validating validateUserRoles payload : ${err}`);
16+
res.boom.badRequest(JSON.stringify({ allowedParameters: { super_user: "boolean", member: "boolean" } }));
17+
}
18+
};
19+
20+
const validateRevokePrivileges = async (req, res, next) => {
21+
const schema = joi
22+
.object()
23+
.strict()
24+
.keys({
25+
action: joi.string().equal("revoke"),
26+
});
27+
try {
28+
await schema.validateAsync(req.body);
29+
next();
30+
} catch (err) {
31+
logger.error(`Error validating validateUserRoles payload : ${err}`);
32+
res.boom.badRequest(JSON.stringify({ allowedParameters: { action: "revoke" } }));
33+
}
34+
};
35+
36+
module.exports = {
37+
validateUserRoles,
38+
validateRevokePrivileges,
39+
};

middlewares/validators/user.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,25 @@ const updateUser = async (req, res, next) => {
1313
.keys({
1414
phone: joi.string().optional(),
1515
email: joi.string().optional(),
16-
username: joi.string().optional(),
16+
username: joi
17+
.string()
18+
.optional()
19+
.min(4)
20+
.max(20)
21+
.regex(/^[a-zA-Z0-9]+$/)
22+
.message("Username must be between 4 and 20 characters long and contain only letters or numbers."),
1723
first_name: joi.string().optional(),
1824
last_name: joi.string().optional(),
1925
yoe: joi.number().min(0).optional(),
2026
company: joi.string().optional(),
2127
designation: joi.string().optional(),
2228
img: joi.string().optional(),
2329
linkedin_id: joi.string().optional(),
24-
twitter_id: joi.string().optional(),
30+
twitter_id: joi
31+
.string()
32+
.optional()
33+
.regex(/^[^@]*$/)
34+
.message("Invalid Twitter ID. ID should not contain special character @"),
2535
instagram_id: joi.string().optional(),
2636
website: joi.string().optional(),
2737
status: joi

models/userStatus.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,16 @@ const updateUserStatus = async (userId, newStatusData) => {
157157
*/
158158

159159
const updateAllUserStatus = async () => {
160+
const summary = {
161+
usersCount: 0,
162+
oooUsersAltered: 0,
163+
oooUsersUnaltered: 0,
164+
nonOooUsersAltered: 0,
165+
nonOooUsersUnaltered: 0,
166+
};
160167
try {
161168
const userStatusDocs = await userStatusModel.where("futureStatus.state", "in", ["ACTIVE", "IDLE", "OOO"]).get();
169+
summary.usersCount = userStatusDocs._size;
162170
const batch = firestore.batch();
163171
const today = new Date().getTime();
164172
userStatusDocs.forEach(async (document) => {
@@ -170,15 +178,23 @@ const updateAllUserStatus = async () => {
170178
const { state: futureState } = futureStatus;
171179
if (futureState === "ACTIVE" || futureState === "IDLE") {
172180
if (today >= futureStatus.from) {
181+
// OOO period is over and we need to update their current status
173182
newStatusData.currentStatus = { ...futureStatus, until: "", updatedAt: today };
174-
newStatusData.futureStatus = {};
183+
delete newStatusData.futureStatus;
175184
toUpdate = !toUpdate;
185+
summary.oooUsersAltered++;
186+
} else {
187+
summary.oooUsersUnaltered++;
176188
}
177189
} else {
190+
// futureState is OOO
178191
if (today > futureStatus.until) {
179-
newStatusData.futureStatus = {};
192+
// the OOO period is over
193+
delete newStatusData.futureStatus;
180194
toUpdate = !toUpdate;
195+
summary.nonOooUsersAltered++;
181196
} else if (today <= doc.futureStatus.until && today >= doc.futureStatus.from) {
197+
// the current date i.e today lies in between the from and until so we need to swap the status
182198
let newCurrentStatus = {};
183199
let newFutureStatus = {};
184200
newCurrentStatus = { ...futureStatus, updatedAt: today };
@@ -188,6 +204,9 @@ const updateAllUserStatus = async () => {
188204
newStatusData.currentStatus = newCurrentStatus;
189205
newStatusData.futureStatus = newFutureStatus;
190206
toUpdate = !toUpdate;
207+
summary.nonOooUsersAltered++;
208+
} else {
209+
summary.nonOooUsersUnaltered++;
191210
}
192211
}
193212
if (toUpdate) {
@@ -200,13 +219,12 @@ const updateAllUserStatus = async () => {
200219
);
201220
}
202221
await batch.commit();
203-
return { status: 204, message: "User Status updated Successfully." };
222+
return summary;
204223
} catch (error) {
205224
logger.error(`error in updating User Status Documents ${error}`);
206225
return { status: 500, message: "User Status couldn't be updated Successfully." };
207226
}
208227
};
209-
210228
/**
211229
* Updates the user status based on a new task assignment.
212230
* @param {string} userId - The ID of the user.

routes/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ app.use("/discord-actions", require("./discordactions.js"));
3030
app.use("/issues", require("./issues.js"));
3131
app.use("/progresses", require("./progresses.js"));
3232
app.use("/monitor", require("./monitor.js"));
33-
33+
app.use("/staging", require("./staging.js"));
3434
module.exports = app;

routes/staging.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const express = require("express");
2+
const authenticate = require("../middlewares/authenticate");
3+
const { validateUserRoles, validateRevokePrivileges } = require("../middlewares/validators/staging");
4+
const { updateRoles, removePrivileges } = require("../controllers/staging");
5+
const router = express.Router();
6+
7+
router.patch("/user", validateUserRoles, authenticate, updateRoles);
8+
router.post("/users/privileges", validateRevokePrivileges, removePrivileges);
9+
10+
module.exports = router;

test/integration/taskBasedStatusUpdate.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,4 +537,59 @@ describe("Task Based Status Updates", function () {
537537
);
538538
});
539539
});
540+
541+
describe("PATCH Update User Status on Task Assignment by SuperUser", function () {
542+
let userId1, user2Name, superUserId, superUserJwt, taskArr;
543+
const reqBody = {};
544+
545+
beforeEach(async function () {
546+
userId1 = await addUser(userData[6]);
547+
superUserId = await addUser(userData[4]);
548+
superUserJwt = authService.generateAuthToken({ userId: superUserId });
549+
await addUser(userData[0]);
550+
user2Name = userData[0].username;
551+
taskArr = allTasks();
552+
const sampleTask1 = taskArr[0];
553+
sampleTask1.assignee = userId1;
554+
sampleTask1.createdBy = superUserId;
555+
await firestore.collection("tasks").doc("taskid123").set(sampleTask1);
556+
const statusData = generateStatusDataForState(userId1, userState.ACTIVE);
557+
await firestore.collection("usersStatus").doc("userStatusDoc001").set(statusData);
558+
});
559+
560+
afterEach(async function () {
561+
await cleanDb();
562+
});
563+
564+
it("Update the old assignee status to IDLE on task reassignment if no tasks is in progress in their name", async function () {
565+
reqBody.assignee = user2Name;
566+
const res = await chai
567+
.request(app)
568+
.patch(`/tasks/taskid123?userStatusFlag=true`)
569+
.set("cookie", `${cookieName}=${superUserJwt}`)
570+
.send(reqBody);
571+
expect(res.status).to.equal(204);
572+
const userStatus002Data = (await userStatusModel.doc("userStatusDoc001").get()).data();
573+
expect(userStatus002Data).to.have.keys(["userId", "currentStatus"]);
574+
expect(userStatus002Data.currentStatus.state).to.equal(userState.IDLE);
575+
});
576+
577+
it("Should maintain the old assignee status to ACTIVE on task reassignment if another task is in progress in their name", async function () {
578+
const sampleTask2 = taskArr[1];
579+
sampleTask2.assignee = userId1;
580+
sampleTask2.createdBy = superUserId;
581+
await firestore.collection("tasks").doc("taskid234").set(sampleTask2);
582+
583+
reqBody.assignee = user2Name;
584+
const res = await chai
585+
.request(app)
586+
.patch(`/tasks/taskid123?userStatusFlag=true`)
587+
.set("cookie", `${cookieName}=${superUserJwt}`)
588+
.send(reqBody);
589+
expect(res.status).to.equal(204);
590+
const userStatus002Data = (await userStatusModel.doc("userStatusDoc001").get()).data();
591+
expect(userStatus002Data).to.have.keys(["userId", "currentStatus"]);
592+
expect(userStatus002Data.currentStatus.state).to.equal(userState.ACTIVE);
593+
});
594+
});
540595
});

test/integration/userStatus.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ describe("UserStatus", function () {
175175
.send();
176176
expect(response3).to.have.status(200);
177177
expect(response3.body.message).to.equal("All User Status updated successfully.");
178+
expect(response3.body.data).to.deep.equal({
179+
usersCount: 1,
180+
oooUsersAltered: 0,
181+
oooUsersUnaltered: 0,
182+
nonOooUsersAltered: 1,
183+
nonOooUsersUnaltered: 0,
184+
});
178185

179186
// Checking the current status
180187
const response4 = await chai.request(app).get(`/users/status/self`).set("Cookie", `${cookieName}=${testUserJwt}`);
@@ -195,6 +202,13 @@ describe("UserStatus", function () {
195202
.send();
196203
expect(response5).to.have.status(200);
197204
expect(response5.body.message).to.equal("All User Status updated successfully.");
205+
expect(response5.body.data).to.deep.equal({
206+
usersCount: 1,
207+
oooUsersAltered: 1,
208+
oooUsersUnaltered: 0,
209+
nonOooUsersAltered: 0,
210+
nonOooUsersUnaltered: 0,
211+
});
198212

199213
const response6 = await chai.request(app).get(`/users/status/self`).set("Cookie", `${cookieName}=${testUserJwt}`);
200214
expect(response6).to.have.status(200);
@@ -236,6 +250,13 @@ describe("UserStatus", function () {
236250
.send();
237251
expect(response3).to.have.status(200);
238252
expect(response3.body.message).to.equal("All User Status updated successfully.");
253+
expect(response3.body.data).to.deep.equal({
254+
usersCount: 1,
255+
oooUsersAltered: 0,
256+
oooUsersUnaltered: 0,
257+
nonOooUsersAltered: 1,
258+
nonOooUsersUnaltered: 0,
259+
});
239260

240261
// Checking the current status
241262
const response4 = await chai.request(app).get(`/users/status/self`).set("Cookie", `${cookieName}=${testUserJwt}`);

0 commit comments

Comments
 (0)