From 812c0b1113236940837a962f371ba91be3f39d04 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Sun, 10 Sep 2023 16:49:14 +0300 Subject: [PATCH 01/16] building meetings routes --- .prettierrc | 4 +- src/controllers/meeting.controller.js | 4 +- src/controllers/mentor.controller.js | 108 +++++++-------- src/controllers/user.controller.js | 62 ++++----- src/models/mentor.model.js | 192 +++++++++++++------------- src/routes/meeting.routes.js | 11 +- src/routes/mentor.routes.js | 20 +-- src/routes/user.routes.js | 4 +- 8 files changed, 193 insertions(+), 212 deletions(-) diff --git a/.prettierrc b/.prettierrc index ed156f0..217337e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,4 @@ { - "singleQuote": true, - "tabWidth": 2 + "singleQuote": true, + "tabWidth": 2 } diff --git a/src/controllers/meeting.controller.js b/src/controllers/meeting.controller.js index e484e91..9fc9e44 100644 --- a/src/controllers/meeting.controller.js +++ b/src/controllers/meeting.controller.js @@ -4,9 +4,7 @@ const factory = require('./controllerUtils/handlerFactory'); const catchAsyncError = require('../utils/catchAsyncErrors'); // ------------- User Operations ------------// exports.getMyMeetings = catchAsyncError(async (req, res, next) => {}); -exports.createMeeting = catchAsyncError(async (req, res, next) => {}); exports.updateMeeting = catchAsyncError(async (req, res, next) => {}); +exports.createMeeting = catchAsyncError(async (req, res, next) => {}); // ---------- Basic CRUD Operations ----------// exports.getMeeting = factory.getOne(Meeting); -exports.getAllMeetings = factory.getAll(Meeting); -exports.deleteMeeting = factory.deleteOne(Meeting); diff --git a/src/controllers/mentor.controller.js b/src/controllers/mentor.controller.js index b012909..0f045d4 100644 --- a/src/controllers/mentor.controller.js +++ b/src/controllers/mentor.controller.js @@ -5,48 +5,46 @@ const AppError = require('../utils/appErrorsClass'); const catchAsyncError = require('../utils/catchAsyncErrors'); //------------handler functions ------------// const filterObj = (obj, ...allowedFields) => { - const returnedFiled = {}; - Object.keys(obj).forEach(key => { - if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; - }); - return returnedFiled; + const returnedFiled = {}; + Object.keys(obj).forEach(key => { + if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; + }); + return returnedFiled; }; // ---------- mentor Operations ---------// exports.getMe = (req, res, next) => { - req.params.id = res.locals.userId; - next(); + req.params.id = res.locals.userId; + next(); }; exports.UpdateMe = catchAsyncError(async (req, res, next) => { - if (req.body.pass || req.body.passConfirm) { - return next( - new AppError('This route is not for password updates.', 400) - ); - } + if (req.body.pass || req.body.passConfirm) { + return next(new AppError('This route is not for password updates.', 400)); + } - const filteredBody = filterObj( - req.body, - 'name', - 'email', - 'about', - 'experience', - 'identityCard', - 'courses' - ); + const filteredBody = filterObj( + req.body, + 'name', + 'email', + 'about', + 'experience', + 'identityCard', + 'courses' + ); - const updatedUser = await Mentor.findById(req.params.id); - Object.keys(filteredBody).forEach(key => { - updatedUser[key] = filteredBody[key]; - }); - await updatedUser.save({ runValidators: true }); + const updatedUser = await Mentor.findById(req.params.id); + Object.keys(filteredBody).forEach(key => { + updatedUser[key] = filteredBody[key]; + }); + await updatedUser.save({ runValidators: true }); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: updatedUser - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: updatedUser + }); }); -exports.deactivateMentor = factory.deactivateOne(Mentor); +// exports.deactivateMentor = factory.deactivateOne(Mentor); //------Basic Admin CRUD Operations------// exports.getMentor = factory.getOne(Mentor); exports.getAllMentors = factory.getAll(Mentor); @@ -54,35 +52,33 @@ exports.activateMentor = factory.activateOne(Mentor); exports.deleteMentor = factory.deleteOne(Mentor); //-----Advance Admin CRUD Operations-----// exports.verifyMentor = catchAsyncError(async (req, res, next) => { - const mentor = await Mentor.findOneAndUpdate( - { - _id: req.params.id, - onboarding_completed: true - }, - { isVerified: true } - ); + const mentor = await Mentor.findOneAndUpdate( + { + _id: req.params.id, + onboarding_completed: true + }, + { isVerified: true } + ); - if (!mentor) { - return next( - new AppError('No mentor found with ID ready to verify', 404) - ); - } + if (!mentor) { + return next(new AppError('No mentor found with ID ready to verify', 404)); + } - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: mentor - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: mentor + }); }); exports.getMentorsReq = catchAsyncError(async (req, res, next) => { - const mentors = await Mentor.find({ - isVerified: false, - onboarding_completed: true - }); + const mentors = await Mentor.find({ + isVerified: false, + onboarding_completed: true + }); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - results: mentors.length, - data: mentors - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + results: mentors.length, + data: mentors + }); }); diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 7f50341..26fcd2f 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -6,50 +6,48 @@ const catchAsyncError = require('../utils/catchAsyncErrors'); const { standarizeUser } = require('../utils/ApiFeatures'); //------------handler functions ------------// const filterObj = (obj, ...allowedFields) => { - const returnedFiled = {}; - Object.keys(obj).forEach(key => { - if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; - }); - return returnedFiled; + const returnedFiled = {}; + Object.keys(obj).forEach(key => { + if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; + }); + return returnedFiled; }; // ---------- User Operations ---------// exports.getMe = (req, res, next) => { - req.params.id = res.locals.userId; - next(); + req.params.id = res.locals.userId; + next(); }; exports.UpdateMe = catchAsyncError(async (req, res, next) => { - if (req.body.pass || req.body.passConfirm) { - return next( - new AppError('This route is not for password updates.', 400) - ); - } + if (req.body.pass || req.body.passConfirm) { + return next(new AppError('This route is not for password updates.', 400)); + } - const filteredBody = filterObj( - req.body, - 'name', - 'email', - 'about', - 'isEmployed', - 'skillsToLearn', - 'skillsLearned' - ); + const filteredBody = filterObj( + req.body, + 'name', + 'email', + 'about', + 'isEmployed', + 'skillsToLearn', + 'skillsLearned' + ); - const updatedUser = await User.findById(req.params.id); - Object.keys(filteredBody).forEach(key => { - updatedUser[key] = filteredBody[key]; - }); - await updatedUser.save({ runValidators: true }); + const updatedUser = await User.findById(req.params.id); + Object.keys(filteredBody).forEach(key => { + updatedUser[key] = filteredBody[key]; + }); + await updatedUser.save({ runValidators: true }); - const userObj = standarizeUser(updatedUser); + const userObj = standarizeUser(updatedUser); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: userObj - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: userObj + }); }); -exports.deactivateUser = factory.deactivateOne(User); +// exports.deactivateUser = factory.deactivateOne(User); //------Basic Admin CRUD Operations------// exports.getUser = factory.getOne(User); exports.getAllUsers = factory.getAll(User); diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index b472590..e582fc0 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -4,119 +4,119 @@ const mongoose = require('mongoose'); const validator = require('validator'); //------------------------------------------// const mentorSchema = new mongoose.Schema( - { - role: { - type: String, - default: 'mentor' - }, - name: { - type: String, - required: [true, 'A user must have a name'] - }, - email: { - type: String, - unique: true, - lowercase: true, - validate: { - validator: validator.isEmail, - message: 'Please provide a valid email' - }, - required: [true, 'A user must have an email'] - }, - pass: { - type: String, - required: [true, 'A user must have a password'], - minlength: 8, - select: false - }, - passConfirm: { - type: String, - validate: { - validator: function(el) { - return el === this.pass; - }, - message: 'Passwords are not the same!' - }, - required: [true, 'A user must have a password confirmation'] - }, - identityCard: { - type: String - }, - photo: { - type: String, - default: 'default.jpg' - }, - about: { - type: String, - default: 'No description' - }, - experience: [ - { - type: String, - default: 'No experience' - } - ], - courses: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: 'Course' - } - ], - onboarding_completed: { - type: Boolean, - default: false - }, - isVerified: { - type: Boolean, - default: false - }, - active: { - type: Boolean, - default: true, - select: false + { + role: { + type: String, + default: 'mentor' + }, + name: { + type: String, + required: [true, 'A user must have a name'] + }, + email: { + type: String, + unique: true, + lowercase: true, + validate: { + validator: validator.isEmail, + message: 'Please provide a valid email' + }, + required: [true, 'A user must have an email'] + }, + pass: { + type: String, + required: [true, 'A user must have a password'], + minlength: 8, + select: false + }, + passConfirm: { + type: String, + validate: { + validator: function(el) { + return el === this.pass; }, - passwordResetToken: String, - passwordResetExpires: Date + message: 'Passwords are not the same!' + }, + required: [true, 'A user must have a password confirmation'] + }, + identityCard: { + type: String + }, + photo: { + type: String, + default: 'default.jpg' + }, + about: { + type: String, + default: 'No description' + }, + experience: [ + { + type: String, + default: 'No experience' + } + ], + courses: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'Course' + } + ], + onboarding_completed: { + type: Boolean, + default: false + }, + isVerified: { + type: Boolean, + default: false + }, + active: { + type: Boolean, + default: true, + select: false }, - { - toJSON: { virtuals: true }, - toObject: { virtuals: true } - } + passwordResetToken: String, + passwordResetExpires: Date + }, + { + toJSON: { virtuals: true }, + toObject: { virtuals: true } + } ); //-------------------Instance Methods-------------------// mentorSchema.methods.correctPassword = async function(loginPass, userPass) { - return await bcrypt.compare(loginPass, userPass); + return await bcrypt.compare(loginPass, userPass); }; mentorSchema.methods.createPasswordResetToken = function() { - const resetToken = crypto.randomBytes(32).toString('hex'); - this.passwordResetToken = crypto - .createHash('sha256') - .update(resetToken) - .digest('hex'); - this.passwordResetExpires = Date.now() + 5 * 60 * 1000; - return resetToken; + const resetToken = crypto.randomBytes(32).toString('hex'); + this.passwordResetToken = crypto + .createHash('sha256') + .update(resetToken) + .digest('hex'); + this.passwordResetExpires = Date.now() + 5 * 60 * 1000; + return resetToken; }; //-------------------Document Middleware-----------------// mentorSchema.pre('save', function(next) { - if (this.isNew) return next(); - this.onboarding_completed = true; - next(); + if (this.isNew) return next(); + this.onboarding_completed = true; + next(); }); mentorSchema.pre('save', async function(next) { - // Only run this function only when password got modified (or created) - if (!this.isModified('pass')) return next(); - this.pass = await bcrypt.hash(this.pass, 12); - this.passConfirm = undefined; + // Only run this function only when password got modified (or created) + if (!this.isModified('pass')) return next(); + this.pass = await bcrypt.hash(this.pass, 12); + this.passConfirm = undefined; }); //-------------------Query Middleware-------------------// mentorSchema.pre(/^find/, function(next) { - this.select( - 'photo name email about experience courses onboarding_completed active role' - ); - this.find({ active: { $ne: false } }); - next(); + this.select( + 'photo name email about experience courses onboarding_completed active role' + ); + this.find({ active: { $ne: false } }); + next(); }); //-------------------------Export-----------------------// const Mentor = mongoose.model('Mentor', mentorSchema); diff --git a/src/routes/meeting.routes.js b/src/routes/meeting.routes.js index a237d3e..8c581db 100644 --- a/src/routes/meeting.routes.js +++ b/src/routes/meeting.routes.js @@ -2,19 +2,16 @@ const express = require('express'); const authController = require('../controllers/auth.controller'); const meetingController = require('../controllers/meeting.controller.js'); //-----------------------------------------// -const router = express.Router(); +const router = express.Router({ mergeParams: true }); //-------------------Router----------------// router .route('/') - .get(meetingController.getAllMeetings) - .post(authController.restrictTo('user'), meetingController.createMeeting); + .get(meetingController.getMyMeetings) + .post(authController.restrictTo('mentor'), meetingController.updateMeeting); router .route('/:id') .get(meetingController.getMeeting) - .patch(meetingController.updateMeeting) - .delete(meetingController.deleteMeeting); - -router.get('MyMeetings', meetingController.getMyMeetings); + .post(authController.restrictTo('user'), meetingController.createMeeting); //-------------------------------------------// module.exports = router; diff --git a/src/routes/mentor.routes.js b/src/routes/mentor.routes.js index 8acf9dc..575a7b0 100644 --- a/src/routes/mentor.routes.js +++ b/src/routes/mentor.routes.js @@ -9,17 +9,11 @@ const router = express.Router({ mergeParams: true }); router.use('/courses', coursesRouter); router.use('/meetings', meetingsRouter); -router - .get('/me', mentorController.getMe, mentorController.getMentor) - .delete( - '/deactivateMe', - mentorController.getMe, - mentorController.deactivateMentor - ); +router.get('/me', mentorController.getMe, mentorController.getMentor); router.patch( - '/updatePersonalData', - mentorController.getMe, - mentorController.UpdateMe + '/updatePersonalData', + mentorController.getMe, + mentorController.UpdateMe ); // router.patch('/updatePassword', authController.updatePassword); //---------------Admin Routes---------------// @@ -28,9 +22,9 @@ router.get('/', mentorController.getAllMentors); router.get('/mentorsRequests', mentorController.getMentorsReq); router.patch('/verifyMentor/:id', mentorController.verifyMentor); router - .route('/:id') - .get(mentorController.getMentor) - .delete(mentorController.deleteMentor); + .route('/:id') + .get(mentorController.getMentor) + .delete(mentorController.deleteMentor); router.patch('/activateMe', mentorController.activateMentor); //-------------------------------------------// module.exports = router; diff --git a/src/routes/user.routes.js b/src/routes/user.routes.js index d4d8a50..ee82177 100644 --- a/src/routes/user.routes.js +++ b/src/routes/user.routes.js @@ -11,9 +11,7 @@ router.use('/courses', coursesRouter); router.use('/meetings', meetingsRouter); router.use('/friends', friendsRouter); -router - .get('/me', userController.getMe, userController.getUser) - .delete('/deactivateMe', userController.getMe, userController.deactivateUser); +router.get('/me', userController.getMe, userController.getUser); router.patch( '/updatePersonalData', userController.getMe, From 81239afa3b587582b345ce754c6d7e12e87894ff Mon Sep 17 00:00:00 2001 From: mohammed medhat Date: Sun, 10 Sep 2023 18:30:33 +0300 Subject: [PATCH 02/16] Adding mentorRequest flow 1.0 --- src/controllers/auth.controller.js | 35 ++++++++++++++++--- src/controllers/mentor.controller.js | 13 +++++++ src/models/mentor.model.js | 12 +++---- src/routes/auth.Routes.js | 1 + .../email/templates/approvalMail.handlebars | 13 +++++++ ...ebars => mailConfirmation copy.handlebars} | 0 6 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 src/utils/email/templates/approvalMail.handlebars rename src/utils/email/templates/{mailConfirmation.handlebars => mailConfirmation copy.handlebars} (100%) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 8ebbd58..67927af 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -65,10 +65,19 @@ exports.signup = catchAsyncError(async (req, res, next) => { //TODO:only the new users and the users with non active accounts can signup //TODO:what if the 10m are gone and the user didn't confirm his email -> if login without confirming email -> send email again - const newUser = await (req.body.type.toLowerCase() === 'mentor' - ? Mentor - : User - ).create(signUpData); + let newUser; + + if (req.body.type.toLowerCase() === 'mentor') { + newUser = await Mentor.findOneAndUpdate( + { email }, + { + pass, + passConfirm + } + ); + } else { + newUser = await User.create(signUpData); + } //send Activation Mail to User @@ -95,6 +104,24 @@ exports.signup = catchAsyncError(async (req, res, next) => { }); }); +exports.createMentorRequest = catchAsyncError(async (req, res, next) => { + const mentorRequestData = filterObj( + req.body, + 'name', + 'email', + 'phone', + 'skill', + 'requestLetter' + ); + + const newMentorRequest = await Mentor.create(mentorRequestData); + + res.status(200).json({ + status: 'success', + data: newMentorRequest + }); +}); + exports.confirmEmail = catchAsyncError(async (req, res, next) => { const { token } = req.params; diff --git a/src/controllers/mentor.controller.js b/src/controllers/mentor.controller.js index b012909..5beaed1 100644 --- a/src/controllers/mentor.controller.js +++ b/src/controllers/mentor.controller.js @@ -3,6 +3,7 @@ const factory = require('./controllerUtils/handlerFactory'); const AppError = require('../utils/appErrorsClass'); const catchAsyncError = require('../utils/catchAsyncErrors'); +const sendEmail = require('../utils/email/sendMail'); //------------handler functions ------------// const filterObj = (obj, ...allowedFields) => { const returnedFiled = {}; @@ -68,6 +69,18 @@ exports.verifyMentor = catchAsyncError(async (req, res, next) => { ); } + //send Approval Mail to Mentor + + //2-send email + const redirectLink = `${req.protocol}://${process.env.CLIENT_URL}/signup?email=${mentor.email}&type=mentor`; + + sendEmail( + mentor.email, + 'Your Request Is Approved', + { name: mentor.name, link: redirectLink }, + './templates/approvalMail.handlebars' + ); + res.status(res.locals.statusCode || 200).json({ status: 'success', data: mentor diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index b472590..ff67815 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -56,15 +56,13 @@ const mentorSchema = new mongoose.Schema( default: 'No experience' } ], - courses: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: 'Course' - } - ], + skill: { + type: mongoose.Schema.Types.ObjectId, + ref: 'skill' + }, onboarding_completed: { type: Boolean, - default: false + default: true }, isVerified: { type: Boolean, diff --git a/src/routes/auth.Routes.js b/src/routes/auth.Routes.js index ce6a7a9..86e2f37 100644 --- a/src/routes/auth.Routes.js +++ b/src/routes/auth.Routes.js @@ -4,6 +4,7 @@ const authController = require('./../controllers/auth.controller'); const router = express.Router(); //-------------Auth Routes-----------------// router.post('/signup', authController.signup); +router.post('/mentorRequest', authController.createMentorRequest); router.post('/login', authController.login); router.get('/confirmEmail/:token', authController.confirmEmail); router.post('/forgotPass', authController.forgotPassword); diff --git a/src/utils/email/templates/approvalMail.handlebars b/src/utils/email/templates/approvalMail.handlebars new file mode 100644 index 0000000..4f585db --- /dev/null +++ b/src/utils/email/templates/approvalMail.handlebars @@ -0,0 +1,13 @@ + + + + + +

