Skip to content

Commit 2738d67

Browse files
authored
Merge branch 'development' into dependabot/npm_and_yarn/backend/tmp-0.2.4
2 parents 952bc0a + d5dbe7d commit 2738d67

40 files changed

+3429
-722
lines changed

.github/workflows/aws-backend-deploy.yml

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
run: |
3333
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
3434
INPUT_ENV=${{ github.event.inputs.env }}; INPUT_REF=${{ github.event.inputs.ref }}
35-
echo AWS_APPENV="$AWS_APP_NAME"-$INPUT_ENV >> $GITHUB_ENV
35+
echo AWS_APPENV=$AWS_APP_NAME >> $GITHUB_ENV
3636
echo IMAGE_TAG=$(git rev-parse --short HEAD) >> $GITHUB_ENV
3737
echo BUILD_SHA=$(git rev-parse --short HEAD) >> $GITHUB_ENV
3838
fi
@@ -43,18 +43,20 @@ jobs:
4343
build:
4444
name: Build & Push Docker Image
4545
runs-on: ubuntu-latest
46+
permissions:
47+
id-token: write # Needed for OIDC authentication to AWS
4648
needs: [setup_env]
4749
steps:
4850
- name: Checkout
4951
uses: actions/checkout@v4
5052
with:
5153
ref: ${{ github.event.inputs.ref }}
5254
- name: Configure AWS credentials
53-
uses: aws-actions/configure-aws-credentials@v4
55+
uses: aws-actions/configure-aws-credentials@v3 # Sets AWS credentials for CLI
5456
with:
55-
aws-access-key-id: ${{ secrets.INCUBATOR_AWS_ACCESS_KEY_ID }}
56-
aws-secret-access-key: ${{ secrets.INCUBATOR_AWS_SECRET_ACCESS_KEY }}
57-
aws-region: ${{ env.AWS_REGION }}
57+
role-to-assume: arn:aws:iam::035866691871:role/incubator-cicd-vrms # IAM role for deploy
58+
role-session-name: incubator-cicd-vrms-gha # Session name for audit
59+
aws-region: us-west-2 # AWS region
5860
- name: Login to Amazon ECR
5961
id: login-ecr
6062
uses: aws-actions/amazon-ecr-login@v2
@@ -85,42 +87,27 @@ jobs:
8587
--push \
8688
--build-arg BUILD_SHA=$BUILD_SHA \
8789
-f ${{ env.DOCKERFILE }} \
88-
-t $ECR_REGISTRY/$ECR_REPOSITORY:latest \
90+
-t $ECR_REGISTRY/$ECR_REPOSITORY:${{ github.event.inputs.env }} \
8991
-t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
9092
${{ env.DOCKER_PATH }}
9193
deploy:
9294
name: Deploy to AWS ECS
9395
runs-on: ubuntu-latest
9496
needs: [setup_env, build]
97+
permissions:
98+
id-token: write # Needed for OIDC authentication to AWS
9599
steps:
96100
- name: Configure AWS credentials
97-
uses: aws-actions/configure-aws-credentials@v4
101+
uses: aws-actions/configure-aws-credentials@v3 # Sets AWS credentials for CLI
98102
with:
99-
aws-access-key-id: ${{ secrets.INCUBATOR_AWS_ACCESS_KEY_ID }}
100-
aws-secret-access-key: ${{ secrets.INCUBATOR_AWS_SECRET_ACCESS_KEY }}
101-
aws-region: ${{ env.AWS_REGION }}
102-
- name: Login to Amazon ECR
103-
id: login-ecr
104-
uses: aws-actions/amazon-ecr-login@v2
105-
- name: Pull Task Definition & write to file
106-
id: aws-task-definition
103+
role-to-assume: arn:aws:iam::035866691871:role/incubator-cicd-vrms # IAM role for deploy
104+
role-session-name: incubator-cicd-vrms-gha # Session name for audit
105+
aws-region: us-west-2 # AWS region
106+
- name: Restart ECS Service
107+
id: redeploy-service
108+
env:
109+
SERVICE_NAME: ${{env.AWS_APP_NAME}}-${{ github.event.inputs.env }} # ECS service name
110+
# Force a new deployment of the ECS service to use the latest Docker image
107111
run: |
108-
aws ecs describe-task-definition \
109-
--task-definition ${{ needs.setup_env.outputs.AWS_APPENV }} \
110-
--query taskDefinition | \
111-
jq 'del(.taskDefinitionArn,.revision,.status,.registeredBy,.registeredAt,.compatibilities,.requiresAttributes)' > task-def.json
112-
- name: Interpolate new Docker Image into Task Definition
113-
id: task-definition
114-
uses: aws-actions/amazon-ecs-render-task-definition@v1
115-
with:
116-
task-definition: task-def.json
117-
container-name: ${{ needs.setup_env.outputs.AWS_APPENV }}
118-
image: ${{ steps.login-ecr.outputs.registry }}/${{ needs.setup_env.outputs.AWS_APPENV }}:${{ needs.setup_env.outputs.IMAGE_TAG }}
119-
- name: Deploy Amazon ECS
120-
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
121-
with:
122-
task-definition: ${{ steps.task-definition.outputs.task-definition }}
123-
service: ${{ needs.setup_env.outputs.AWS_APPENV }}
124-
cluster: ${{ env.AWS_SHARED_CLUSTER }}
125-
wait-for-service-stability: true
126-
wait-for-minutes: 5
112+
aws ecs update-service --force-new-deployment --service $SERVICE_NAME --cluster $AWS_SHARED_CLUSTER
113+

