Skip to content

Commit 4e1d402

Browse files
Merge pull request #2161 from saitejakaasoju/sai/pause-user-permission-backend
Sai Teja - Add backend support for dedicated pause/resume permission
2 parents a07367f + a478a9d commit 4e1d402

7 files changed

Lines changed: 134 additions & 32 deletions

File tree

src/controllers/notificationController.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ const notificationController = function () {
2424
}
2525
if (
2626
requestor.requestorId !== userId &&
27-
(requestor.role !== 'Administrator' || requestor.role !== 'Owner')
27+
requestor.role !== 'Administrator' &&
28+
requestor.role !== 'Owner'
2829
) {
2930
res.status(403).send({ error: 'Unauthorized request' });
3031
return;
@@ -55,7 +56,8 @@ const notificationController = function () {
5556
}
5657
if (
5758
requestor.requestorId !== userId &&
58-
(requestor.role !== 'Administrator' || requestor.role !== 'Owner')
59+
requestor.role !== 'Administrator' &&
60+
requestor.role !== 'Owner'
5961
) {
6062
res.status(403).send({ error: 'Unauthorized request' });
6163
return;

src/controllers/notificationController.spec.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const makeSut = () => {
2424
};
2525

2626
describe('Notification controller Unit Tests', () => {
27+
const privilegedRoles = ['Administrator', 'Owner'];
28+
2729
beforeEach(() => {
2830
mockReq.params.userId = '65cf6c3706d8ac105827bb2e';
2931
mockReq.body.requestor.role = 'Administrator';
@@ -45,13 +47,27 @@ describe('Notification controller Unit Tests', () => {
4547
const response = await getUserNotifications(mockReq, mockRes);
4648
assertResMock(400, errorMsg, response, mockRes);
4749
});
48-
test('Ensures getUserNotifications returns error 403 if userId does not match requestorId', async () => {
50+
test('Ensures getUserNotifications returns error 403 if userId does not match requestorId and requestor is not Admin or Owner', async () => {
4951
const { getUserNotifications } = makeSut();
5052
const errorMsg = { error: 'Unauthorized request' };
5153
mockReq.body.requestor.requestorId = 'differentUserId';
54+
mockReq.body.requestor.role = 'Volunteer';
5255
const response = await getUserNotifications(mockReq, mockRes);
5356
assertResMock(403, errorMsg, response, mockRes);
5457
});
58+
test.each(privilegedRoles)(
59+
'Ensures getUserNotifications returns 200 if userId does not match requestorId but requestor is %s',
60+
async (role) => {
61+
const { getUserNotifications } = makeSut();
62+
const mockNotifications = [{ id: '123', message: 'Notification Test 1' }];
63+
mockReq.body.requestor.requestorId = 'differentUserId';
64+
mockReq.body.requestor.role = role;
65+
const mockService = jest.fn().mockResolvedValue(mockNotifications);
66+
notificationService.getNotifications = mockService;
67+
await expect(getUserNotifications(mockReq, mockRes)).resolves.toBeUndefined();
68+
assertResMock(200, mockNotifications, undefined, mockRes);
69+
},
70+
);
5571
test('Ensures getUserNotifications returns 200 and notifications data when notifications are fetched successfully', async () => {
5672
const { getUserNotifications } = makeSut();
5773
const mockNotifications = [
@@ -82,13 +98,27 @@ describe('Notification controller Unit Tests', () => {
8298
const response = await getUnreadUserNotifications(mockReq, mockRes);
8399
assertResMock(400, errorMsg, response, mockRes);
84100
});
85-
test('Ensures getUnreadUserNotifications returns error 403 if userId does not match requestorId', async () => {
101+
test('Ensures getUnreadUserNotifications returns error 403 if userId does not match requestorId and requestor is not Admin or Owner', async () => {
86102
const { getUnreadUserNotifications } = makeSut();
87103
const errorMsg = { error: 'Unauthorized request' };
88104
mockReq.body.requestor.requestorId = 'differentUserId';
105+
mockReq.body.requestor.role = 'Volunteer';
89106
const response = await getUnreadUserNotifications(mockReq, mockRes);
90107
assertResMock(403, errorMsg, response, mockRes);
91108
});
109+
test.each(privilegedRoles)(
110+
'Ensures getUnreadUserNotifications returns 200 if userId does not match requestorId but requestor is %s',
111+
async (role) => {
112+
const { getUnreadUserNotifications } = makeSut();
113+
const mockNotifications = [{ id: '123', message: 'Notification Test 1' }];
114+
mockReq.body.requestor.requestorId = 'differentUserId';
115+
mockReq.body.requestor.role = role;
116+
const mockService = jest.fn().mockResolvedValue(mockNotifications);
117+
notificationService.getUnreadUserNotifications = mockService;
118+
await expect(getUnreadUserNotifications(mockReq, mockRes)).resolves.toBeUndefined();
119+
assertResMock(200, mockNotifications, undefined, mockRes);
120+
},
121+
);
92122
test('Ensures getUnreadUserNotifications returns 200 and notifications data when notifications are fetched successfully', async () => {
93123
const { getUnreadUserNotifications } = makeSut();
94124
const mockNotifications = [

src/controllers/userProfileController.js

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,13 @@ const createControllerMethods = function (UserProfile, Project, cache) {
662662

663663
const hasChangeStatusPermission = await hasPermission(requestor, 'changeUserStatus');
664664
const hasFinalDayPermission = await hasPermission(requestor, 'setFinalDay');
665-
if (!(hasChangeStatusPermission && hasFinalDayPermission && canEditProtectedAccount)) {
665+
const hasPausePermission = await hasPermission(requestor, 'interactWithPauseUserButton');
666+
if (
667+
!(
668+
((hasChangeStatusPermission && hasFinalDayPermission) || hasPausePermission) &&
669+
canEditProtectedAccount
670+
)
671+
) {
666672
if (PROTECTED_EMAIL_ACCOUNT.includes(requestor.email)) {
667673
logger.logInfo(
668674
`Unauthorized attempt to change protected user status. Requestor: ${requestor.requestorId} Target: ${userId}`,
@@ -929,7 +935,12 @@ const createControllerMethods = function (UserProfile, Project, cache) {
929935
};
930936

931937
const getUserProfiles = async function (req, res) {
932-
if (!(await checkPermission(req, 'getUserProfiles'))) {
938+
if (
939+
!(
940+
(await checkPermission(req, 'getUserProfiles')) ||
941+
(await checkPermission(req, 'interactWithPauseUserButton'))
942+
)
943+
) {
933944
return forbidden(res, 'You are not authorized to view all users');
934945
}
935946

@@ -1233,9 +1244,7 @@ const createControllerMethods = function (UserProfile, Project, cache) {
12331244
);
12341245

12351246
if (verificationUser.bioPosted !== bioPosted) {
1236-
console.error(
1237-
`WARNING: Database update failed! Expected: ${bioPosted}, Actual: ${verificationUser.bioPosted}`,
1238-
);
1247+
logger.logInfo('Database update failed while verifying bio status change.');
12391248
return res.status(500).json({ error: 'Failed to update bio status in database.' });
12401249
}
12411250

@@ -1651,7 +1660,7 @@ const createControllerMethods = function (UserProfile, Project, cache) {
16511660

16521661
await user.save();
16531662

1654-
console.log(`✅ Saved ${key} in DB:`, user[key]);
1663+
logger.logInfo(`Saved ${key} in database.`);
16551664

16561665
// ================================
16571666
// CACHE INVALIDATION (MERGED)
@@ -1903,7 +1912,6 @@ const createControllerMethods = function (UserProfile, Project, cache) {
19031912
}
19041913
return null;
19051914
};
1906-
19071915
const changeUserStatus = async function (req, res) {
19081916
const { userId } = req.params;
19091917
const { action, endDate, reactivationDate } = req.body;
@@ -2031,6 +2039,74 @@ const createControllerMethods = function (UserProfile, Project, cache) {
20312039
}
20322040
};
20332041

2042+
const pauseResumeUser = async function (req, res) {
2043+
const { userId } = req.params;
2044+
const activationDate = req.body.reactivationDate;
2045+
const status = req.body.status === 'Active';
2046+
2047+
if (!mongoose.Types.ObjectId.isValid(userId)) {
2048+
return res.status(400).send({ error: 'Bad Request' });
2049+
}
2050+
2051+
const canEditProtectedAccount = await canRequestorUpdateUser(
2052+
req.body.requestor.requestorId,
2053+
userId,
2054+
);
2055+
2056+
if (
2057+
!(
2058+
(await hasPermission(req.body.requestor, 'interactWithPauseUserButton')) &&
2059+
canEditProtectedAccount
2060+
)
2061+
) {
2062+
if (PROTECTED_EMAIL_ACCOUNT.includes(req.body.requestor.email)) {
2063+
logger.logInfo(
2064+
`Unauthorized attempt to change protected user status. Requestor: ${req.body.requestor.requestorId} Target: ${userId}`,
2065+
);
2066+
}
2067+
return res.status(403).send('You are not authorized to change user status');
2068+
}
2069+
2070+
cache.removeCache(`user-${userId}`);
2071+
2072+
try {
2073+
const user = await UserProfile.findById(userId, 'isActive email firstName lastName');
2074+
if (!user) {
2075+
return res.status(404).send({ error: 'User not found' });
2076+
}
2077+
2078+
user.set({
2079+
isActive: status,
2080+
reactivationDate: activationDate,
2081+
});
2082+
2083+
await user.save();
2084+
2085+
const isUserInCache = cache.hasCache('allusers');
2086+
if (isUserInCache) {
2087+
const allUserData = JSON.parse(cache.getCache('allusers'));
2088+
const userIdx = allUserData.findIndex((u) => u._id === userId);
2089+
if (userIdx !== -1) {
2090+
const userData = allUserData[userIdx];
2091+
userData.isActive = user.isActive;
2092+
allUserData.splice(userIdx, 1, userData);
2093+
cache.setCache('allusers', JSON.stringify(allUserData));
2094+
}
2095+
}
2096+
2097+
auditIfProtectedAccountUpdated({
2098+
requestorId: req.body.requestor.requestorId,
2099+
updatedRecordEmail: user.email,
2100+
actionPerformed: 'UserStatusUpdate',
2101+
});
2102+
2103+
return res.status(200).send({ message: 'status updated' });
2104+
} catch (error) {
2105+
logger.logException(error);
2106+
return res.status(500).send({ error: 'Internal Error' });
2107+
}
2108+
};
2109+
20342110
const changeUserRehireableStatus = async function (req, res) {
20352111
const { userId } = req.params;
20362112
const { isRehireable } = req.body;
@@ -2955,8 +3031,8 @@ const createControllerMethods = function (UserProfile, Project, cache) {
29553031
}
29563032
return res.status(200).json(result.data);
29573033
} catch (error) {
2958-
console.error('Error fetching skill data:', error);
2959-
return res.status(500).send({ error: error.message });
3034+
logger.logException(error);
3035+
return res.status(500).send({ error: 'Internal Error' });
29603036
}
29613037
};
29623038

@@ -2976,6 +3052,7 @@ const createControllerMethods = function (UserProfile, Project, cache) {
29763052
getTeamMembersofUser,
29773053
getProjectMembers,
29783054
changeUserStatus,
3055+
pauseResumeUser,
29793056
resetPassword,
29803057
getUserByName,
29813058
getAllUsersWithFacebookLink,

src/helpers/userHelper.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2598,15 +2598,15 @@ const userHelper = function () {
25982598
});
25992599

26002600
let badgeOfType;
2601-
for (let i = 0; i < badgeCollection.length; i += 1) {
2602-
if (badgeCollection[i].badge?.type === 'Lead a team of X+') {
2603-
if (badgeOfType && badgeOfType.people <= badgeCollection[i].badge.people) {
2601+
for (const badgeEntry of badgeCollection) {
2602+
if (badgeEntry.badge?.type === 'Lead a team of X+') {
2603+
if (badgeOfType && badgeOfType.people <= badgeEntry.badge.people) {
26042604
await removeDupBadge(personId, badgeOfType._id);
2605-
badgeOfType = badgeCollection[i].badge;
2606-
} else if (badgeOfType && badgeOfType.people > badgeCollection[i].badge.people) {
2607-
await removeDupBadge(personId, badgeCollection[i].badge._id);
2605+
badgeOfType = badgeEntry.badge;
2606+
} else if (badgeOfType && badgeOfType.people > badgeEntry.badge.people) {
2607+
await removeDupBadge(personId, badgeEntry.badge._id);
26082608
} else if (!badgeOfType) {
2609-
badgeOfType = badgeCollection[i].badge;
2609+
badgeOfType = badgeEntry.badge;
26102610
}
26112611
}
26122612
}

src/routes/userProfileRouter.js

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,7 @@ const routes = function (userProfile, project) {
66
const controller = require('../controllers/userProfileController')(userProfile, project);
77
const userProfileRouter = express.Router();
88

9-
// Skills radar route
10-
userProfileRouter.get('/userProfile/:userId/skills-radar', async (req, res) => {
11-
try {
12-
if (typeof controller.getUserSkillRadarData === 'function') {
13-
await controller.getUserSkillRadarData(req, res);
14-
} else {
15-
res.status(500).send('Controller function not found');
16-
}
17-
} catch (err) {
18-
res.status(500).send({ error: err.message });
19-
}
20-
});
9+
userProfileRouter.get('/userProfile/:userId/skills-radar', controller.getUserSkillRadarData);
2110

2211
// Other routes (unchanged, just formatted for clarity)
2312
userProfileRouter
@@ -120,6 +109,7 @@ const routes = function (userProfile, project) {
120109
.patch(controller.changeUserStatus);
121110

122111
userProfileRouter.route('/userProfile/name/:name').get(controller.getUserByName);
112+
userProfileRouter.route('/userProfile/:userId/pause').patch(controller.pauseResumeUser);
123113
userProfileRouter
124114
.route('/userProfile/:userId/rehireable')
125115
.patch(controller.changeUserRehireableStatus);

src/utilities/createInitialPermissions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ const permissionsRoles = [
108108
'accessHgnSkillsDashboard',
109109
'manageFAQs',
110110
'setFinalDay',
111+
'interactWithPauseUserButton',
111112
],
112113
},
113114
{
@@ -297,6 +298,7 @@ const permissionsRoles = [
297298
'manageHGNAccessSetup',
298299
'setFinalDay',
299300
'resendBlueSquareAndSummaryEmails',
301+
'interactWithPauseUserButton',
300302
],
301303
},
302304
];

src/utilities/playwrightUtil.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable no-console */
22
const { execSync } = require('node:child_process');
3+
// eslint-disable-next-line import/no-unresolved
34
const { chromium } = require('playwright');
45

56
async function ensureBrowserInstalled() {

0 commit comments

Comments
 (0)