Hi {{name}},

+

Welcome to SkillSync your request has been approved: +

+ accept + + \ No newline at end of file diff --git a/src/utils/email/templates/mailConfirmation.handlebars b/src/utils/email/templates/mailConfirmation copy.handlebars similarity index 100% rename from src/utils/email/templates/mailConfirmation.handlebars rename to src/utils/email/templates/mailConfirmation copy.handlebars From 3eab2c4eb3a62bddc05a969b94beb3a3bc18d3b9 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Sun, 10 Sep 2023 19:16:14 +0300 Subject: [PATCH 03/16] An EndPoint called set-working-hours is created to allow mentors to set their working range of hourse for the next week and then i create all the meeting in this week with a state of not-selected --- src/controllers/mentor.controller.js | 55 +++++++++++++++++++++++++++- src/models/meeting.model.js | 42 ++++++++++----------- src/models/mentor.model.js | 4 ++ src/routes/mentor.routes.js | 5 +++ src/services/meetings.services.js | 17 +++++++++ 5 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 src/services/meetings.services.js diff --git a/src/controllers/mentor.controller.js b/src/controllers/mentor.controller.js index 0f045d4..249cb51 100644 --- a/src/controllers/mentor.controller.js +++ b/src/controllers/mentor.controller.js @@ -1,5 +1,7 @@ const Mentor = require('../models/mentor.model'); +const Meeting = require('../models/meeting.model'); const factory = require('./controllerUtils/handlerFactory'); +const calculateMeetingSlots = require('../services/meetings.services'); const AppError = require('../utils/appErrorsClass'); const catchAsyncError = require('../utils/catchAsyncErrors'); @@ -44,7 +46,58 @@ exports.UpdateMe = catchAsyncError(async (req, res, next) => { }); }); -// exports.deactivateMentor = factory.deactivateOne(Mentor); +exports.setWorkingHours = catchAsyncError(async (req, res, next) => { + const mentorId = req.params.id; + const { workingHours } = req.body; + + // Find the mentor by ID + const mentor = await Mentor.findById(mentorId); + if (!mentor) { + return next(new AppError('No mentor found with that ID', 404)); + } + + // Calculate meeting slots for the next 7 days + const nextWeekDates = Array.from({ length: 7 }, (_, i) => { + const date = new Date(); + date.setDate(date.getDate() + i); + return date; + }); + + const meetingSlots = nextWeekDates.reduce((slots, day) => { + slots.push(...calculateMeetingSlots(workingHours, day)); + return slots; + }, []); + + // Create an array of meetings for the mentor + const mentorMeetings = meetingSlots.map(date => ({ + mentor: mentorId, + scheduledDate: date + })); + + try { + // Insert meetings into the database + await Meeting.insertMany(mentorMeetings); + + // Update the mentor's working hours + mentor.workHoursRange = workingHours; + + await mentor.save(); + + res.status(res.locals.statusCode || 200).json({ + status: 'success' + }); + } catch (error) { + console.error(error.message); + return next(new AppError('Error creating meetings', 500)); + } + + // .toLocaleDateString('en-US', { + // year: 'numeric', + // month: '2-digit', + // day: '2-digit', + // hour: '2-digit' + // }) +}); //------Basic Admin CRUD Operations------// exports.getMentor = factory.getOne(Mentor); exports.getAllMentors = factory.getAll(Mentor); diff --git a/src/models/meeting.model.js b/src/models/meeting.model.js index e19b7a6..bedd71d 100644 --- a/src/models/meeting.model.js +++ b/src/models/meeting.model.js @@ -1,27 +1,27 @@ const mongoose = require('mongoose'); //-------------------Schema----------------// const meetingsSchema = new mongoose.Schema({ - mentor: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Mentor' - }, - user: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User' - }, - status: { - type: String, - enum: ['pending', 'accepted', 'rejected'], - default: 'pending' - }, - scheduledDate: { - type: Date, - required: [true, 'A meeting must have a scheduled date'] - }, - createdAt: { - type: Date, - default: Date.now() - } + mentor: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Mentor' + }, + user: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User' + }, + status: { + type: String, + enum: ['not-selected', 'pending', 'accepted', 'rejected'], + default: 'not-selected' + }, + scheduledDate: { + type: Date, + required: [true, 'A meeting must have a scheduled date'] + }, + createdAt: { + type: Date, + default: Date.now() + } }); //-------------------------Export-----------------------// diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index e582fc0..11f0539 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -62,6 +62,10 @@ const mentorSchema = new mongoose.Schema( ref: 'Course' } ], + workHoursRange: { + type: String, + default: '9:00-14:00' + }, onboarding_completed: { type: Boolean, default: false diff --git a/src/routes/mentor.routes.js b/src/routes/mentor.routes.js index 575a7b0..39b1b92 100644 --- a/src/routes/mentor.routes.js +++ b/src/routes/mentor.routes.js @@ -15,6 +15,11 @@ router.patch( mentorController.getMe, mentorController.UpdateMe ); +router.post( + '/set-working-hours', + mentorController.getMe, + mentorController.setWorkingHours +); // router.patch('/updatePassword', authController.updatePassword); //---------------Admin Routes---------------// router.use(authController.restrictTo('admin')); diff --git a/src/services/meetings.services.js b/src/services/meetings.services.js new file mode 100644 index 0000000..4e80a63 --- /dev/null +++ b/src/services/meetings.services.js @@ -0,0 +1,17 @@ +// exports.calculateMeetingSlots = (mentorWorkingHours, day) => { +const calculateMeetingSlots = function(mentorWorkingHours, day) { + const meetingSlots = []; + const [startHour, endHour] = mentorWorkingHours + .split('-') + .map(time => parseInt(time)); + + for (let hour = startHour; hour < endHour; hour++) { + const slotStartTime = new Date(day); + slotStartTime.setHours(hour, 0, 0, 0); + + meetingSlots.push(slotStartTime); + } + return meetingSlots; +}; + +module.exports = calculateMeetingSlots; From 373b64f47917added9865090e8db2214f5997075 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Sun, 10 Sep 2023 19:30:33 +0300 Subject: [PATCH 04/16] get my meetings endpoint is created to return just the schedules meetings for either user or mentor to be shown in the home page --- src/controllers/meeting.controller.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/controllers/meeting.controller.js b/src/controllers/meeting.controller.js index 9fc9e44..6731fe3 100644 --- a/src/controllers/meeting.controller.js +++ b/src/controllers/meeting.controller.js @@ -3,7 +3,26 @@ const AppError = require('../utils/appErrorsClass'); const factory = require('./controllerUtils/handlerFactory'); const catchAsyncError = require('../utils/catchAsyncErrors'); // ------------- User Operations ------------// -exports.getMyMeetings = catchAsyncError(async (req, res, next) => {}); +exports.getMyMeetings = catchAsyncError(async (req, res, next) => { + const userId = res.locals.userId; + let meetings; + if (res.locals.userType === 'mentor') { + meetings = await Meeting.find({ + mentor: userId, + state: { $ne: 'not-selected' } + }); + } else { + meetings = await Meeting.find({ user: userId }); + } + + res.status(200).json({ + status: 'success', + results: meetings.length, + data: { + meetings + } + }); +}); exports.updateMeeting = catchAsyncError(async (req, res, next) => {}); exports.createMeeting = catchAsyncError(async (req, res, next) => {}); // ---------- Basic CRUD Operations ----------// From 53004a59c1f603143df2e2695c16c8ed013d5053 Mon Sep 17 00:00:00 2001 From: mohammed medhat Date: Sun, 10 Sep 2023 19:57:41 +0300 Subject: [PATCH 05/16] Adding Most Relevant Mentors Route 1.0 --- src/controllers/auth.controller.js | 19 +++++++++------- src/controllers/user.controller.js | 18 ++++++++++++++- src/models/mentor.model.js | 9 ++++++-- src/routes/user.routes.js | 22 ++++++++++++------- ...handlebars => mailConfirmation.handlebars} | 0 5 files changed, 49 insertions(+), 19 deletions(-) rename src/utils/email/templates/{mailConfirmation copy.handlebars => mailConfirmation.handlebars} (100%) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 67927af..9bf1949 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -68,13 +68,13 @@ exports.signup = catchAsyncError(async (req, res, next) => { let newUser; if (req.body.type.toLowerCase() === 'mentor') { - newUser = await Mentor.findOneAndUpdate( - { email }, - { - pass, - passConfirm - } - ); + newUser = await Mentor.findOne({ email: signUpData.email }); + if (!newUser) { + return next(new AppError('No mentor found with that email', 404)); + } + newUser.pass = signUpData.pass; + newUser.passConfirm = signUpData.passConfirm; + await newUser.save({ validateBeforeSave: false }); } else { newUser = await User.create(signUpData); } @@ -105,7 +105,7 @@ exports.signup = catchAsyncError(async (req, res, next) => { }); exports.createMentorRequest = catchAsyncError(async (req, res, next) => { - const mentorRequestData = filterObj( + let mentorRequestData = filterObj( req.body, 'name', 'email', @@ -114,6 +114,9 @@ exports.createMentorRequest = catchAsyncError(async (req, res, next) => { 'requestLetter' ); + mentorRequestData.pass = '12345678'; + mentorRequestData.passConfirm = '12345678'; + const newMentorRequest = await Mentor.create(mentorRequestData); res.status(200).json({ diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 4fa5afc..84c8a90 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,9 +1,10 @@ const User = require('../models/user.model'); +const Mentor = require('../models/mentor.model'); const factory = require('./controllerUtils/handlerFactory'); +const { ObjectId } = require('mongoose').Types; const AppError = require('../utils/appErrorsClass'); const catchAsyncError = require('../utils/catchAsyncErrors'); -const { standarizeUser } = require('../utils/ApiFeatures'); //------------handler functions ------------// const filterObj = (obj, ...allowedFields) => { const returnedFiled = {}; @@ -47,6 +48,21 @@ exports.UpdateMe = catchAsyncError(async (req, res, next) => { }); }); +exports.getRelevantMentors = catchAsyncError(async (req, res, next) => { + const user = await User.findById(res.locals.userId); + + const mentors = await Mentor.find({ + skill: { + $in: user.skillsToLearn.map(skill => skill._id) + } + }); + + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: mentors + }); +}); + exports.deactivateUser = factory.deactivateOne(User); //------Basic Admin CRUD Operations------// exports.getUser = factory.getOne(User); diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index ff67815..3bc21a8 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -60,6 +60,11 @@ const mentorSchema = new mongoose.Schema( type: mongoose.Schema.Types.ObjectId, ref: 'skill' }, + requestLetter: { + type: String, + trim: true, + default: 'No request letter' + }, onboarding_completed: { type: Boolean, default: true @@ -70,7 +75,7 @@ const mentorSchema = new mongoose.Schema( }, active: { type: Boolean, - default: true, + default: false, select: false }, passwordResetToken: String, @@ -111,7 +116,7 @@ mentorSchema.pre('save', async function(next) { //-------------------Query Middleware-------------------// mentorSchema.pre(/^find/, function(next) { this.select( - 'photo name email about experience courses onboarding_completed active role' + 'photo name email about experience courses onboarding_completed active role skill' ); this.find({ active: { $ne: false } }); next(); diff --git a/src/routes/user.routes.js b/src/routes/user.routes.js index d4d8a50..b65253d 100644 --- a/src/routes/user.routes.js +++ b/src/routes/user.routes.js @@ -11,22 +11,28 @@ router.use('/courses', coursesRouter); router.use('/meetings', meetingsRouter); router.use('/friends', friendsRouter); +router.get('/relevantMentors', userController.getRelevantMentors); + router - .get('/me', userController.getMe, userController.getUser) - .delete('/deactivateMe', userController.getMe, userController.deactivateUser); + .get('/me', userController.getMe, userController.getUser) + .delete( + '/deactivateMe', + userController.getMe, + userController.deactivateUser + ); router.patch( - '/updatePersonalData', - userController.getMe, - userController.UpdateMe + '/updatePersonalData', + userController.getMe, + userController.UpdateMe ); // router.patch('/updatePassword', authController.updatePassword); //---------------Admin Routes---------------// // router.use(authController.restrictTo('admin')); router.get('/', userController.getAllUsers); router - .route('/:id') - .get(userController.getUser) - .delete(userController.deleteUser); + .route('/:id') + .get(userController.getUser) + .delete(userController.deleteUser); router.patch('/activateMe', userController.activateUser); //-------------------------------------------// module.exports = router; diff --git a/src/utils/email/templates/mailConfirmation copy.handlebars b/src/utils/email/templates/mailConfirmation.handlebars similarity index 100% rename from src/utils/email/templates/mailConfirmation copy.handlebars rename to src/utils/email/templates/mailConfirmation.handlebars From 29d90eceded0f281f7bd5c438dbdd66b7cfbcb8e Mon Sep 17 00:00:00 2001 From: mohammed medhat Date: Sun, 10 Sep 2023 19:59:12 +0300 Subject: [PATCH 06/16] Adding Most Relevant Mentors Route 1.1 --- src/controllers/user.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 84c8a90..6572bf4 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,7 +1,6 @@ const User = require('../models/user.model'); const Mentor = require('../models/mentor.model'); const factory = require('./controllerUtils/handlerFactory'); -const { ObjectId } = require('mongoose').Types; const AppError = require('../utils/appErrorsClass'); const catchAsyncError = require('../utils/catchAsyncErrors'); From 9be5f4d528db5c84027cac8d585819f4f52cd287 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Sun, 10 Sep 2023 21:17:30 +0300 Subject: [PATCH 07/16] adding meeting standardization and create two endpoints one to get mentor meetings for user and other for user to ask for meeting --- src/controllers/meeting.controller.js | 71 ++++++++++++++++----- src/controllers/skill.controller.js | 30 ++++----- src/models/meeting.model.js | 6 +- src/routes/mentor.routes.js | 1 - src/utils/ApiFeatures.js | 92 ++++++++++++++++++--------- 5 files changed, 137 insertions(+), 63 deletions(-) diff --git a/src/controllers/meeting.controller.js b/src/controllers/meeting.controller.js index 6731fe3..1794a17 100644 --- a/src/controllers/meeting.controller.js +++ b/src/controllers/meeting.controller.js @@ -1,29 +1,68 @@ const Meeting = require('../models/meeting.model'); const AppError = require('../utils/appErrorsClass'); -const factory = require('./controllerUtils/handlerFactory'); const catchAsyncError = require('../utils/catchAsyncErrors'); +const { + standMentorsMeeting, + standUsersMeeting +} = require('../utils/ApiFeatures'); // ------------- User Operations ------------// exports.getMyMeetings = catchAsyncError(async (req, res, next) => { const userId = res.locals.userId; - let meetings; - if (res.locals.userType === 'mentor') { - meetings = await Meeting.find({ - mentor: userId, - state: { $ne: 'not-selected' } - }); - } else { - meetings = await Meeting.find({ user: userId }); + let meetingsQuery = { user: userId }; + let populatePath = 'mentor'; + + if (res.locals.userType == 'mentor') { + meetingsQuery = { mentor: userId, status: { $ne: 'not-selected' } }; + populatePath = 'user'; } - res.status(200).json({ - status: 'success', - results: meetings.length, - data: { - meetings + const meetings = await Meeting.find({ + ...meetingsQuery + }).populate({ + path: populatePath + }); + + const selectedMeetings = meetings.map(meeting => { + if (res.locals.userType === 'mentor') { + return standMentorsMeeting(meeting); + } else { + return standUsersMeeting(meeting); } }); + + res.status(res.locals.statusCode || 200).json({ + status: 'success', + results: selectedMeetings.length, + data: selectedMeetings + }); }); + exports.updateMeeting = catchAsyncError(async (req, res, next) => {}); -exports.createMeeting = catchAsyncError(async (req, res, next) => {}); +exports.createMeeting = catchAsyncError(async (req, res, next) => { + const userId = res.locals.userId; + const mentorId = req.params.id; + const { scheduledDate } = req.body; + + const meeting = await Meeting.find({ + mentor: mentorId, + scheduledDate + }); + + res.status(res.locals.statusCode || 201).json({ + status: 'success', + data: meeting + }); +}); // ---------- Basic CRUD Operations ----------// -exports.getMeeting = factory.getOne(Meeting); +exports.getMeeting = catchAsyncError(async (req, res, next) => { + const meeting = await Meeting.find({ + mentor: req.params.id + }); + if (!meeting) return next(new AppError('No meeting found with that ID', 404)); + + res.status(res.locals.statusCode || 200).json({ + status: 'success', + length: meeting.length, + data: meeting + }); +}); diff --git a/src/controllers/skill.controller.js b/src/controllers/skill.controller.js index 719caa8..20cc456 100644 --- a/src/controllers/skill.controller.js +++ b/src/controllers/skill.controller.js @@ -8,26 +8,26 @@ exports.getSkill = factory.getOne(Skill); exports.getAllSkills = factory.getAll(Skill); exports.deleteSkill = factory.deleteOne(Skill); exports.createSkill = catchAsyncError(async (req, res, next) => { - const skillObj = filterObj(req.body, 'name', 'description'); + const skillObj = filterObj(req.body, 'name', 'description'); - const newSkill = await Skill.create(skillObj); + const newSkill = await Skill.create(skillObj); - res.status(res.locals.statusCdoe || 201).json({ - status: 'success', - data: newSkill - }); + res.status(res.locals.statusCode || 201).json({ + status: 'success', + data: newSkill + }); }); exports.updateSkill = catchAsyncError(async (req, res, next) => { - const skillObj = filterObj(req.body, 'name', 'description', 'logo'); + const skillObj = filterObj(req.body, 'name', 'description', 'logo'); - const skill = await Skill.findByIdAndUpdate(req.params.id, skillObj, { - new: true, - runValidators: true - }); + const skill = await Skill.findByIdAndUpdate(req.params.id, skillObj, { + new: true, + runValidators: true + }); - res.status(res.locals.statusCdoe || 200).json({ - status: 'success', - data: skill - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: skill + }); }); diff --git a/src/models/meeting.model.js b/src/models/meeting.model.js index bedd71d..5a652c7 100644 --- a/src/models/meeting.model.js +++ b/src/models/meeting.model.js @@ -23,7 +23,11 @@ const meetingsSchema = new mongoose.Schema({ default: Date.now() } }); +//-------------------Query Middleware----------------// +meetingsSchema.pre(/^find/, function(next) { + this.select('mentor user status scheduledDate'); + next(); +}); //-------------------------Export-----------------------// - const Meeting = mongoose.model('Meeting', meetingsSchema); module.exports = Meeting; diff --git a/src/routes/mentor.routes.js b/src/routes/mentor.routes.js index 39b1b92..bf47c76 100644 --- a/src/routes/mentor.routes.js +++ b/src/routes/mentor.routes.js @@ -20,7 +20,6 @@ router.post( mentorController.getMe, mentorController.setWorkingHours ); -// router.patch('/updatePassword', authController.updatePassword); //---------------Admin Routes---------------// router.use(authController.restrictTo('admin')); router.get('/', mentorController.getAllMentors); diff --git a/src/utils/ApiFeatures.js b/src/utils/ApiFeatures.js index 885a66e..d9b2bee 100644 --- a/src/utils/ApiFeatures.js +++ b/src/utils/ApiFeatures.js @@ -1,39 +1,71 @@ exports.filterObj = (obj, ...allowedAtt) => { - const newObj = {}; - for (att in obj) { - if (allowedAtt.includes(att)) { - newObj[att] = obj[att]; - } + const newObj = {}; + for (att in obj) { + if (allowedAtt.includes(att)) { + newObj[att] = obj[att]; } - return newObj; + } + return newObj; }; exports.standarizeUser = user => { - const userObj = { - _id: user._id, - role: user.role, - active: user.active, - about: user.about, - name: user.name, - email: user.email, - isEmployed: user.isEmployed, - skillsToLearn: user.skillsToLearn, - skillsLearned: user.skillsLearned - }; - return userObj; + const userObj = { + _id: user._id, + role: user.role, + active: user.active, + about: user.about, + name: user.name, + email: user.email, + isEmployed: user.isEmployed, + skillsToLearn: user.skillsToLearn, + skillsLearned: user.skillsLearned + }; + return userObj; }; exports.standarizeMentor = user => { - const userObj = { - _id: user._id, - role: user.role, - active: user.active, - about: user.about, - name: user.name, - email: user.email, - identityCard: user.identityCard, - courses: user.courses, - experience: user.experience - }; - return userObj; + const userObj = { + _id: user._id, + role: user.role, + active: user.active, + about: user.about, + name: user.name, + email: user.email, + identityCard: user.identityCard, + courses: user.courses, + experience: user.experience + }; + return userObj; +}; + +exports.standMentorsMeeting = meeting => { + const meetingObj = { + _id: meeting._id, + mentor: meeting.mentor, + status: meeting.status, + scheduledDate: meeting.scheduledDate, + user: { + _id: meeting.user._id, + name: meeting.user.name, + email: meeting.user.email, + photo: meeting.user.photo + } + }; + return meetingObj; +}; + +exports.standUsersMeeting = meeting => { + const meetingObj = { + _id: meeting._id, + user: meeting.user, + status: meeting.status, + scheduledDate: meeting.scheduledDate, + mentor: { + _id: meeting.mentor._id, + name: meeting.mentor.name, + email: meeting.mentor.email, + photo: meeting.mentor.photo + } + }; + return meetingObj; }; From 2a3f719f0c3a262144846b03ad05b63b8df39b59 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Sun, 10 Sep 2023 22:48:47 +0300 Subject: [PATCH 08/16] two end point has been created one to sk for session and other for mentor to update session --- src/controllers/meeting.controller.js | 38 ++++++++++++++++++--------- src/routes/meeting.routes.js | 8 +++--- src/utils/ApiFeatures.js | 10 +++---- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/controllers/meeting.controller.js b/src/controllers/meeting.controller.js index 1794a17..3825c12 100644 --- a/src/controllers/meeting.controller.js +++ b/src/controllers/meeting.controller.js @@ -8,17 +8,18 @@ const { // ------------- User Operations ------------// exports.getMyMeetings = catchAsyncError(async (req, res, next) => { const userId = res.locals.userId; - let meetingsQuery = { user: userId }; - let populatePath = 'mentor'; + let meetingsQuery = {}; + let populatePath; if (res.locals.userType == 'mentor') { meetingsQuery = { mentor: userId, status: { $ne: 'not-selected' } }; populatePath = 'user'; + } else { + meetingsQuery = { user: userId }; + populatePath = 'mentor'; } - const meetings = await Meeting.find({ - ...meetingsQuery - }).populate({ + const meetings = await Meeting.find(meetingsQuery).populate({ path: populatePath }); @@ -37,17 +38,30 @@ exports.getMyMeetings = catchAsyncError(async (req, res, next) => { }); }); -exports.updateMeeting = catchAsyncError(async (req, res, next) => {}); +exports.updateMeeting = catchAsyncError(async (req, res, next) => { + const status = req.body.status == 'accepted' ? 'accepted' : 'rejected'; + const meeting = await Meeting.findOneAndUpdate( + { _id: req.params.id, status: 'pending' }, + { status }, + { new: true } + ); + if (!meeting) return next(new AppError('No meeting found with that ID', 404)); + + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: meeting + }); +}); + exports.createMeeting = catchAsyncError(async (req, res, next) => { const userId = res.locals.userId; - const mentorId = req.params.id; - const { scheduledDate } = req.body; - - const meeting = await Meeting.find({ - mentor: mentorId, - scheduledDate + const meeting = await Meeting.findByIdAndUpdate(req.params.id, { + user: userId, + status: 'pending' }); + if (!meeting) return next(new AppError('No meeting found with that ID', 404)); + res.status(res.locals.statusCode || 201).json({ status: 'success', data: meeting diff --git a/src/routes/meeting.routes.js b/src/routes/meeting.routes.js index 8c581db..6b634a7 100644 --- a/src/routes/meeting.routes.js +++ b/src/routes/meeting.routes.js @@ -4,14 +4,12 @@ const meetingController = require('../controllers/meeting.controller.js'); //-----------------------------------------// const router = express.Router({ mergeParams: true }); //-------------------Router----------------// -router - .route('/') - .get(meetingController.getMyMeetings) - .post(authController.restrictTo('mentor'), meetingController.updateMeeting); +router.route('/').get(meetingController.getMyMeetings); router .route('/:id') .get(meetingController.getMeeting) - .post(authController.restrictTo('user'), meetingController.createMeeting); + .patch(authController.restrictTo('user'), meetingController.createMeeting) + .post(authController.restrictTo('mentor'), meetingController.updateMeeting); //-------------------------------------------// module.exports = router; diff --git a/src/utils/ApiFeatures.js b/src/utils/ApiFeatures.js index d9b2bee..8a40298 100644 --- a/src/utils/ApiFeatures.js +++ b/src/utils/ApiFeatures.js @@ -41,14 +41,13 @@ exports.standarizeMentor = user => { exports.standMentorsMeeting = meeting => { const meetingObj = { _id: meeting._id, - mentor: meeting.mentor, status: meeting.status, scheduledDate: meeting.scheduledDate, user: { - _id: meeting.user._id, - name: meeting.user.name, - email: meeting.user.email, - photo: meeting.user.photo + _id: meeting.user?._id, + name: meeting.user?.name, + email: meeting.user?.email, + photo: meeting.user?.photo } }; return meetingObj; @@ -57,7 +56,6 @@ exports.standMentorsMeeting = meeting => { exports.standUsersMeeting = meeting => { const meetingObj = { _id: meeting._id, - user: meeting.user, status: meeting.status, scheduledDate: meeting.scheduledDate, mentor: { From 498820e9b027096bc82c91636bbed668b577723b Mon Sep 17 00:00:00 2001 From: mohammed medhat Date: Sun, 10 Sep 2023 23:33:23 +0300 Subject: [PATCH 09/16] Resolving mentor issue 1.0 --- src/controllers/mentor.controller.js | 209 ++++++++++++++------------- src/models/mentor.model.js | 52 +++---- 2 files changed, 132 insertions(+), 129 deletions(-) diff --git a/src/controllers/mentor.controller.js b/src/controllers/mentor.controller.js index 4cb2c61..21cad2d 100644 --- a/src/controllers/mentor.controller.js +++ b/src/controllers/mentor.controller.js @@ -8,96 +8,98 @@ const catchAsyncError = require('../utils/catchAsyncErrors'); const sendEmail = require('../utils/email/sendMail'); //------------handler functions ------------// const filterObj = (obj, ...allowedFields) => { - const returnedFiled = {}; - Object.keys(obj).forEach(key => { - if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; - }); - return returnedFiled; + const returnedFiled = {}; + Object.keys(obj).forEach(key => { + if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; + }); + return returnedFiled; }; // ---------- mentor Operations ---------// exports.getMe = (req, res, next) => { - req.params.id = res.locals.userId; - next(); + req.params.id = res.locals.userId; + next(); }; exports.UpdateMe = catchAsyncError(async (req, res, next) => { - if (req.body.pass || req.body.passConfirm) { - return next(new AppError('This route is not for password updates.', 400)); - } - - const filteredBody = filterObj( - req.body, - 'name', - 'email', - 'about', - 'experience', - 'identityCard', - 'courses' - ); - - const updatedUser = await Mentor.findById(req.params.id); - Object.keys(filteredBody).forEach(key => { - updatedUser[key] = filteredBody[key]; - }); - await updatedUser.save({ runValidators: true }); - - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: updatedUser - }); -}); + if (req.body.pass || req.body.passConfirm) { + return next( + new AppError('This route is not for password updates.', 400) + ); + } + + const filteredBody = filterObj( + req.body, + 'name', + 'email', + 'about', + 'experience', + 'identityCard', + 'courses' + ); -exports.setWorkingHours = catchAsyncError(async (req, res, next) => { - const mentorId = req.params.id; - const { workingHours } = req.body; - - // Find the mentor by ID - const mentor = await Mentor.findById(mentorId); - if (!mentor) { - return next(new AppError('No mentor found with that ID', 404)); - } - - // Calculate meeting slots for the next 7 days - const nextWeekDates = Array.from({ length: 7 }, (_, i) => { - const date = new Date(); - date.setDate(date.getDate() + i); - return date; - }); - - const meetingSlots = nextWeekDates.reduce((slots, day) => { - slots.push(...calculateMeetingSlots(workingHours, day)); - return slots; - }, []); - - // Create an array of meetings for the mentor - const mentorMeetings = meetingSlots.map(date => ({ - mentor: mentorId, - scheduledDate: date - })); - - try { - // Insert meetings into the database - await Meeting.insertMany(mentorMeetings); - - // Update the mentor's working hours - mentor.workHoursRange = workingHours; - - await mentor.save(); + const updatedUser = await Mentor.findById(req.params.id); + Object.keys(filteredBody).forEach(key => { + updatedUser[key] = filteredBody[key]; + }); + await updatedUser.save({ runValidators: true }); res.status(res.locals.statusCode || 200).json({ - status: 'success' + status: 'success', + data: updatedUser + }); +}); + +exports.setWorkingHours = catchAsyncError(async (req, res, next) => { + const mentorId = req.params.id; + const { workingHours } = req.body; + + // Find the mentor by ID + const mentor = await Mentor.findById(mentorId); + if (!mentor) { + return next(new AppError('No mentor found with that ID', 404)); + } + + // Calculate meeting slots for the next 7 days + const nextWeekDates = Array.from({ length: 7 }, (_, i) => { + const date = new Date(); + date.setDate(date.getDate() + i); + return date; }); - } catch (error) { - console.error(error.message); - return next(new AppError('Error creating meetings', 500)); - } - - // .toLocaleDateString('en-US', { - // year: 'numeric', - // month: '2-digit', - // day: '2-digit', - // hour: '2-digit' - // }) + + const meetingSlots = nextWeekDates.reduce((slots, day) => { + slots.push(...calculateMeetingSlots(workingHours, day)); + return slots; + }, []); + + // Create an array of meetings for the mentor + const mentorMeetings = meetingSlots.map(date => ({ + mentor: mentorId, + scheduledDate: date + })); + + try { + // Insert meetings into the database + await Meeting.insertMany(mentorMeetings); + + // Update the mentor's working hours + mentor.workHoursRange = workingHours; + + await mentor.save(); + + res.status(res.locals.statusCode || 200).json({ + status: 'success' + }); + } catch (error) { + console.error(error.message); + return next(new AppError('Error creating meetings', 500)); + } + + // .toLocaleDateString('en-US', { + // year: 'numeric', + // month: '2-digit', + // day: '2-digit', + // hour: '2-digit' + // }) }); //------Basic Admin CRUD Operations------// exports.getMentor = factory.getOne(Mentor); @@ -106,17 +108,19 @@ exports.activateMentor = factory.activateOne(Mentor); exports.deleteMentor = factory.deleteOne(Mentor); //-----Advance Admin CRUD Operations-----// exports.verifyMentor = catchAsyncError(async (req, res, next) => { - const mentor = await Mentor.findOneAndUpdate( - { - _id: req.params.id, - onboarding_completed: true - }, - { isVerified: true } - ); - -if (!mentor) { - return next(new AppError('No mentor found with ID ready to verify', 404)); - } + const mentor = await Mentor.findOneAndUpdate( + { + _id: req.params.id, + onboarding_completed: true + }, + { isVerified: true } + ); + + if (!mentor) { + return next( + new AppError('No mentor found with ID ready to verify', 404) + ); + } //send Approval Mail to Mentor @@ -133,19 +137,18 @@ if (!mentor) { res.status(res.locals.statusCode || 200).json({ status: 'success', data: mentor -    }); -}); + }); }); exports.getMentorsReq = catchAsyncError(async (req, res, next) => { - const mentors = await Mentor.find({ - isVerified: false, - onboarding_completed: true - }); - - res.status(res.locals.statusCode || 200).json({ - status: 'success', - results: mentors.length, - data: mentors - }); + const mentors = await Mentor.find({ + isVerified: false, + onboarding_completed: true + }); + + res.status(res.locals.statusCode || 200).json({ + status: 'success', + results: mentors.length, + data: mentors + }); }); diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index ded2e3f..d225c90 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -78,51 +78,51 @@ const mentorSchema = new mongoose.Schema( default: false, select: false }, - workHoursRange: { - type: String, - default: '9:00-14:00' + workHoursRange: { + type: String, + default: '9:00-14:00' + }, + passwordResetToken: String, + passwordResetExpires: Date }, - passwordResetToken: String, - passwordResetExpires: Date - }, - { - toJSON: { virtuals: true }, - toObject: { virtuals: true } - } + { + toJSON: { virtuals: true }, + toObject: { virtuals: true } + } ); //-------------------Instance Methods-------------------// mentorSchema.methods.correctPassword = async function(loginPass, userPass) { - return await bcrypt.compare(loginPass, userPass); + return await bcrypt.compare(loginPass, userPass); }; mentorSchema.methods.createPasswordResetToken = function() { - const resetToken = crypto.randomBytes(32).toString('hex'); - this.passwordResetToken = crypto - .createHash('sha256') - .update(resetToken) - .digest('hex'); - this.passwordResetExpires = Date.now() + 5 * 60 * 1000; - return resetToken; + const resetToken = crypto.randomBytes(32).toString('hex'); + this.passwordResetToken = crypto + .createHash('sha256') + .update(resetToken) + .digest('hex'); + this.passwordResetExpires = Date.now() + 5 * 60 * 1000; + return resetToken; }; //-------------------Document Middleware-----------------// mentorSchema.pre('save', function(next) { - if (this.isNew) return next(); - this.onboarding_completed = true; - next(); + if (this.isNew) return next(); + this.onboarding_completed = true; + next(); }); mentorSchema.pre('save', async function(next) { - // Only run this function only when password got modified (or created) - if (!this.isModified('pass')) return next(); - this.pass = await bcrypt.hash(this.pass, 12); - this.passConfirm = undefined; + // Only run this function only when password got modified (or created) + if (!this.isModified('pass')) return next(); + this.pass = await bcrypt.hash(this.pass, 12); + this.passConfirm = undefined; }); //-------------------Query Middleware-------------------// mentorSchema.pre(/^find/, function(next) { this.select( 'photo name email about experience courses onboarding_completed active role skill' ); - this.find({ active: { $ne: false } }); + // this.find({ active: { $ne: false } }); next(); }); //-------------------------Export-----------------------// From 618a926771a15231d0fc24025449a7fba1aa1710 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Sun, 10 Sep 2023 23:43:58 +0300 Subject: [PATCH 10/16] edit standardization 1.5 --- src/controllers/mentor.controller.js | 29 ++++++++++++++-------------- src/utils/ApiFeatures.js | 5 ++--- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/controllers/mentor.controller.js b/src/controllers/mentor.controller.js index 4cb2c61..2d21274 100644 --- a/src/controllers/mentor.controller.js +++ b/src/controllers/mentor.controller.js @@ -114,27 +114,26 @@ exports.verifyMentor = catchAsyncError(async (req, res, next) => { { isVerified: true } ); -if (!mentor) { + if (!mentor) { return next(new AppError('No mentor found with ID ready to verify', 404)); } - //send Approval Mail to Mentor + //send Approval Mail to Mentor - //2-send email - const redirectLink = `${req.protocol}://${process.env.CLIENT_URL}/signup?email=${mentor.email}&type=mentor`; + //2-send email + const redirectLink = `${req.protocol}://${process.env.CLIENT_URL}/signup?email=${mentor.email}&type=mentor`; - sendEmail( - mentor.email, - 'Your Request Is Approved', - { name: mentor.name, link: redirectLink }, - './templates/approvalMail.handlebars' - ); + sendEmail( + mentor.email, + 'Your Request Is Approved', + { name: mentor.name, link: redirectLink }, + './templates/approvalMail.handlebars' + ); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: mentor -    }); -}); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: mentor + }); }); exports.getMentorsReq = catchAsyncError(async (req, res, next) => { diff --git a/src/utils/ApiFeatures.js b/src/utils/ApiFeatures.js index 09e85db..bfdcc61 100644 --- a/src/utils/ApiFeatures.js +++ b/src/utils/ApiFeatures.js @@ -15,7 +15,6 @@ exports.standMentorsMeeting = meeting => { user: { _id: meeting.user?._id, name: meeting.user?.name, - email: meeting.user?.email, photo: meeting.user?.photo } }; @@ -30,8 +29,8 @@ exports.standUsersMeeting = meeting => { mentor: { _id: meeting.mentor._id, name: meeting.mentor.name, - email: meeting.mentor.email, - photo: meeting.mentor.photo + photo: meeting.mentor.photo, + skill: meeting.mentor.skill } }; return meetingObj; From 5a1506af0b070f87a437a63891cd115aca747a46 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Mon, 11 Sep 2023 00:50:40 +0300 Subject: [PATCH 11/16] add skill name to mentor meetings object that appears to user --- public/dev-data/.import-data.js | 64 +++++----- public/dev-data/mentors.json | 129 ++++++++++---------- src/controllers/meeting.controller.js | 27 +++-- src/models/mentor.model.js | 162 +++++++++++++------------- src/utils/ApiFeatures.js | 8 +- 5 files changed, 198 insertions(+), 192 deletions(-) diff --git a/public/dev-data/.import-data.js b/public/dev-data/.import-data.js index 2c7f451..2a90cff 100644 --- a/public/dev-data/.import-data.js +++ b/public/dev-data/.import-data.js @@ -12,22 +12,22 @@ const Review = require('../../src/models/review.model'); dotenv.config({ path: path.join(__dirname, '..', '..', 'config.env') }); //--------------------DB-------------------// const DB = process.env.DATABASE_Connection.replace( - '', - process.env.DATABASE_Password + '', + process.env.DATABASE_Password ); mongoose - .connect(DB, { - useNewUrlParser: true, - useUnifiedTopology: true - }) - .then(console.log('DB connection successful!')); + .connect(DB, { + useNewUrlParser: true, + useUnifiedTopology: true + }) + .then(console.log('DB connection successful!')); //------------------Read_File----------------// const users = JSON.parse(fs.readFileSync(`${__dirname}/users.json`, 'utf-8')); // const skills = JSON.parse(fs.readFileSync(`${__dirname}/skills.json`, 'utf-8')); const mentors = JSON.parse( - fs.readFileSync(`${__dirname}/mentors.json`, 'utf-8') + fs.readFileSync(`${__dirname}/mentors.json`, 'utf-8') ); // const courses = JSON.parse( // fs.readFileSync(`${__dirname}/courses.json`, 'utf-8') @@ -37,35 +37,35 @@ const mentors = JSON.parse( // ); //--------------------CRUD------------------// async function importData() { - try { - // await skill.create(skills); - // await Course.create(courses); - // await Review.create(reviews); - await User.create(users, { validateBeforeSave: false }); - await Mentor.create(mentors, { validateBeforeSave: false }); - console.log('Data successfully loaded!'); - } catch (err) { - console.log(err); - } - process.exit(); + try { + // await skill.create(skills); + // await Course.create(courses); + // await Review.create(reviews); + await User.create(users, { validateBeforeSave: false }); + await Mentor.create(mentors, { validateBeforeSave: false }); + console.log('Data successfully loaded!'); + } catch (err) { + console.log(err); + } + process.exit(); } async function deleteData() { - try { - await User.deleteMany(); - await skill.deleteMany(); - await Mentor.deleteMany(); - await Course.deleteMany(); - await Review.deleteMany(); - console.log('Data successfully deleted!'); - } catch (err) { - console.log(err); - } - process.exit(); + try { + await User.deleteMany(); + await skill.deleteMany(); + await Mentor.deleteMany(); + await Course.deleteMany(); + await Review.deleteMany(); + console.log('Data successfully deleted!'); + } catch (err) { + console.log(err); + } + process.exit(); } //--------------Run_Commands----------------// if (process.argv[2] === '--import') { - importData(); + importData(); } else if (process.argv[2] === '--delete') { - deleteData(); + deleteData(); } diff --git a/public/dev-data/mentors.json b/public/dev-data/mentors.json index 888390e..e86fe41 100644 --- a/public/dev-data/mentors.json +++ b/public/dev-data/mentors.json @@ -1,65 +1,68 @@ [ - { - "name": "Mentor 1", - "email": "mentor1@example.com", - "pass": "password123", - "passConfirm": "password123", - "identityCard": "12345", - "photo": "mentor1.jpg", - "about": "About Mentor 1", - "experience": [ - { - "title": "Experience 1", - "description": "Description 1" - }, - { - "title": "Experience 2", - "description": "Description 2" - } - ], - "courses": [], - "onboarding_completed": false - }, - { - "name": "Mentor 2", - "email": "mentor2@example.com", - "pass": "password456", - "passConfirm": "password456", - "identityCard": "54321", - "photo": "mentor2.jpg", - "about": "About Mentor 2", - "experience": [ - { - "title": "Experience 3", - "description": "Description 3" - }, - { - "title": "Experience 4", - "description": "Description 4" - } - ], - "courses": [], - "onboarding_completed": false - }, - { - "name": "Mentor 3", - "email": "mentor3@example.com", - "pass": "password789", - "passConfirm": "password789", - "identityCard": "98765", - "photo": "mentor3.jpg", - "about": "About Mentor 3", - "experience": [ - { - "title": "Experience 5", - "description": "Description 5" - }, - { - "title": "Experience 6", - "description": "Description 6" - } - ], - "courses": [], - "onboarding_completed": false - } + { + "name": "Mentor 1", + "email": "mentor1@example.com", + "pass": "password123", + "passConfirm": "password123", + "identityCard": "12345", + "photo": "mentor1.jpg", + "skill": "64fcb555d14e59693748102e", + "about": "About Mentor 1", + "experience": [ + { + "title": "Experience 1", + "description": "Description 1" + }, + { + "title": "Experience 2", + "description": "Description 2" + } + ], + "courses": [], + "onboarding_completed": false + }, + { + "name": "Mentor 2", + "email": "mentor2@example.com", + "pass": "password456", + "passConfirm": "password456", + "identityCard": "54321", + "photo": "mentor2.jpg", + "skill": "64fcc1c431010b05d9634812", + "about": "About Mentor 2", + "experience": [ + { + "title": "Experience 3", + "description": "Description 3" + }, + { + "title": "Experience 4", + "description": "Description 4" + } + ], + "courses": [], + "onboarding_completed": false + }, + { + "name": "Mentor 3", + "email": "mentor3@example.com", + "pass": "password789", + "passConfirm": "password789", + "identityCard": "98765", + "photo": "mentor3.jpg", + "skill": "64fcb555d14e59693748102e", + "about": "About Mentor 3", + "experience": [ + { + "title": "Experience 5", + "description": "Description 5" + }, + { + "title": "Experience 6", + "description": "Description 6" + } + ], + "courses": [], + "onboarding_completed": false + } ] diff --git a/src/controllers/meeting.controller.js b/src/controllers/meeting.controller.js index 3825c12..12a1c30 100644 --- a/src/controllers/meeting.controller.js +++ b/src/controllers/meeting.controller.js @@ -8,20 +8,23 @@ const { // ------------- User Operations ------------// exports.getMyMeetings = catchAsyncError(async (req, res, next) => { const userId = res.locals.userId; - let meetingsQuery = {}; - let populatePath; - if (res.locals.userType == 'mentor') { - meetingsQuery = { mentor: userId, status: { $ne: 'not-selected' } }; - populatePath = 'user'; - } else { - meetingsQuery = { user: userId }; - populatePath = 'mentor'; - } + const meetingsQuery = + res.locals.userType === 'mentor' + ? { mentor: userId, status: { $ne: 'not-selected' } } + : { user: userId }; + const populateOptions = + res.locals.userType === 'mentor' + ? { path: 'user' } + : { + path: 'mentor', + populate: { + path: 'skill', + select: 'name' + } + }; - const meetings = await Meeting.find(meetingsQuery).populate({ - path: populatePath - }); + const meetings = await Meeting.find(meetingsQuery).populate(populateOptions); const selectedMeetings = meetings.map(meeting => { if (res.locals.userType === 'mentor') { diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index ded2e3f..2595cce 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -4,83 +4,83 @@ const mongoose = require('mongoose'); const validator = require('validator'); //------------------------------------------// const mentorSchema = new mongoose.Schema( - { - role: { - type: String, - default: 'mentor' - }, - name: { - type: String, - required: [true, 'A user must have a name'] - }, - email: { - type: String, - unique: true, - lowercase: true, - validate: { - validator: validator.isEmail, - message: 'Please provide a valid email' - }, - required: [true, 'A user must have an email'] - }, - pass: { - type: String, - required: [true, 'A user must have a password'], - minlength: 8, - select: false - }, - passConfirm: { - type: String, - validate: { - validator: function(el) { - return el === this.pass; - }, - message: 'Passwords are not the same!' - }, - required: [true, 'A user must have a password confirmation'] - }, - identityCard: { - type: String - }, - photo: { - type: String, - default: 'default.jpg' - }, - about: { - type: String, - default: 'No description' - }, - experience: [ - { - type: String, - default: 'No experience' - } - ], - skill: { - type: mongoose.Schema.Types.ObjectId, - ref: 'skill' - }, - requestLetter: { - type: String, - trim: true, - default: 'No request letter' - }, - onboarding_completed: { - type: Boolean, - default: true - }, - isVerified: { - type: Boolean, - default: false - }, - active: { - type: Boolean, - default: false, - select: false + { + role: { + type: String, + default: 'mentor' + }, + name: { + type: String, + required: [true, 'A user must have a name'] + }, + email: { + type: String, + unique: true, + lowercase: true, + validate: { + validator: validator.isEmail, + message: 'Please provide a valid email' + }, + required: [true, 'A user must have an email'] + }, + pass: { + type: String, + required: [true, 'A user must have a password'], + minlength: 8, + select: false + }, + passConfirm: { + type: String, + validate: { + validator: function(el) { + return el === this.pass; }, - workHoursRange: { - type: String, - default: '9:00-14:00' + message: 'Passwords are not the same!' + }, + required: [true, 'A user must have a password confirmation'] + }, + identityCard: { + type: String + }, + photo: { + type: String, + default: 'default.jpg' + }, + about: { + type: String, + default: 'No description' + }, + experience: [ + { + type: String, + default: 'No experience' + } + ], + skill: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Skill' + }, + requestLetter: { + type: String, + trim: true, + default: 'No request letter' + }, + onboarding_completed: { + type: Boolean, + default: true + }, + isVerified: { + type: Boolean, + default: false + }, + active: { + type: Boolean, + default: false, + select: false + }, + workHoursRange: { + type: String, + default: '9:00-14:00' }, passwordResetToken: String, passwordResetExpires: Date @@ -119,11 +119,11 @@ mentorSchema.pre('save', async function(next) { }); //-------------------Query Middleware-------------------// mentorSchema.pre(/^find/, function(next) { - this.select( - 'photo name email about experience courses onboarding_completed active role skill' - ); - this.find({ active: { $ne: false } }); - next(); + this.select( + 'photo name email about experience courses onboarding_completed active role skill' + ); + this.find({ active: { $ne: false } }); + next(); }); //-------------------------Export-----------------------// const Mentor = mongoose.model('Mentor', mentorSchema); diff --git a/src/utils/ApiFeatures.js b/src/utils/ApiFeatures.js index bfdcc61..565557b 100644 --- a/src/utils/ApiFeatures.js +++ b/src/utils/ApiFeatures.js @@ -27,10 +27,10 @@ exports.standUsersMeeting = meeting => { status: meeting.status, scheduledDate: meeting.scheduledDate, mentor: { - _id: meeting.mentor._id, - name: meeting.mentor.name, - photo: meeting.mentor.photo, - skill: meeting.mentor.skill + _id: meeting.mentor?._id, + name: meeting.mentor?.name, + photo: meeting.mentor?.photo, + skill: meeting.mentor?.skill?.name } }; return meetingObj; From 0131ff975f52b2e281e1583143b38e47329b6664 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Mon, 11 Sep 2023 01:03:48 +0300 Subject: [PATCH 12/16] bug fixed --- src/models/mentor.model.js | 49 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index 3ad7582..539b285 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -81,46 +81,47 @@ const mentorSchema = new mongoose.Schema( workHoursRange: { type: String, default: '9:00-14:00' - }, - { - toJSON: { virtuals: true }, - toObject: { virtuals: true } } + }, + { + toJSON: { virtuals: true }, + toObject: { virtuals: true } + } ); //-------------------Instance Methods-------------------// mentorSchema.methods.correctPassword = async function(loginPass, userPass) { - return await bcrypt.compare(loginPass, userPass); + return await bcrypt.compare(loginPass, userPass); }; mentorSchema.methods.createPasswordResetToken = function() { - const resetToken = crypto.randomBytes(32).toString('hex'); - this.passwordResetToken = crypto - .createHash('sha256') - .update(resetToken) - .digest('hex'); - this.passwordResetExpires = Date.now() + 5 * 60 * 1000; - return resetToken; + const resetToken = crypto.randomBytes(32).toString('hex'); + this.passwordResetToken = crypto + .createHash('sha256') + .update(resetToken) + .digest('hex'); + this.passwordResetExpires = Date.now() + 5 * 60 * 1000; + return resetToken; }; //-------------------Document Middleware-----------------// mentorSchema.pre('save', function(next) { - if (this.isNew) return next(); - this.onboarding_completed = true; - next(); + if (this.isNew) return next(); + this.onboarding_completed = true; + next(); }); mentorSchema.pre('save', async function(next) { - // Only run this function only when password got modified (or created) - if (!this.isModified('pass')) return next(); - this.pass = await bcrypt.hash(this.pass, 12); - this.passConfirm = undefined; + // Only run this function only when password got modified (or created) + if (!this.isModified('pass')) return next(); + this.pass = await bcrypt.hash(this.pass, 12); + this.passConfirm = undefined; }); //-------------------Query Middleware-------------------// mentorSchema.pre(/^find/, function(next) { - this.select( - 'photo name email about experience courses onboarding_completed active role skill' - ); - // this.find({ active: { $ne: false } }); - next(); + this.select( + 'photo name email about experience courses onboarding_completed active role skill' + ); + // this.find({ active: { $ne: false } }); + next(); }); //-------------------------Export-----------------------// const Mentor = mongoose.model('Mentor', mentorSchema); From 88fe60485d924eee32d112473ca902bfd7b98c6b Mon Sep 17 00:00:00 2001 From: mohammed medhat Date: Mon, 11 Sep 2023 01:05:19 +0300 Subject: [PATCH 13/16] Resolving mentor issue 1.4 --- src/models/mentor.model.js | 202 +++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 100 deletions(-) diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index 539b285..2e033bf 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -4,124 +4,126 @@ const mongoose = require('mongoose'); const validator = require('validator'); //------------------------------------------// const mentorSchema = new mongoose.Schema( - { - role: { - type: String, - default: 'mentor' - }, - name: { - type: String, - required: [true, 'A user must have a name'] - }, - email: { - type: String, - unique: true, - lowercase: true, - validate: { - validator: validator.isEmail, - message: 'Please provide a valid email' - }, - required: [true, 'A user must have an email'] - }, - pass: { - type: String, - required: [true, 'A user must have a password'], - minlength: 8, - select: false - }, - passConfirm: { - type: String, - validate: { - validator: function(el) { - return el === this.pass; + { + role: { + type: String, + default: 'mentor' }, - message: 'Passwords are not the same!' - }, - required: [true, 'A user must have a password confirmation'] - }, - identityCard: { - type: String - }, - photo: { - type: String, - default: 'default.jpg' - }, - about: { - type: String, - default: 'No description' - }, - experience: [ - { - type: String, - default: 'No experience' - } - ], - skill: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Skill' - }, - requestLetter: { - type: String, - trim: true, - default: 'No request letter' - }, - onboarding_completed: { - type: Boolean, - default: true - }, - isVerified: { - type: Boolean, - default: false - }, - active: { - type: Boolean, - default: false, - select: false + name: { + type: String, + required: [true, 'A user must have a name'] + }, + email: { + type: String, + unique: true, + lowercase: true, + validate: { + validator: validator.isEmail, + message: 'Please provide a valid email' + }, + required: [true, 'A user must have an email'] + }, + pass: { + type: String, + required: [true, 'A user must have a password'], + minlength: 8, + select: false + }, + passConfirm: { + type: String, + validate: { + validator: function(el) { + return el === this.pass; + }, + message: 'Passwords are not the same!' + }, + required: [true, 'A user must have a password confirmation'] + }, + identityCard: { + type: String + }, + photo: { + type: String, + default: 'default.jpg' + }, + about: { + type: String, + default: 'No description' + }, + experience: [ + { + type: String, + default: 'No experience' + } + ], + skill: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Skill' + }, + requestLetter: { + type: String, + trim: true, + default: 'No request letter' + }, + onboarding_completed: { + type: Boolean, + default: true + }, + isVerified: { + type: Boolean, + default: false + }, + active: { + type: Boolean, + default: false, + select: false + }, + workHoursRange: { + type: String, + default: '9:00-14:00' + }, + passwordResetToken: String, + passwordResetExpires: Date }, - workHoursRange: { - type: String, - default: '9:00-14:00' + { + toJSON: { virtuals: true }, + toObject: { virtuals: true } } - }, - { - toJSON: { virtuals: true }, - toObject: { virtuals: true } - } ); //-------------------Instance Methods-------------------// mentorSchema.methods.correctPassword = async function(loginPass, userPass) { - return await bcrypt.compare(loginPass, userPass); + return await bcrypt.compare(loginPass, userPass); }; mentorSchema.methods.createPasswordResetToken = function() { - const resetToken = crypto.randomBytes(32).toString('hex'); - this.passwordResetToken = crypto - .createHash('sha256') - .update(resetToken) - .digest('hex'); - this.passwordResetExpires = Date.now() + 5 * 60 * 1000; - return resetToken; + const resetToken = crypto.randomBytes(32).toString('hex'); + this.passwordResetToken = crypto + .createHash('sha256') + .update(resetToken) + .digest('hex'); + this.passwordResetExpires = Date.now() + 5 * 60 * 1000; + return resetToken; }; //-------------------Document Middleware-----------------// mentorSchema.pre('save', function(next) { - if (this.isNew) return next(); - this.onboarding_completed = true; - next(); + if (this.isNew) return next(); + this.onboarding_completed = true; + next(); }); mentorSchema.pre('save', async function(next) { - // Only run this function only when password got modified (or created) - if (!this.isModified('pass')) return next(); - this.pass = await bcrypt.hash(this.pass, 12); - this.passConfirm = undefined; + // Only run this function only when password got modified (or created) + if (!this.isModified('pass')) return next(); + this.pass = await bcrypt.hash(this.pass, 12); + this.passConfirm = undefined; }); //-------------------Query Middleware-------------------// mentorSchema.pre(/^find/, function(next) { - this.select( - 'photo name email about experience courses onboarding_completed active role skill' - ); - // this.find({ active: { $ne: false } }); - next(); + this.select( + 'photo name email about experience courses onboarding_completed active role skill' + ); + // this.find({ active: { $ne: false } }); + next(); }); //-------------------------Export-----------------------// const Mentor = mongoose.model('Mentor', mentorSchema); From c06091efd211e45c72cc3247c088801fa51285f6 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Mon, 11 Sep 2023 01:44:22 +0300 Subject: [PATCH 14/16] remove experience attribute from mentor --- public/dev-data/mentors.json | 30 ---- src/controllers/mentor.controller.js | 201 +++++++++++++-------------- src/models/mentor.model.js | 200 +++++++++++++------------- 3 files changed, 195 insertions(+), 236 deletions(-) diff --git a/public/dev-data/mentors.json b/public/dev-data/mentors.json index e86fe41..bb228e4 100644 --- a/public/dev-data/mentors.json +++ b/public/dev-data/mentors.json @@ -8,16 +8,6 @@ "photo": "mentor1.jpg", "skill": "64fcb555d14e59693748102e", "about": "About Mentor 1", - "experience": [ - { - "title": "Experience 1", - "description": "Description 1" - }, - { - "title": "Experience 2", - "description": "Description 2" - } - ], "courses": [], "onboarding_completed": false }, @@ -30,16 +20,6 @@ "photo": "mentor2.jpg", "skill": "64fcc1c431010b05d9634812", "about": "About Mentor 2", - "experience": [ - { - "title": "Experience 3", - "description": "Description 3" - }, - { - "title": "Experience 4", - "description": "Description 4" - } - ], "courses": [], "onboarding_completed": false }, @@ -52,16 +32,6 @@ "photo": "mentor3.jpg", "skill": "64fcb555d14e59693748102e", "about": "About Mentor 3", - "experience": [ - { - "title": "Experience 5", - "description": "Description 5" - }, - { - "title": "Experience 6", - "description": "Description 6" - } - ], "courses": [], "onboarding_completed": false } diff --git a/src/controllers/mentor.controller.js b/src/controllers/mentor.controller.js index 2c6a26d..6b28ef9 100644 --- a/src/controllers/mentor.controller.js +++ b/src/controllers/mentor.controller.js @@ -8,98 +8,95 @@ const catchAsyncError = require('../utils/catchAsyncErrors'); const sendEmail = require('../utils/email/sendMail'); //------------handler functions ------------// const filterObj = (obj, ...allowedFields) => { - const returnedFiled = {}; - Object.keys(obj).forEach(key => { - if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; - }); - return returnedFiled; + const returnedFiled = {}; + Object.keys(obj).forEach(key => { + if (allowedFields.includes(key)) returnedFiled[key] = obj[key]; + }); + return returnedFiled; }; // ---------- mentor Operations ---------// exports.getMe = (req, res, next) => { - req.params.id = res.locals.userId; - next(); + req.params.id = res.locals.userId; + next(); }; exports.UpdateMe = catchAsyncError(async (req, res, next) => { - if (req.body.pass || req.body.passConfirm) { - return next( - new AppError('This route is not for password updates.', 400) - ); - } - - const filteredBody = filterObj( - req.body, - 'name', - 'email', - 'about', - 'experience', - 'identityCard', - 'courses' - ); - - const updatedUser = await Mentor.findById(req.params.id); - Object.keys(filteredBody).forEach(key => { - updatedUser[key] = filteredBody[key]; - }); - await updatedUser.save({ runValidators: true }); + if (req.body.pass || req.body.passConfirm) { + return next(new AppError('This route is not for password updates.', 400)); + } + + const filteredBody = filterObj( + req.body, + 'name', + 'email', + 'about', + 'identityCard', + 'courses' + ); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: updatedUser - }); + const updatedUser = await Mentor.findById(req.params.id); + Object.keys(filteredBody).forEach(key => { + updatedUser[key] = filteredBody[key]; + }); + await updatedUser.save({ runValidators: true }); + + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: updatedUser + }); }); exports.setWorkingHours = catchAsyncError(async (req, res, next) => { - const mentorId = req.params.id; - const { workingHours } = req.body; - - // Find the mentor by ID - const mentor = await Mentor.findById(mentorId); - if (!mentor) { - return next(new AppError('No mentor found with that ID', 404)); - } - - // Calculate meeting slots for the next 7 days - const nextWeekDates = Array.from({ length: 7 }, (_, i) => { - const date = new Date(); - date.setDate(date.getDate() + i); - return date; - }); + const mentorId = req.params.id; + const { workingHours } = req.body; + + // Find the mentor by ID + const mentor = await Mentor.findById(mentorId); + if (!mentor) { + return next(new AppError('No mentor found with that ID', 404)); + } + + // Calculate meeting slots for the next 7 days + const nextWeekDates = Array.from({ length: 7 }, (_, i) => { + const date = new Date(); + date.setDate(date.getDate() + i); + return date; + }); + + const meetingSlots = nextWeekDates.reduce((slots, day) => { + slots.push(...calculateMeetingSlots(workingHours, day)); + return slots; + }, []); + + // Create an array of meetings for the mentor + const mentorMeetings = meetingSlots.map(date => ({ + mentor: mentorId, + scheduledDate: date + })); - const meetingSlots = nextWeekDates.reduce((slots, day) => { - slots.push(...calculateMeetingSlots(workingHours, day)); - return slots; - }, []); - - // Create an array of meetings for the mentor - const mentorMeetings = meetingSlots.map(date => ({ - mentor: mentorId, - scheduledDate: date - })); - - try { - // Insert meetings into the database - await Meeting.insertMany(mentorMeetings); - - // Update the mentor's working hours - mentor.workHoursRange = workingHours; - - await mentor.save(); - - res.status(res.locals.statusCode || 200).json({ - status: 'success' - }); - } catch (error) { - console.error(error.message); - return next(new AppError('Error creating meetings', 500)); - } - - // .toLocaleDateString('en-US', { - // year: 'numeric', - // month: '2-digit', - // day: '2-digit', - // hour: '2-digit' - // }) + try { + // Insert meetings into the database + await Meeting.insertMany(mentorMeetings); + + // Update the mentor's working hours + mentor.workHoursRange = workingHours; + + await mentor.save(); + + res.status(res.locals.statusCode || 200).json({ + status: 'success' + }); + } catch (error) { + console.error(error.message); + return next(new AppError('Error creating meetings', 500)); + } + + // .toLocaleDateString('en-US', { + // year: 'numeric', + // month: '2-digit', + // day: '2-digit', + // hour: '2-digit' + // }) }); //------Basic Admin CRUD Operations------// exports.getMentor = factory.getOne(Mentor); @@ -108,19 +105,17 @@ exports.activateMentor = factory.activateOne(Mentor); exports.deleteMentor = factory.deleteOne(Mentor); //-----Advance Admin CRUD Operations-----// exports.verifyMentor = catchAsyncError(async (req, res, next) => { - const mentor = await Mentor.findOneAndUpdate( - { - _id: req.params.id, - onboarding_completed: true - }, - { isVerified: true } - ); - - if (!mentor) { - return next( - new AppError('No mentor found with ID ready to verify', 404) - ); - } + const mentor = await Mentor.findOneAndUpdate( + { + _id: req.params.id, + onboarding_completed: true + }, + { isVerified: true } + ); + + if (!mentor) { + return next(new AppError('No mentor found with ID ready to verify', 404)); + } //send Approval Mail to Mentor @@ -141,14 +136,14 @@ exports.verifyMentor = catchAsyncError(async (req, res, next) => { }); exports.getMentorsReq = catchAsyncError(async (req, res, next) => { - const mentors = await Mentor.find({ - isVerified: false, - onboarding_completed: true - }); + const mentors = await Mentor.find({ + isVerified: false, + onboarding_completed: true + }); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - results: mentors.length, - data: mentors - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + results: mentors.length, + data: mentors + }); }); diff --git a/src/models/mentor.model.js b/src/models/mentor.model.js index 2e033bf..365e8cd 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -4,126 +4,120 @@ const mongoose = require('mongoose'); const validator = require('validator'); //------------------------------------------// const mentorSchema = new mongoose.Schema( - { - role: { - type: String, - default: 'mentor' - }, - name: { - type: String, - required: [true, 'A user must have a name'] - }, - email: { - type: String, - unique: true, - lowercase: true, - validate: { - validator: validator.isEmail, - message: 'Please provide a valid email' - }, - required: [true, 'A user must have an email'] - }, - pass: { - type: String, - required: [true, 'A user must have a password'], - minlength: 8, - select: false - }, - passConfirm: { - type: String, - validate: { - validator: function(el) { - return el === this.pass; - }, - message: 'Passwords are not the same!' - }, - required: [true, 'A user must have a password confirmation'] - }, - identityCard: { - type: String - }, - photo: { - type: String, - default: 'default.jpg' - }, - about: { - type: String, - default: 'No description' - }, - experience: [ - { - type: String, - default: 'No experience' - } - ], - skill: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Skill' - }, - requestLetter: { - type: String, - trim: true, - default: 'No request letter' - }, - onboarding_completed: { - type: Boolean, - default: true - }, - isVerified: { - type: Boolean, - default: false - }, - active: { - type: Boolean, - default: false, - select: false - }, - workHoursRange: { - type: String, - default: '9:00-14:00' + { + role: { + type: String, + default: 'mentor' + }, + name: { + type: String, + required: [true, 'A user must have a name'] + }, + email: { + type: String, + unique: true, + lowercase: true, + validate: { + validator: validator.isEmail, + message: 'Please provide a valid email' + }, + required: [true, 'A user must have an email'] + }, + pass: { + type: String, + required: [true, 'A user must have a password'], + minlength: 8, + select: false + }, + passConfirm: { + type: String, + validate: { + validator: function(el) { + return el === this.pass; }, - passwordResetToken: String, - passwordResetExpires: Date + message: 'Passwords are not the same!' + }, + required: [true, 'A user must have a password confirmation'] + }, + identityCard: { + type: String + }, + photo: { + type: String, + default: 'default.jpg' + }, + about: { + type: String, + default: 'No description' + }, + skill: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Skill' + }, + requestLetter: { + type: String, + trim: true, + default: 'No request letter' + }, + onboarding_completed: { + type: Boolean, + default: true + }, + isVerified: { + type: Boolean, + default: false + }, + active: { + type: Boolean, + default: false, + select: false + }, + workHoursRange: { + type: String, + default: '9:00-14:00' }, - { - toJSON: { virtuals: true }, - toObject: { virtuals: true } - } + passwordResetToken: String, + passwordResetExpires: Date + }, + { + toJSON: { virtuals: true }, + toObject: { virtuals: true } + } ); //-------------------Instance Methods-------------------// mentorSchema.methods.correctPassword = async function(loginPass, userPass) { - return await bcrypt.compare(loginPass, userPass); + return await bcrypt.compare(loginPass, userPass); }; mentorSchema.methods.createPasswordResetToken = function() { - const resetToken = crypto.randomBytes(32).toString('hex'); - this.passwordResetToken = crypto - .createHash('sha256') - .update(resetToken) - .digest('hex'); - this.passwordResetExpires = Date.now() + 5 * 60 * 1000; - return resetToken; + const resetToken = crypto.randomBytes(32).toString('hex'); + this.passwordResetToken = crypto + .createHash('sha256') + .update(resetToken) + .digest('hex'); + this.passwordResetExpires = Date.now() + 5 * 60 * 1000; + return resetToken; }; //-------------------Document Middleware-----------------// mentorSchema.pre('save', function(next) { - if (this.isNew) return next(); - this.onboarding_completed = true; - next(); + if (this.isNew) return next(); + this.onboarding_completed = true; + next(); }); mentorSchema.pre('save', async function(next) { - // Only run this function only when password got modified (or created) - if (!this.isModified('pass')) return next(); - this.pass = await bcrypt.hash(this.pass, 12); - this.passConfirm = undefined; + // Only run this function only when password got modified (or created) + if (!this.isModified('pass')) return next(); + this.pass = await bcrypt.hash(this.pass, 12); + this.passConfirm = undefined; }); //-------------------Query Middleware-------------------// mentorSchema.pre(/^find/, function(next) { - this.select( - 'photo name email about experience courses onboarding_completed active role skill' - ); - // this.find({ active: { $ne: false } }); - next(); + this.select( + 'photo name email about courses onboarding_completed active role skill' + ); + // this.find({ active: { $ne: false } }); + next(); }); //-------------------------Export-----------------------// const Mentor = mongoose.model('Mentor', mentorSchema); From 96f11cae110a27976027a3aaf7cae312202b72c7 Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Mon, 11 Sep 2023 01:46:27 +0300 Subject: [PATCH 15/16] populate the skill in relavent mentors endpoint --- src/controllers/user.controller.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 979305d..6d0f5db 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -40,24 +40,27 @@ exports.UpdateMe = catchAsyncError(async (req, res, next) => { await updatedUser.save({ runValidators: true }); res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: updatedUser - }); + status: 'success', + data: updatedUser + }); }); exports.getRelevantMentors = catchAsyncError(async (req, res, next) => { - const user = await User.findById(res.locals.userId); + const user = await User.findById(res.locals.userId); - const mentors = await Mentor.find({ - skill: { - $in: user.skillsToLearn.map(skill => skill._id) - } - }); + const mentors = await Mentor.find({ + skill: { + $in: user.skillsToLearn.map(skill => skill._id) + } + }).populate({ + path: 'skill', + select: 'name' + }); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: mentors - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: mentors + }); }); // exports.deactivateUser = factory.deactivateOne(User); From c47650a2350f511e969939f6a7ee3d7ab035a8ad Mon Sep 17 00:00:00 2001 From: MAES-Pyramids Date: Mon, 11 Sep 2023 01:51:11 +0300 Subject: [PATCH 16/16] remove rejected meetings from mentors endpoint to get mymeeting --- src/controllers/meeting.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/meeting.controller.js b/src/controllers/meeting.controller.js index 12a1c30..c4df77b 100644 --- a/src/controllers/meeting.controller.js +++ b/src/controllers/meeting.controller.js @@ -11,7 +11,7 @@ exports.getMyMeetings = catchAsyncError(async (req, res, next) => { const meetingsQuery = res.locals.userType === 'mentor' - ? { mentor: userId, status: { $ne: 'not-selected' } } + ? { mentor: userId, status: { $nin: ['not-selected', 'rejected'] } } : { user: userId }; const populateOptions = res.locals.userType === 'mentor'