backend/controllers/project.controller.js

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

34
const ProjectController = {};
45

@@ -66,4 +67,67 @@ ProjectController.destroy = async function (req, res) {
6667
}
6768
};
6869

70+
ProjectController.updateManagedByUsers = async function (req, res) {
71+
const { ProjectId } = req.params;
72+
const { action, userId } = req.body; // action - 'add' or 'remove'
73+
74+
try {
75+
// Update project's managedByUsers and the user's managedProjects
76+
const project = await Project.findById(ProjectId);
77+
let managedByUsers = project.managedByUsers || [];
78+
79+
const user = await User.findById(userId);
80+
let managedProjects = user.managedProjects || [];
81+
82+
if (action === 'add') {
83+
managedByUsers = [...new Set([...managedByUsers, userId])];
84+
managedProjects = [...new Set([...managedProjects, ProjectId])];
85+
} else {
86+
// remove case
87+
managedByUsers = managedByUsers.filter((id) => id !== userId);
88+
managedProjects = managedProjects.filter((id) => id !== ProjectId);
89+
}
90+
91+
// Update project's managedByUsers
92+
project.managedByUsers = managedByUsers;
93+
await project.save({ validateBeforeSave: false });
94+
95+
// Update user's managedProjects
96+
user.managedProjects = managedProjects;
97+
await user.save({ validateBeforeSave: false });
98+
99+
return res.status(200).send({ project, user });
100+
} catch (err) {
101+
console.log(err);
102+
return res.sendStatus(400);
103+
}
104+
};
105+
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+
69133
module.exports = ProjectController;

backend/controllers/user.controller.js

Lines changed: 110 additions & 15 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');
@@ -21,6 +22,25 @@ UserController.user_list = async function (req, res) {
2122
try {
2223
const user = await User.find(query);
2324
return res.status(200).send(user);
25+
} catch (err) {
26+
console.error(err);
27+
return res.sendStatus(400);
28+
}
29+
};
30+
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);
2444
} catch (err) {
2545
console.log(err);
2646
return res.sendStatus(400);
@@ -39,7 +59,7 @@ UserController.admin_list = async function (req, res) {
3959
const admins = await User.find({ accessLevel: { $in: ['admin', 'superadmin'] } });
4060
return res.status(200).send(admins);
4161
} catch (err) {
42-
console.log(err);
62+
console.error(err);
4363
return res.sendStatus(400);
4464
}
4565
};
@@ -53,17 +73,24 @@ UserController.projectManager_list = async function (req, res) {
5373

5474
try {
5575
const projectManagers = await User.find({
56-
$and: [
57-
{ accessLevel: { $in: ['admin', 'superadmin'] } },
58-
{ managedProjects: { $exists: true, $type: 'array', $ne: [] } },
59-
],
76+
managedProjects: { $exists: true, $type: 'array', $ne: [] },
6077
});
6178

6279
// Collect all unique project IDs
63-
const allProjectIds = [...new Set(projectManagers.flatMap((pm) => pm.managedProjects))];
80+
const allProjectIds = [
81+
...new Set(
82+
projectManagers
83+
.flatMap((pm) => pm.managedProjects)
84+
.filter((id) => typeof id === 'string' && id.match(/^[a-f\d]{24}$/i)),
85+
),
86+
];
6487

6588
// Fetch all projects in one query
66-
const projects = await Project.find({ _id: { $in: allProjectIds } });
89+
const projects = await Project.find(
90+
{ _id: { $in: allProjectIds } },
91+
{ _id: 1, name: 1 }, // projection
92+
);
93+
6794
const projectIdToName = {};
6895
for (const project of projects) {
6996
projectIdToName[project._id.toString()] = project.name;
@@ -80,7 +107,8 @@ UserController.projectManager_list = async function (req, res) {
80107

81108
return res.status(200).send(updatedProjectManagers);
82109
} catch (err) {
83-
console.log(err);
110+
console.error(err);
111+
console.log('Projectlead error', err);
84112
return res.sendStatus(400);
85113
}
86114
};
@@ -96,11 +124,9 @@ UserController.user_by_id = async function (req, res) {
96124

97125
try {
98126
const user = await User.findById(UserId);
99-
// TODO throw 404 if User.findById returns empty object
100-
// and look downstream to see whether 404 would break anything
101127
return res.status(200).send(user);
102128
} catch (err) {
103-
console.log(err);
129+
console.error(err);
104130
return res.sendStatus(400);
105131
}
106132
};
@@ -144,7 +170,7 @@ UserController.update = async function (req, res) {
144170
const user = await User.findOneAndUpdate({ _id: UserId }, req.body, { new: true });
145171
return res.status(200).send(user);
146172
} catch (err) {
147-
console.log(err);
173+
console.error(err);
148174
return res.sendStatus(400);
149175
}
150176
};
@@ -162,7 +188,7 @@ UserController.delete = async function (req, res) {
162188
const user = await User.findByIdAndDelete(UserId);
163189
return res.status(200).send(user);
164190
} catch (err) {
165-
console.log(err);
191+
console.error(err);
166192
return res.sendStatus(400);
167193
}
168194
};
@@ -232,7 +258,6 @@ UserController.signin = function (req, res) {
232258
};
233259

