Skip to content

Commit 68ea9e7

Browse files
authored
Merge pull request #2016 from jng34/manageProjMembers1984
Component for managing project members in Projects
2 parents d5b7014 + 9057d93 commit 68ea9e7

File tree

13 files changed

+679
-10
lines changed

13 files changed

+679
-10
lines changed

backend/controllers/project.controller.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { Project, User } = require('../models');
2+
const { ObjectId } = require('mongodb');
23

34
const ProjectController = {};
45

@@ -87,11 +88,11 @@ ProjectController.updateManagedByUsers = async function (req, res) {
8788
managedProjects = managedProjects.filter((id) => id !== ProjectId);
8889
}
8990

90-
// Update project's managedByUsers
91+
// Update project's managedByUsers
9192
project.managedByUsers = managedByUsers;
9293
await project.save({ validateBeforeSave: false });
9394

94-
// Update user's managedProjects
95+
// Update user's managedProjects
9596
user.managedProjects = managedProjects;
9697
await user.save({ validateBeforeSave: false });
9798

@@ -102,4 +103,31 @@ ProjectController.updateManagedByUsers = async function (req, res) {
102103
}
103104
};
104105

106+
ProjectController.bulkUpdateManagedByUsers = async function (req, res) {
107+
const { bulkOps } = req.body;
108+
109+
// Convert string IDs to ObjectId in bulkOps
110+
bulkOps.forEach((op) => {
111+
if (op?.updateOne?.filter._id) {
112+
op.updateOne.filter._id = ObjectId(op.updateOne.filter._id);
113+
}
114+
if (op?.updateOne?.update) {
115+
const update = op.updateOne.update;
116+
if (update?.$addToSet?.managedByUsers) {
117+
update.$addToSet.managedByUsers = ObjectId(update.$addToSet.managedByUsers);
118+
}
119+
if (update?.$pull?.managedByUsers) {
120+
update.$pull.managedByUsers = ObjectId(update.$pull.managedByUsers);
121+
}
122+
}
123+
});
124+
125+
try {
126+
const result = await Project.bulkWrite(bulkOps);
127+
res.status(200).json(result);
128+
} catch (err) {
129+
res.status(500).json({ error: err.message });
130+
}
131+
};
132+
105133
module.exports = ProjectController;

backend/controllers/user.controller.js

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const jwt = require('jsonwebtoken');
2+
const { ObjectId } = require('mongodb');
23

34
const EmailController = require('./email.controller');
45
const { CONFIG_AUTH } = require('../config');
@@ -27,6 +28,25 @@ UserController.user_list = async function (req, res) {
2728
}
2829
};
2930

