Skip to content

Commit dac077f

Browse files
authored
Merge pull request #1977 from jng34/userPermissionFixProjectMembers
Bug Fix to show both Admins and Users in Project Members search
2 parents fbf39f7 + 96478f4 commit dac077f

File tree

8 files changed

+300
-554
lines changed

8 files changed

+300
-554
lines changed

backend/controllers/user.controller.js

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ UserController.admin_list = async function (req, res) {
4242
}
4343
};
4444

45-
// Get list of Users with accessLevel 'admin' or 'superadmin' and also managed projects with GET
46-
UserController.projectLead_list = async function (req, res) {
45+
UserController.projectManager_list = async function (req, res) {
4746
const { headers } = req;
4847

4948
if (headers['x-customrequired-header'] !== expectedHeader) {
@@ -52,33 +51,68 @@ UserController.projectLead_list = async function (req, res) {
5251

5352
try {
5453
const projectManagers = await User.find({
55-
$and: [
56-
{ accessLevel: { $in: ['admin', 'superadmin'] } },
57-
{ managedProjects: { $exists: true, $type: 'array', $ne: [] } },
58-
],
54+
managedProjects: { $exists: true, $type: 'array', $not: { $size: 0 } },
55+
});
56+
57+
// Filter out managers with empty arrays early
58+
const validProjectManagers = projectManagers.filter(
59+
(manager) => manager.managedProjects && manager.managedProjects.length > 0,
60+
);
61+
62+
if (validProjectManagers.length === 0) {
63+
return res.status(200).send([]);
64+
}
65+
66+
// Collect all unique project IDs to fetch in one query
67+
const allProjectIds = new Set();
68+
validProjectManagers.forEach((manager) => {
69+
manager.managedProjects.forEach((projectId) => {
70+
// Filter out invalid project IDs (non-string or obviously invalid values)
71+
if (typeof projectId === 'string' && projectId !== 'false' && projectId.length > 0) {
72+
allProjectIds.add(projectId);
73+
}
74+
});
75+
});
76+
77+
// Fetch all projects in a single query
78+
const projects = await Project.find({ _id: { $in: Array.from(allProjectIds) } });
79+
80+
// Create a map for O(1) lookup
81+
const projectMap = new Map();
82+
projects.forEach((project) => {
83+
projectMap.set(project._id.toString(), project.name);
5984
});
6085

6186
const updatedProjectManagers = [];
6287

63-
for (const projectManager of projectManagers) {
88+
for (const projectManager of validProjectManagers) {
6489
const projectManagerObj = projectManager.toObject();
65-
projectManagerObj.isProjectLead = true;
90+
projectManagerObj.isProjectMember = true;
6691
const projectNames = [];
6792

6893
for (const projectId of projectManagerObj.managedProjects) {
69-
const projectDetail = await Project.findById(projectId);
70-
if (projectDetail && projectDetail.name) {
71-
projectNames.push(projectDetail.name);
72-
} else {
73-
console.warn('Project detail is null, cannot access name');
94+
// using try-catch block because old user data had invalid strings (aka 'false') for ProjectIds
95+
try {
96+
const projectName = projectMap.get(projectId.toString());
97+
if (projectName) {
98+
projectNames.push(projectName);
99+
} else {
100+
console.warn('Project detail is null, cannot access name');
101+
}
102+
} catch (error) {
103+
console.warn('Failed to fetch project details for ID:', projectId, error);
74104
}
75105
}
76-
projectManagerObj.managedProjectNames = projectNames;
77106

78-
updatedProjectManagers.push(projectManagerObj);
107+
if (projectNames.length) {
108+
projectManagerObj.managedProjectNames = projectNames;
109+
updatedProjectManagers.push(projectManagerObj);
110+
}
79111
}
112+
80113
return res.status(200).send(updatedProjectManagers);
81114
} catch (err) {
115+
console.log('Projectlead error', err);
82116
return res.sendStatus(400);
83117
}
84118
};

backend/models/project.model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Idea for the future: programmingLanguages, numberGithubContributions (pull these
1616
*/
1717

1818
const projectSchema = mongoose.Schema({
19-
name: { type: String, trim: true },
19+
name: { type: String, trim: true, required: true },
2020
description: { type: String, trim: true },
2121
githubIdentifier: { type: String, trim: true },
2222
projectStatus: { type: String }, // Active, Completed, or Paused

backend/models/user.model.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const mongoose = require("mongoose");
1+
const mongoose = require('mongoose');
22
// const bcrypt = require('bcrypt-nodejs');
33

44
mongoose.Promise = global.Promise;
@@ -9,10 +9,10 @@ const userSchema = mongoose.Schema({
99
lastName: { type: String },
1010
},
1111
email: { type: String, unique: true },
12-
accessLevel: {
13-
type: String,
14-
enum: ["user", "admin", "superadmin"], // restricts values to "user", "admin" and "superadmin"
15-
default: "user"
12+
accessLevel: {
13+
type: String,
14+
enum: ['user', 'admin', 'superadmin'], // restricts values to "user", "admin" and "superadmin"
15+
default: 'user',
1616
},
1717
createdDate: { type: Date, default: Date.now },
1818
currentRole: { type: String }, // will remove but need to update check-in form
@@ -27,7 +27,7 @@ const userSchema = mongoose.Schema({
2727
projects: [
2828
{
2929
type: mongoose.Schema.Types.ObjectId,
30-
ref: "Project",
30+
ref: 'Project',
3131
},
3232
],
3333
githubHandle: { type: String }, // handle not including @, not the URL
@@ -37,10 +37,15 @@ 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: [
41+
{
42+
type: mongoose.Schema.Types.ObjectId,
43+
ref: 'Project',
44+
},
45+
], // Which projects managed by user.
4146
//currentProject: { type: String } // no longer need this as we can get it from Project Team Member table
4247
// password: { type: String, required: true }
43-
isActive: { type: Boolean, default: true }
48+
isActive: { type: Boolean, default: true },
4449
});
4550

4651
userSchema.methods.serialize = function () {
@@ -71,10 +76,10 @@ userSchema.methods.serialize = function () {
7176
githubPublic2FA: this.githubPublic2FA,
7277
availability: this.availability,
7378
managedProjects: this.managedProjects,
74-
isActive: this.isActive
79+
isActive: this.isActive,
7580
};
7681
};
7782

78-
const User = mongoose.model("User", userSchema);
83+
const User = mongoose.model('User', userSchema);
7984

8085
module.exports = { User };

backend/routers/users.router.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ router.get('/', UserController.user_list);
88

99
router.get('/admins', UserController.admin_list);
1010

11-
router.get('/projectManagers', UserController.projectLead_list);
11+
router.get('/projectManagers', UserController.projectManager_list);
1212

1313
router.post('/', UserController.create);
1414

0 commit comments

Comments
 (0)