234260
UserController.verifySignIn = async function (req, res) {
235-
236261
let token = req.headers['x-access-token'] || req.headers['authorization'];
237262
if (token.startsWith('Bearer ')) {
238263
// Remove Bearer from string
@@ -245,7 +270,7 @@ UserController.verifySignIn = async function (req, res) {
245270
res.cookie('token', token, { httpOnly: true });
246271
return res.send(user);
247272
} catch (err) {
248-
console.log(err);
273+
console.error(err);
249274
return res.status(403);
250275
}
251276
};
@@ -259,4 +284,74 @@ UserController.logout = async function (req, res) {
259284
return res.clearCookie('token').status(200).send('Successfully logged out.');
260285
};
261286

287+
// Update user's managedProjects
288+
UserController.updateManagedProjects = async function (req, res) {
289+
const { headers } = req;
290+
const { UserId } = req.params;
291+
const { action, projectId } = req.body; // action - 'add' or 'remove'
292+
// console.log('action:', action, 'projectId:', projectId);
293+
294+
if (headers['x-customrequired-header'] !== expectedHeader) {
295+
return res.sendStatus(403);
296+
}
297+
298+
try {
299+
// Update user's managedProjects and the project's managedByUsers
300+
const user = await User.findById(UserId);
301+
let managedProjects = user.managedProjects || [];
302+
303+
const project = await Project.findById(projectId);
304+
let managedByUsers = project.managedByUsers || [];
305+
306+
if (action === 'add') {
307+
managedProjects = [...managedProjects, projectId];
308+
managedByUsers = [...managedByUsers, UserId];
309+
} else {
310+
// remove case
311+
managedProjects = managedProjects.filter((id) => id !== projectId);
312+
managedByUsers = managedByUsers.filter((id) => id !== UserId);
313+
}
314+
315+
// Update user's managedProjects
316+
user.managedProjects = managedProjects;
317+
await user.save({ validateBeforeSave: false });
318+
319+
// Update project's managedByUsers
320+
project.managedByUsers = managedByUsers;
321+
await project.save({ validateBeforeSave: false });
322+
323+
return res.status(200).send({ user, project });
324+
} catch (err) {
325+
console.log(err);
326+
return res.sendStatus(400);
327+
}
328+
};
329+
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+
262357
module.exports = UserController;

backend/models/user.model.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const userSchema = mongoose.Schema({
88
firstName: { type: String },
99
lastName: { type: String },
1010
},
11-
email: { type: String, unique: true },
11+
email: { type: String, unique: true, lowercase: true },
1212
accessLevel: {
1313
type: String,
1414
enum: ["user", "admin", "superadmin"], // restricts values to "user", "admin" and "superadmin"
@@ -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 }

0 commit comments

Comments
 (0)