31+
UserController.user_by_email = async function (req, res) {
32+
const { headers } = req;
33+
const { email } = req.params;
34+
35+
console.log('email: ', email);
36+
37+
if (headers['x-customrequired-header'] !== expectedHeader) {
38+
return res.sendStatus(403);
39+
}
40+
41+
try {
42+
const user = await User.find({ email });
43+
return res.status(200).send(user);
44+
} catch (err) {
45+
console.log(err);
46+
return res.sendStatus(400);
47+
}
48+
};
49+
3050
// Get list of Users with accessLevel 'admin' or 'superadmin' with GET
3151
UserController.admin_list = async function (req, res) {
3252
const { headers } = req;
@@ -104,8 +124,6 @@ UserController.user_by_id = async function (req, res) {
104124

105125
try {
106126
const user = await User.findById(UserId);
107-
// TODO throw 404 if User.findById returns empty object
108-
// and look downstream to see whether 404 would break anything
109127
return res.status(200).send(user);
110128
} catch (err) {
111129
console.error(err);
@@ -294,7 +312,7 @@ UserController.updateManagedProjects = async function (req, res) {
294312
managedByUsers = managedByUsers.filter((id) => id !== UserId);
295313
}
296314

297-
// Update user's managedProjects
315+
// Update user's managedProjects
298316
user.managedProjects = managedProjects;
299317
await user.save({ validateBeforeSave: false });
300318

@@ -309,4 +327,31 @@ UserController.updateManagedProjects = async function (req, res) {
309327
}
310328
};
311329

330+
UserController.bulkUpdateManagedProjects = async function (req, res) {
331+
const { bulkOps } = req.body;
332+
333+
// Convert string IDs to ObjectId in bulkOps
334+
bulkOps.forEach((op) => {
335+
if (op?.updateOne?.filter._id) {
336+
op.updateOne.filter._id = ObjectId(op.updateOne.filter._id);
337+
}
338+
if (op?.updateOne?.update) {
339+
const update = op.updateOne.update;
340+
if (update?.$addToSet?.managedProjects) {
341+
update.$addToSet.managedProjects = ObjectId(update.$addToSet.managedProjects);
342+
}
343+
if (update?.$pull?.managedProjects) {
344+
update.$pull.managedProjects = ObjectId(update.$pull.managedProjects);
345+
}
346+
}
347+
});
348+
349+
try {
350+
const result = await User.bulkWrite(bulkOps);
351+
res.status(200).json(result);
352+
} catch (err) {
353+
res.status(500).json({ error: err.message });
354+
}
355+
};
356+
312357
module.exports = UserController;

backend/models/user.model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const userSchema = mongoose.Schema({
3737
isHflaGithubMember: { type: Boolean }, // pull from API once github handle in place?
3838
githubPublic2FA: { type: Boolean }, // does the user have 2FA enabled on their github and membership set to public?
3939
availability: { type: String }, // availability to meet outside of hacknight times; string for now, more structured in future
40-
managedProjects: [{ type: String}], // Which projects managed by user.
40+
managedProjects: [{ type: String }], // Which projects managed by user.
4141
//currentProject: { type: String } // no longer need this as we can get it from Project Team Member table
4242
// password: { type: String, required: true }
4343
isActive: { type: Boolean, default: true }

backend/routers/projects.router.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ router.put('/', ProjectController.pm_filtered_projects);
1212

1313
router.post('/', AuthUtil.verifyCookie, ProjectController.create);
1414

15-
router.get('/:ProjectId', ProjectController.project_by_id);
15+
router.get('/:ProjectId', AuthUtil.verifyCookie, ProjectController.project_by_id);
1616

1717
router.put('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update);
1818

1919
// Update project's managedByUsers in db
2020
router.patch('/:ProjectId', AuthUtil.verifyCookie, ProjectController.updateManagedByUsers);
2121

22+
// Bulk update for editing project members
23+
router.post('/bulk-updates', AuthUtil.verifyCookie, ProjectController.bulkUpdateManagedByUsers);
24+
2225
module.exports = router;

backend/routers/users.router.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ const { UserController } = require('../controllers');
66
// The base is /api/users
77
router.get('/', UserController.user_list);
88

9+
router.get('/id/:UserId', UserController.user_by_id);
10+
11+
router.get('/email/:email', UserController.user_by_email);
12+
913
router.get('/admins', UserController.admin_list);
1014

1115
router.get('/projectManagers', UserController.projectManager_list);
1216

1317
router.post('/', UserController.create);
1418

15-
router.get('/:UserId', UserController.user_by_id);
19+
router.post('/bulk-updates', UserController.bulkUpdateManagedProjects);
1620

1721
router.patch('/:UserId', UserController.update);
1822

19-
// Update user projects in db
2023
router.patch('/:UserId/managedProjects', UserController.updateManagedProjects);
2124

2225
router.delete('/:UserId', UserController.delete);

client/src/api/ProjectApiService.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,35 @@ class ProjectApiService {
119119
return undefined;
120120
}
121121
}
122+
123+
async fetchManagedByUsers(projectId) {
124+
const url = `${this.baseProjectUrl}${projectId}`;
125+
try {
126+
const res = await fetch(url, {
127+
headers: this.headers,
128+
method: 'GET',
129+
});
130+
return await res.json();
131+
} catch (error) {
132+
console.error(`fetchManagedByUsers error: ${error}`);
133+
alert('Server not responding. Please refresh the page.');
134+
}
135+
}
136+
137+
async bulkUpdateManagedByUsers(bulkOps) {
138+
const url = `${this.baseProjectUrl}bulk-updates`;
139+
try {
140+
const res = await fetch(url, {
141+
method: 'POST',
142+
headers: this.headers,
143+
body: JSON.stringify({ bulkOps }),
144+
});
145+
return await res.json();
146+
} catch (error) {
147+
console.error(`bulkUpdateManagedByUsers error: ${error}`);
148+
alert('Server not responding. Please refresh the page.');
149+
}
150+
}
122151
}
123152

124153
export default ProjectApiService;

client/src/api/UserApiService.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,36 @@ class UserApiService {
1414
const res = await fetch(this.baseUserUrl, {
1515
headers: this.headers,
1616
});
17+
console.log(res);
18+
return await res.json();
19+
} catch (error) {
20+
console.error(`fetchUsers error: ${error}`);
21+
alert('Server not responding. Please refresh the page.');
22+
}
23+
return [];
24+
}
25+
26+
async fetchUserById(id) {
27+
try {
28+
const uri = `${this.baseUserUrl}id/${id}`;
29+
const res = await fetch(uri, {
30+
headers: this.headers,
31+
});
32+
return await res.json();
33+
} catch (error) {
34+
console.error(`fetchUsers error: ${error}`);
35+
alert('Server not responding. Please refresh the page.');
36+
}
37+
return [];
38+
}
39+
40+
// Fetch user by email
41+
async fetchUserByEmail(email) {
42+
try {
43+
const uri = `${this.baseUserUrl}email/${email}`;
44+
const res = await fetch(uri, {
45+
headers: this.headers,
46+
});
1747
return await res.json();
1848
} catch (error) {
1949
console.error(`fetchUsers error: ${error}`);
@@ -118,6 +148,21 @@ class UserApiService {
118148
alert('server not responding. Please try again.');
119149
}
120150
}
151+
152+
async bulkUpdateManagedProjects(bulkOps) {
153+
const url = `${this.baseUserUrl}bulk-updates`;
154+
try {
155+
const res = await fetch(url, {
156+
method: 'POST',
157+
headers: this.headers,
158+
body: JSON.stringify({ bulkOps }),
159+
});
160+
return await res.json();
161+
} catch (error) {
162+
console.error(`bulkUpdateManagedProjects error: ${error}`);
163+
alert('Server not responding. Please try again.');
164+
}
165+
}
121166
}
122167

123168
export default UserApiService;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { CircularProgress, Grid } from "@mui/material";
2+
import { StyledButton } from '../../ProjectForm';
3+
4+
const ButtonGroup = ({ btnName1, btnName2, callBackFn1, callBackFn2, isLoading }) => (
5+
<Grid container justifyContent="space-evenly" sx={{ my: 3 }}>
6+
<Grid item xs="auto">
7+
<StyledButton
8+
sx="large"
9+
cursor="pointer"
10+
variant="contained"
11+
onClick={(btn) => callBackFn1(btn)}
12+
>
13+
{isLoading ? <CircularProgress /> : `${btnName1}`}
14+
</StyledButton>
15+
</Grid>
16+
<Grid item xs="auto">
17+
<StyledButton
18+
sx="large"
19+
cursor="pointer"
20+
variant="contained"
21+
onClick={callBackFn2}
22+
>
23+
{btnName2}
24+
</StyledButton>
25+
</Grid>
26+
</Grid>
27+
);
28+
29+
export default ButtonGroup

0 commit comments

Comments
 (0)