Skip to content

Commit 37270d5

Browse files
authored
Merge branch 'development' into fix/recurring-checkin-bulk
2 parents ef315e4 + 4b2ae51 commit 37270d5

File tree

12 files changed

+300
-156
lines changed

12 files changed

+300
-156
lines changed

backend/controllers/project.controller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,15 @@ ProjectController.bulkUpdateManagedByUsers = async function (req, res) {
109109
// Convert string IDs to ObjectId in bulkOps
110110
bulkOps.forEach((op) => {
111111
if (op?.updateOne?.filter._id) {
112-
op.updateOne.filter._id = ObjectId(op.updateOne.filter._id);
112+
op.updateOne.filter._id = new ObjectId(op.updateOne.filter._id);
113113
}
114114
if (op?.updateOne?.update) {
115115
const update = op.updateOne.update;
116116
if (update?.$addToSet?.managedByUsers) {
117-
update.$addToSet.managedByUsers = ObjectId(update.$addToSet.managedByUsers);
117+
update.$addToSet.managedByUsers = new ObjectId(update.$addToSet.managedByUsers);
118118
}
119119
if (update?.$pull?.managedByUsers) {
120-
update.$pull.managedByUsers = ObjectId(update.$pull.managedByUsers);
120+
update.$pull.managedByUsers = new ObjectId(update.$pull.managedByUsers);
121121
}
122122
}
123123
});

backend/controllers/user.controller.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ function generateAccessToken(user, auth_origin) {
204204
);
205205
}
206206

