Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

huijie - schedule meeting bell notification #1154

Open
wants to merge 10 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions src/controllers/meetingController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
const moment = require('moment-timezone');
const mongoose = require('mongoose');
const logger = require('../startup/logger');

const UserProfile = require('../models/userProfile');

const meetingController = function (Meeting) {
const postMeeting = async function (req, res) {
const isInvalid =
!req.body.dateOfMeeting ||
!moment(req.body.dateOfMeeting).isValid() ||
req.body.startHour == null ||
req.body.startMinute == null ||
!req.body.startTimePeriod ||
!['AM', 'PM'].includes(req.body.startTimePeriod) ||
!req.body.duration ||
!req.body.organizer ||
!req.body.participantList ||
req.body.participantList.length === 0 ||
(req.body.location && !['Zoom', 'Phone call', 'On-site'].includes(req.body.location));

if (isInvalid) {
return res.status(400).send({ error: 'Bad request: Invalid form values' });
}

try {
await Promise.all(
req.body.participantList.map(async (userProfileId) => {
if (!mongoose.Types.ObjectId.isValid(userProfileId)) {
throw new Error('Invalid participant ID');
}
const userProfileExists = await UserProfile.exists({ _id: userProfileId });
if (!userProfileExists) {
throw new Error('Participant ID does not exist');
}
}),
);
if (!mongoose.Types.ObjectId.isValid(req.body.organizer)) {
throw new Error('Invalid organizer ID');
}
const organizerExists = await UserProfile.exists({ _id: req.body.organizer });
if (!organizerExists) {
throw new Error('Organizer ID does not exist');
}
} catch (error) {
return res.status(400).send({ error: `Bad request: ${error.message}` });
}

const session = await mongoose.startSession();
session.startTransaction();
try {
const dateTimeString = `${req.body.dateOfMeeting} ${req.body.startHour}:${req.body.startMinute} ${req.body.startTimePeriod}`;
const dateTimeISO = moment(dateTimeString, 'YYYY-MM-DD hh:mm A').toISOString();

const meeting = new Meeting();
meeting.dateTime = dateTimeISO;
meeting.duration = req.body.duration;
meeting.organizer = req.body.organizer;
meeting.participantList = req.body.participantList.map((participant) => ({
participant,
notificationIsRead: false,
}));
meeting.location = req.body.location;
meeting.notes = req.body.notes;

await meeting.save({ session });
await session.commitTransaction();
session.endSession();
res.status(201).json({ message: 'Meeting saved successfully' });
} catch (err) {
await session.abortTransaction();
logger.logException(err);
return res.status(500).send({ error: err.toString() });
} finally {
session.endSession();
}
};

const getMeetings = async function (req, res) {
try {
const { startTime, endTime } = req.query;
const decodedStartTime = decodeURIComponent(startTime);
const decodedEndTime = decodeURIComponent(endTime);

const meetings = await Meeting.aggregate([
{
$match: {
dateTime: {
$gte: new Date(decodedStartTime),
$lte: new Date(decodedEndTime),
},
},
},
{ $unwind: '$participantList' },
{
$project: {
_id: 1,
dateTime: 1,
duration: 1,
organizer: 1,
location: 1,
notes: 1,
recipient: '$participantList.participant',
isRead: '$participantList.notificationIsRead',
},
},
]);
res.status(200).json(meetings);
} catch (error) {
console.error('Error fetching meetings:', error);
res.status(500).json({ error: 'Failed to fetch meetings' });
}
};

const markMeetingAsRead = async function (req, res) {
try {
const { meetingId, recipient } = req.params;
const result = await Meeting.updateOne(
{ _id: meetingId, 'participantList.participant': recipient },
{ $set: { 'participantList.$.notificationIsRead': true } },
);
if (result.nModified === 0) {
return res.status(404).json({ error: 'Meeting not found or already marked as read' });
}
res.status(200).json({ message: 'Meeting marked as read successfully' });
} catch (error) {
console.error('Error marking meeting as read:', error);
res.status(500).json({ error: 'Failed to mark meeting as read' });
}
};

const getAllMeetingsByOrganizer = async function (req, res) {
try {
const { organizerId } = req.query;
if (!mongoose.Types.ObjectId.isValid(organizerId)) {
return res.status(400).json({ error: 'Invalid organizer userId' });
}
const userProfileExists = await UserProfile.exists({ _id: organizerId });
if (!userProfileExists) {
throw new Error('Organizer ID does not exist');
}

const currentTime = new Date();
const meetings = await Meeting.aggregate([
{
$match: {
dateTime: { $gt: currentTime },
organizer: mongoose.Types.ObjectId(organizerId),
},
},
{
$project: {
_id: 1,
dateTime: 1,
duration: 1,
organizer: 1,
location: 1,
notes: 1,
participantList: 1,
},
},
]);

res.status(200).json(meetings);
} catch (error) {
console.error('Error fetching all upcoming meetings:', error);
res.status(500).json({ error: 'Failed to fetch all upcoming meetings' });
}
};

return {
postMeeting,
getMeetings,
markMeetingAsRead,
getAllMeetingsByOrganizer,
};
};

