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..bb228e4 100644 --- a/public/dev-data/mentors.json +++ b/public/dev-data/mentors.json @@ -1,65 +1,38 @@ [ - { - "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", + "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", + "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", + "courses": [], + "onboarding_completed": false + } ] diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 8ebbd58..9bf1949 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.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); + } //send Activation Mail to User @@ -95,6 +104,27 @@ exports.signup = catchAsyncError(async (req, res, next) => { }); }); +exports.createMentorRequest = catchAsyncError(async (req, res, next) => { + let mentorRequestData = filterObj( + req.body, + 'name', + 'email', + 'phone', + 'skill', + 'requestLetter' + ); + + mentorRequestData.pass = '12345678'; + mentorRequestData.passConfirm = '12345678'; + + 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/meeting.controller.js b/src/controllers/meeting.controller.js index e484e91..c4df77b 100644 --- a/src/controllers/meeting.controller.js +++ b/src/controllers/meeting.controller.js @@ -1,12 +1,85 @@ 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) => {}); -exports.createMeeting = catchAsyncError(async (req, res, next) => {}); -exports.updateMeeting = catchAsyncError(async (req, res, next) => {}); +exports.getMyMeetings = catchAsyncError(async (req, res, next) => { + const userId = res.locals.userId; + + const meetingsQuery = + res.locals.userType === 'mentor' + ? { mentor: userId, status: { $nin: ['not-selected', 'rejected'] } } + : { user: userId }; + const populateOptions = + res.locals.userType === 'mentor' + ? { path: 'user' } + : { + path: 'mentor', + populate: { + path: 'skill', + select: 'name' + } + }; + + const meetings = await Meeting.find(meetingsQuery).populate(populateOptions); + + 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) => { + 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 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 + }); +}); // ---------- Basic CRUD Operations ----------// -exports.getMeeting = factory.getOne(Meeting); -exports.getAllMeetings = factory.getAll(Meeting); -exports.deleteMeeting = factory.deleteOne(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/mentor.controller.js b/src/controllers/mentor.controller.js index b012909..6b28ef9 100644 --- a/src/controllers/mentor.controller.js +++ b/src/controllers/mentor.controller.js @@ -1,52 +1,103 @@ 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'); +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' + ); + + 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 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', - data: updatedUser + status: 'success' }); -}); + } catch (error) { + console.error(error.message); + return next(new AppError('Error creating meetings', 500)); + } -exports.deactivateMentor = factory.deactivateOne(Mentor); + // .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); @@ -54,35 +105,45 @@ 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 } + ); - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: mentor - }); + if (!mentor) { + return next(new AppError('No mentor found with ID ready to verify', 404)); + } + + //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 + }); }); 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/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/controllers/user.controller.js b/src/controllers/user.controller.js index 4fa5afc..6d0f5db 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,53 +1,69 @@ const User = require('../models/user.model'); +const Mentor = require('../models/mentor.model'); const factory = require('./controllerUtils/handlerFactory'); const AppError = require('../utils/appErrorsClass'); 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 updatedUser = await User.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.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) } + }).populate({ + path: 'skill', + select: 'name' + }); - 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 }); - - res.status(res.locals.statusCode || 200).json({ - status: 'success', - data: updatedUser - }); + res.status(res.locals.statusCode || 200).json({ + status: 'success', + data: mentors + }); }); -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/meeting.model.js b/src/models/meeting.model.js index e19b7a6..5a652c7 100644 --- a/src/models/meeting.model.js +++ b/src/models/meeting.model.js @@ -1,29 +1,33 @@ 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() + } +}); +//-------------------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/models/mentor.model.js b/src/models/mentor.model.js index b472590..365e8cd 100644 --- a/src/models/mentor.model.js +++ b/src/models/mentor.model.js @@ -4,119 +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' - } - ], - 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' + }, + 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' - ); - 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); 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/routes/meeting.routes.js b/src/routes/meeting.routes.js index a237d3e..6b634a7 100644 --- a/src/routes/meeting.routes.js +++ b/src/routes/meeting.routes.js @@ -2,19 +2,14 @@ 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); +router.route('/').get(meetingController.getMyMeetings); router .route('/:id') .get(meetingController.getMeeting) - .patch(meetingController.updateMeeting) - .delete(meetingController.deleteMeeting); - -router.get('MyMeetings', meetingController.getMyMeetings); + .patch(authController.restrictTo('user'), meetingController.createMeeting) + .post(authController.restrictTo('mentor'), meetingController.updateMeeting); //-------------------------------------------// module.exports = router; diff --git a/src/routes/mentor.routes.js b/src/routes/mentor.routes.js index 8acf9dc..bf47c76 100644 --- a/src/routes/mentor.routes.js +++ b/src/routes/mentor.routes.js @@ -9,28 +9,26 @@ 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.post( + '/set-working-hours', + mentorController.getMe, + mentorController.setWorkingHours ); -// router.patch('/updatePassword', authController.updatePassword); //---------------Admin Routes---------------// router.use(authController.restrictTo('admin')); 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..9f15e2e 100644 --- a/src/routes/user.routes.js +++ b/src/routes/user.routes.js @@ -11,22 +11,22 @@ 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('/relevantMentors', userController.getRelevantMentors); + +router.get('/me', userController.getMe, userController.getUser); 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/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; diff --git a/src/utils/ApiFeatures.js b/src/utils/ApiFeatures.js index bfc904a..565557b 100644 --- a/src/utils/ApiFeatures.js +++ b/src/utils/ApiFeatures.js @@ -1,9 +1,37 @@ 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.standMentorsMeeting = meeting => { + const meetingObj = { + _id: meeting._id, + status: meeting.status, + scheduledDate: meeting.scheduledDate, + user: { + _id: meeting.user?._id, + name: meeting.user?.name, + photo: meeting.user?.photo + } + }; + return meetingObj; +}; + +exports.standUsersMeeting = meeting => { + const meetingObj = { + _id: meeting._id, + status: meeting.status, + scheduledDate: meeting.scheduledDate, + mentor: { + _id: meeting.mentor?._id, + name: meeting.mentor?.name, + photo: meeting.mentor?.photo, + skill: meeting.mentor?.skill?.name + } + }; + return meetingObj; }; 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