207-
UserController.createUser = function (req, res) {
207+
UserController.createUser = async function (req, res) {
208208
const { firstName, lastName, email } = req.body;
209209
const { origin } = req.headers;
210210

@@ -217,13 +217,12 @@ UserController.createUser = function (req, res) {
217217
accessLevel: 'user',
218218
});
219219

220-
// eslint-disable-next-line
221-
user.save((err, usr) => {
222-
if (err) {
223-
res.sendStatus(400);
224-
}
220+
try {
221+
await user.save();
225222
res.sendStatus(201);
226-
});
223+
} catch (err) {
224+
res.sendStatus(400);
225+
}
227226

228227
const jsonToken = generateAccessToken(user);
229228

@@ -333,15 +332,15 @@ UserController.bulkUpdateManagedProjects = async function (req, res) {
333332
// Convert string IDs to ObjectId in bulkOps
334333
bulkOps.forEach((op) => {
335334
if (op?.updateOne?.filter._id) {
336-
op.updateOne.filter._id = ObjectId(op.updateOne.filter._id);
335+
op.updateOne.filter._id = new ObjectId(op.updateOne.filter._id);
337336
}
338337
if (op?.updateOne?.update) {
339338
const update = op.updateOne.update;
340339
if (update?.$addToSet?.managedProjects) {
341-
update.$addToSet.managedProjects = ObjectId(update.$addToSet.managedProjects);
340+
update.$addToSet.managedProjects = new ObjectId(update.$addToSet.managedProjects);
342341
}
343342
if (update?.$pull?.managedProjects) {
344-
update.$pull.managedProjects = ObjectId(update.$pull.managedProjects);
343+
update.$pull.managedProjects = new ObjectId(update.$pull.managedProjects);
345344
}
346345
}
347346
});

backend/models/user.model.js

Lines changed: 9 additions & 11 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, lowercase: 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
@@ -23,11 +23,10 @@ const userSchema = mongoose.Schema({
2323
skillsToMatch: [{ type: String }], // skills the user either has or wants to learn - will use to match to projects
2424
firstAttended: { type: String },
2525
attendanceReason: { type: String },
26-
githubHandle: { type: String },
2726
projects: [
2827
{
2928
type: mongoose.Schema.Types.ObjectId,
30-
ref: "Project",
29+
ref: 'Project',
3130
},
3231
],
3332
githubHandle: { type: String }, // handle not including @, not the URL
@@ -40,7 +39,7 @@ const userSchema = mongoose.Schema({
4039
managedProjects: [{ type: String }], // Which projects managed by user.
4140
//currentProject: { type: String } // no longer need this as we can get it from Project Team Member table
4241
// password: { type: String, required: true }
43-
isActive: { type: Boolean, default: true }
42+
isActive: { type: Boolean, default: true },
4443
});
4544

4645
userSchema.methods.serialize = function () {
@@ -61,7 +60,6 @@ userSchema.methods.serialize = function () {
6160
skillsToMatch: this.skillsToMatch,
6261
firstAttended: this.firstAttended,
6362
attendanceReason: this.attendanceReason,
64-
githubHandle: this.githubHandle,
6563
projects: this.projects,
6664
//currentProject: this.currentProject
6765
phone: this.phone,
@@ -71,10 +69,10 @@ userSchema.methods.serialize = function () {
7169
githubPublic2FA: this.githubPublic2FA,
7270
availability: this.availability,
7371
managedProjects: this.managedProjects,
74-
isActive: this.isActive
72+
isActive: this.isActive,
7573
};
7674
};
7775

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

8078
module.exports = { User };

backend/models/user.model.test.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ describe('Unit tests for User Model', () => {
2828
skillsToMatch: ['Jest', 'Node.js'],
2929
firstAttended: '2025-01-01',
3030
attendanceReason: 'To learn and contribute',
31-
githubHandle: 'mockuser',
3231
projects: ['ProjectId1', 'ProjectId2'],
3332
phone: '123-456-7890',
3433
textingOk: true,
@@ -62,7 +61,6 @@ describe('Unit tests for User Model', () => {
6261
skillsToMatch: mockUser.skillsToMatch,
6362
firstAttended: mockUser.firstAttended,
6463
attendanceReason: mockUser.attendanceReason,
65-
githubHandle: mockUser.githubHandle,
6664
projects: mockUser.projects,
6765
phone: mockUser.phone,
6866
textingOk: mockUser.textingOk,
@@ -108,7 +106,6 @@ describe('Unit tests for User Model', () => {
108106
expect(mockUser.email).toBe(uppercaseEmail.toLowerCase());
109107
});
110108

111-
112109
it('should pass validation with valid user data', async () => {
113110
// Create a mock user with valid data
114111
const mockUser = new User({

backend/models/user.test.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
const mongoose = require('mongoose');
2+
const { User } = require('./user.model');
3+
const { setupIntegrationDB } = require('../setup-test');
4+
5+
setupIntegrationDB('user-model');
6+
7+
describe('User Model - Create and Read', () => {
8+
test('Save a model instance and then read from the db', async () => {
9+
const submittedData = {
10+
name: {
11+
firstName: 'Test',
12+
lastName: 'User',
13+
},
14+
email: 'test@test.com',
15+
accessLevel: 'user',
16+
createdDate: 1594023390039,
17+
currentRole: 'mage',
18+
desiredRole: 'warlock',
19+
newMember: true,
20+
currentJobTitle: 'freehand artist',
21+
desiredJobTitle: 'textile factory worker',
22+
skillsToMatch: ['marketing assistant'],
23+
firstAttended: 'year 0',
24+
attendanceReason: 'training',
25+
phone: '867-5309',
26+
textingOk: true,
27+
slackName: 'slacktestuser',
28+
};
29+
30+
await User.create(submittedData);
31+
const savedData = await User.findOne({ email: submittedData.email });
32+
expect(savedData.name.firstName).toBe(submittedData.name.firstName);
33+
expect(savedData.currentRole).toBe(submittedData.currentRole);
34+
expect(savedData.desiredJobTitle).toBe(submittedData.desiredJobTitle);
35+
});
36+
37+
test('Create a simple user', async () => {
38+
const submittedData = {
39+
name: {
40+
firstName: 'Simple',
41+
lastName: 'User',
42+
},
43+
email: 'simple@test.com',
44+
};
45+
46+
await User.create(submittedData);
47+
const savedData = await User.findOne({ email: submittedData.email });
48+
expect(savedData.name.firstName).toBe(submittedData.name.firstName);
49+
expect(savedData.email).toBe(submittedData.email);
50+
});
51+
});
52+
53+
describe('User Model - Serialization', () => {
54+
test('should serialize user data correctly', async () => {
55+
const userData = {
56+
name: { firstName: 'Serialize', lastName: 'Test' },
57+
email: 'serialize.test@example.com',
58+
accessLevel: 'admin',
59+
currentRole: 'Backend Developer',
60+
desiredRole: 'Lead Developer',
61+
newMember: false,
62+
currentJobTitle: 'Actual Current Job Title',
63+
desiredJobTitle: 'Actual Desired Job Title',
64+
skillsToMatch: ['JavaScript', 'MongoDB'],
65+
phone: '555-0101',
66+
textingOk: true,
67+
slackName: 'serializeslack',
68+
isHflaGithubMember: true,
69+
githubPublic2FA: false,
70+
availability: 'Evenings',
71+
managedProjects: ['ProjectGamma'],
72+
isActive: true,
73+
createdDate: new Date(2023, 0, 15),
74+
};
75+
76+
const user = await User.create(userData);
77+
const serializedUser = user.serialize();
78+
79+
expect(serializedUser.id.toString()).toBe(user._id.toString());
80+
expect(serializedUser.name.firstName).toBe(userData.name.firstName);
81+
expect(serializedUser.name.lastName).toBe(userData.name.lastName);
82+
expect(serializedUser.email).toBe(userData.email);
83+
expect(serializedUser.accessLevel).toBe(userData.accessLevel);
84+
expect(serializedUser.createdDate).toEqual(userData.createdDate);
85+
expect(serializedUser.currentRole).toBe(userData.currentRole);
86+
expect(serializedUser.desiredRole).toBe(userData.desiredRole);
87+
expect(serializedUser.newMember).toBe(userData.newMember);
88+
expect([...serializedUser.skillsToMatch]).toEqual(userData.skillsToMatch);
89+
expect(serializedUser.phone).toBe(userData.phone);
90+
expect(serializedUser.textingOk).toBe(userData.textingOk);
91+
expect(serializedUser.slackName).toBe(userData.slackName);
92+
expect(serializedUser.isHflaGithubMember).toBe(userData.isHflaGithubMember);
93+
expect(serializedUser.githubPublic2FA).toBe(userData.githubPublic2FA);
94+
expect(serializedUser.availability).toBe(userData.availability);
95+
expect([...serializedUser.managedProjects]).toEqual(userData.managedProjects);
96+
expect(serializedUser.isActive).toBe(userData.isActive);
97+
});
98+
});
99+
100+
describe('User Model - Validation', () => {
101+
test('should fail if email is not unique', async () => {
102+
const email = 'unique.validation@example.com';
103+
await User.create({
104+
name: { firstName: 'First', lastName: 'User' },
105+
email: email,
106+
accessLevel: 'user',
107+
});
108+
109+
await expect(
110+
User.create({
111+
name: { firstName: 'Second', lastName: 'User' },
112+
email: email,
113+
accessLevel: 'user',
114+
}),
115+
).rejects.toMatchObject({ code: 11000 });
116+
});
117+
118+
test('should fail if accessLevel is not in enum', async () => {
119+
const userData = {
120+
name: { firstName: 'Enum', lastName: 'Test' },
121+
email: 'enum.test@example.com',
122+
accessLevel: 'invalid_access_level',
123+
};
124+
125+
const user = new User(userData);
126+
await expect(user.save()).rejects.toBeInstanceOf(mongoose.Error.ValidationError);
127+
});
128+
});

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"helmet": "^3.22.0",
5353
"jsonwebtoken": "^8.5.1",
5454
"mongodb-memory-server": "^6.9.0",
55-
"mongoose": "^5.10.0",
55+
"mongoose": "^8.9.5",
5656
"morgan": "^1.10.0",
5757
"node-cron": "^2.0.3",
5858
"node-fetch": "^2.6.7",

backend/scripts/cloneOrSyncCollections.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ async function connectDbs() {
6969
// Connect PROD using Mongoose
7070
await mongoose.connect(process.env.PROD_DB_URI, {
7171
dbName: PROD_DB_NAME,
72-
useNewUrlParser: true,
73-
useUnifiedTopology: true,
7472
});
7573
// Connect DEV using MongoClient
7674
const dev = new MongoClient(process.env.DEV_DB_URI);

backend/server.js

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@ mongoose.Promise = global.Promise;
1212
let server;
1313
async function runServer(databaseUrl = CONFIG_DB.DATABASE_URL, port = CONFIG_DB.PORT) {
1414
await mongoose
15-
.connect(databaseUrl, {
16-
useNewUrlParser: true,
17-
useCreateIndex: true,
18-
useUnifiedTopology: true,
19-
useFindAndModify: false,
20-
})
15+
.connect(databaseUrl)
2116
.catch((err) => err);
2217

2318
server = app
@@ -48,35 +43,30 @@ async function closeServer() {
4843
});
4944
}
5045

51-
function initial() {
52-
Role.collection.estimatedDocumentCount((err, count) => {
53-
if (!err && count === 0) {
54-
new Role({
55-
name: "APP_USER",
56-
}).save((err) => {
57-
if (err) {
58-
console.log("error", err);
59-
}
46+
async function initial() {
47+
try {
48+
const count = await Role.collection.estimatedDocumentCount();
6049

61-
console.log("added 'user' to roles collection");
62-
});
50+
if (count === 0) {
51+
await new Role({
52+
name: "APP_USER",
53+
}).save();
54+
console.log("added 'user' to roles collection");
6355

64-
new Role({
56+
await new Role({
6557
name: "APP_ADMIN",
66-
}).save((err) => {
67-
if (err) {
68-
console.log("error", err);
69-
}
70-
71-
console.log("added 'moderator' to roles collection");
72-
});
58+
}).save();
59+
console.log("added 'moderator' to roles collection");
7360
}
74-
});
61+
} catch (err) {
62+
console.log("error", err);
63+
}
7564
}
7665

7766
if (require.main === module) {
78-
runServer().catch((err) => console.error(err));
79-
initial();
67+
runServer()
68+
.then(() => initial())
69+
.catch((err) => console.error(err));
8070
}
8171

8272
module.exports = { app, runServer, closeServer };

backend/setup-test.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// test-setup.js
22
const mongoose = require("mongoose");
3-
mongoose.set("useCreateIndex", true);
43
mongoose.promise = global.Promise;
54

65
const { MongoMemoryServer } = require("mongodb-memory-server");
@@ -40,15 +39,11 @@ module.exports = {
4039
instance: { dbName: databaseName },
4140
});
4241
const mongoUri = await mongoServer.getUri();
43-
const opts = {
44-
useNewUrlParser: true,
45-
useFindAndModify: false,
46-
useCreateIndex: true,
47-
useUnifiedTopology: true,
48-
};
49-
await mongoose.connect(mongoUri, opts, (err) => {
50-
if (err) console.error(err);
51-
});
42+
try {
43+
await mongoose.connect(mongoUri);
44+
} catch (err) {
45+
console.error(err);
46+
}
5247
});
5348

5449
// Disconnect Mongoose

0 commit comments

Comments
 (0)