module.exports = meetingController;
17 changes: 17 additions & 0 deletions src/models/meeting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const mongoose = require('mongoose');

const meetingSchema = new mongoose.Schema({
dateTime: { type: Date, required: true },
duration: { type: Number, required: true },
organizer: { type: mongoose.Schema.Types.ObjectId, ref: 'userProfile', required: true },
participantList: [
{
participant: { type: mongoose.Schema.Types.ObjectId, ref: 'userProfile', required: true },
notificationIsRead: { type: Boolean, default: false },
},
],
location: { type: String },
notes: { type: String },
});

module.exports = mongoose.model('Meeting', meetingSchema);
18 changes: 18 additions & 0 deletions src/routes/meetingRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const express = require('express');

const routes = function (Meeting) {
const MeetingRouter = express.Router();

const controller = require('../controllers/meetingController')(Meeting);

MeetingRouter.route('/meetings/new').post(controller.postMeeting);
MeetingRouter.route('/meetings').get(controller.getMeetings);
MeetingRouter.route('/meetings/markRead/:meetingId/:recipient').post(
controller.markMeetingAsRead,
);
MeetingRouter.route('/meetings/upcoming/:organizerId').get(controller.getAllMeetingsByOrganizer);

return MeetingRouter;
};

module.exports = routes;
3 changes: 3 additions & 0 deletions src/startup/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const {
} = require('../models/bmdashboard/buildingInventoryItem');
const timeOffRequest = require('../models/timeOffRequest');
const followUp = require('../models/followUp');
const meeting = require('../models/meeting');

const userProfileRouter = require('../routes/userProfileRouter')(userProfile, project);
const warningRouter = require('../routes/warningRouter')(userProfile);
Expand Down Expand Up @@ -96,6 +97,7 @@ const timeOffRequestRouter = require('../routes/timeOffRequestRouter')(
userProfile,
);
const followUpRouter = require('../routes/followUpRouter')(followUp);
const meetingRouter = require('../routes/meetingRouter')(meeting);

// bm dashboard
const bmLoginRouter = require('../routes/bmdashboard/bmLoginRouter')();
Expand Down Expand Up @@ -162,6 +164,7 @@ module.exports = function (app) {
app.use('/api', timeOffRequestRouter);
app.use('/api', followUpRouter);
app.use('/api', blueSquareEmailAssignmentRouter);
app.use('/api', meetingRouter);
app.use('/api/jobs', jobsRouter)
// bm dashboard
app.use('/api/bm', bmLoginRouter);
Expand Down
4 changes: 4 additions & 0 deletions src/utilities/createInitialPermissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ const permissionsRoles = [

'seeUsersInDashboard',
'editTeamCode',
// Meeting
'scheduleMeetings',
],
},
{
Expand Down Expand Up @@ -146,6 +148,7 @@ const permissionsRoles = [
'postInvType',
'getTimeZoneAPIKey',
'checkLeadTeamOfXplus',
'scheduleMeetings',
],
},
{
Expand Down Expand Up @@ -247,6 +250,7 @@ const permissionsRoles = [
'checkLeadTeamOfXplus',
'editTeamCode',
'totalValidWeeklySummaries',
'scheduleMeetings',

// Title
'seeQSC',
Expand Down