diff --git a/client/src/apis/student.js b/client/src/apis/student.js index c948446..a09446d 100644 --- a/client/src/apis/student.js +++ b/client/src/apis/student.js @@ -4,9 +4,9 @@ import { message } from "antd"; export const getStudent = async (id, navigate) => { try { - console.log("get student", backendURL) + console.log("get student", backendURL); const response = await axios.get(`${backendURL}/api/v1/students/${id}`, { - withCredentials: true, + withCredentials: true }); console.log(response.data); return response.data; @@ -20,21 +20,20 @@ export const updateStudent = async (id, data) => { try { console.log(data); const response = await axios.put(`${backendURL}/api/v1/students/${id}`, data, { - withCredentials: true, + withCredentials: true }); return response.data; } catch (error) { console.log(error); return error?.response?.data || error; - } }; export const getAppliedJobsByStudents = async (id, navigate) => { try { const response = await axios.get(`${backendURL}/api/v1/students/${id}/intern-applied`, { - withCredentials: true, + withCredentials: true }); console.log(response); return response.data; @@ -46,17 +45,23 @@ export const getAppliedJobsByStudents = async (id, navigate) => { export const applyToJobs = async (id, internId, navigate) => { try { - const response = await axios.post(`${backendURL}/api/v1/students/${id}/intern-apply/${internId}`, { - withCredentials: true, - }); + const response = await axios.post( + `${backendURL}/api/v1/job/apply`, + { + job_id: internId, + user_id: id + }, + { + headers: { + "Content-Type": "application/json" + }, + withCredentials: true + } + ); // console.log(response); return response.data; } catch (error) { console.log(error); - if (error?.response?.status === 400) { - message.error("Already Applied"); - return error?.response?.data; - } // navigate("/500"); return error?.response?.data || error; } @@ -65,7 +70,7 @@ export const applyToJobs = async (id, internId, navigate) => { export const getAllStudents = async (navigate) => { try { const response = await axios.get(`${backendURL}/api/v1/students`, { - withCredentials: true, + withCredentials: true }); return response.data; } catch (error) { diff --git a/client/src/student/pages/internships/InternshipCard.jsx b/client/src/student/pages/internships/InternshipCard.jsx index 6e859af..a0951ee 100644 --- a/client/src/student/pages/internships/InternshipCard.jsx +++ b/client/src/student/pages/internships/InternshipCard.jsx @@ -21,25 +21,21 @@ function InternshipCard({ arr }) { message.success("Applied Successfully"); navigate(`/student/applied`); } else { - // console.log(res); - if (res.message === "Already applied") { - return; - } - // message.error("Failed to apply"); + message.error(res.message || "Failed to apply"); } }; useEffect(() => { if (arr.applicants) { console.log(`applied ${arr}`); - const appliedOrNot = arr.applicants.find( - (person) => {if(person === user.connection_id){ + const appliedOrNot = arr.applicants.find((person) => { + if (person === user.connection_id) { console.log(person); console.log(user.connection_id); return true; - }} - ); - console.log(appliedOrNot) + } + }); + console.log(appliedOrNot); if (appliedOrNot) { setApplied(true); } diff --git a/client/src/student/pages/internships/InternshipDetail.jsx b/client/src/student/pages/internships/InternshipDetail.jsx index fbf36c0..3ab8835 100644 --- a/client/src/student/pages/internships/InternshipDetail.jsx +++ b/client/src/student/pages/internships/InternshipDetail.jsx @@ -52,11 +52,7 @@ export default function DriveDetail() { message.success("Applied Successfully"); navigate(`/student/applied`); } else { - // console.log(res); - if (res.message === "Already applied") { - return; - } - // message.error("Failed to apply"); + message.error(res.message || "Failed to apply"); } }; diff --git a/server/package.json b/server/package.json index adf023a..a095dad 100644 --- a/server/package.json +++ b/server/package.json @@ -26,6 +26,7 @@ "marked": "^12.0.2", "mongoose": "^8.12.1", "multer": "^1.4.5-lts.1", + "node-cron": "^4.1.1", "node-fetch": "^3.3.2", "querystring": "^0.2.1", "sanitize-html": "^2.13.0", diff --git a/server/src/admin/controller/updates.js b/server/src/admin/controller/updates.js index 88ef3c0..28f96e2 100644 --- a/server/src/admin/controller/updates.js +++ b/server/src/admin/controller/updates.js @@ -1,11 +1,27 @@ import logger from "../../utils/logger.js"; import Updates from "../models/updates.js"; - +import axios from "axios"; const createUpdate = async (req, res) => { try { const { title, description, link } = req.body; const update = await Updates.create({ title, description, link }); logger.info(`Update created with ID: ${update._id}, title: ${title}`); + + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/create`, + { + title, + message: description, + link, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info("Notification sent successfully:", notificationResponse.data); + return res.status(201).json({ status: "success", message: "Update created successfully", @@ -45,7 +61,7 @@ const getUpdateById = async (req, res) => { try { const update = await Updates.findById(id); if (!update) { - logger.warn(`Update with ID ${id} not found`); + logger.warn(`Update with ID ${id} not found`); return res.status(404).json({ status: "error", message: "Update not found", @@ -81,14 +97,14 @@ const editUpdate = async (req, res) => { data: null, }); } - + return res.status(200).json({ status: "success", message: "Update edited successfully", data: update, }); } catch (err) { -logger.error(`Error editing update with ID ${id}: ${err.message}`); + logger.error(`Error editing update with ID ${id}: ${err.message}`); res.status(500).json({ status: "error", message: "Internal server error", @@ -103,12 +119,12 @@ const deleteUpdate = async (req, res) => { const { id } = req.params; const update = await Updates.findByIdAndDelete(id); if (!update) { - logger.warn(`update not found!`); - return res.status(404).json({ - status: "error", - message: "Update not found", - data: null, - }); + logger.warn(`update not found!`); + return res.status(404).json({ + status: "error", + message: "Update not found", + data: null, + }); } logger.info(`Update with ID ${id} was deleted`); return res.status(200).json({ diff --git a/server/src/auth/controllers/auth.js b/server/src/auth/controllers/auth.js index 9e33280..dbcfd5a 100644 --- a/server/src/auth/controllers/auth.js +++ b/server/src/auth/controllers/auth.js @@ -2,7 +2,11 @@ import dotenv from "dotenv"; import querystring from "querystring"; import jwt from "jsonwebtoken"; import { roles } from "../../utils/roles.js"; -import { createUser, getUserFromToken } from "../../users/controller.js"; +import { + createUser, + getUserFromToken, + generateEmailVerificationToken, +} from "../../users/controller.js"; import { User } from "../../users/model.js"; import logger from "../../utils/logger.js"; import { frontendUrl } from "../../../frontend-url.js"; @@ -97,6 +101,7 @@ export const onedriveRedirect = async (req, res) => { user_id: findUser._id, connection_id: findUser.connection_id, typeOfUser: findUser.typeOfUser, + isVerified: findUser.isVerified, }; const stringify = JSON.stringify(userCookie); @@ -123,34 +128,58 @@ export const onedriveRedirect = async (req, res) => { name: userDataFromGraphApi.name, email: userDataFromGraphApi.email, typeOfUser: state, + isVerified: false, }); + // Generate email verification token and send verification email + const verificationToken = generateEmailVerificationToken(createdUser); + const verificationLink = `${ + process.env.EMAIL_VERIFICATION_URL || frontendUrl + }/api/users/verify?token=${verificationToken}`; + try { + await axios.post(`${process.env.EMAIL_URL}/send-email`, { + emails: [createdUser.email], + subject: "Verify your email for Research Intern Portal, IIT Guwahati", + message: `Welcome! Please verify your email by clicking the following link: ${verificationLink}\n\nIf you did not request this, please ignore this email.`, + }); + } catch (err) { + logger.error("Failed to send verification email", err); + } + const newUserCookie = { name: createdUser.name, user_id: createdUser._id, connection_id: createdUser.connection_id, typeOfUser: createdUser.typeOfUser, + isVerified: createdUser.isVerified, }; - // if (newUserCookie) { - // try { - // await axios.post(`${process.env.NOTIFICATION_URL}/createOne`, { - // title: "Welcome to Research Intern Portal, IIT Guwahati", - // message: - // "We are glad to have you on board! Feel free to explore the platform and reach out to us in case of any queries. We recommend you to complete your profile to get the best experience.", - // link: `/profile`, - // userIds: [createdUser.connection_id], - // }); - // await axios.post(`${process.env.EMAIL_URL}/send-email`, { - // emails: [createdUser.email], - // subject: "Welcome to Research Intern Portal, IIT Guwahati", - // message: `We are glad to have you on board! Feel free to explore the platform and reach out to us in case of any queries. We recommend you to complete your profile to get the best experience.`, - // }); - // } catch (err) { - // console.log(err); - // logger.error(err); - // } - // } + if (newUserCookie) { + try { + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/createOne`, + { + title: "Welcome to Research Intern Portal, IIT Guwahati", + message: + "We are glad to have you on board! Feel free to explore the platform and reach out to us in case of any queries. We recommend you to complete your profile to get the best experience.", + link: `/${createdUser.typeOfUser}/profile/overview`, + userIds: [createdUser.connection_id], + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info( + "Notification sent successfully:", + notificationResponse.data + ); + } catch (err) { + console.log(err); + logger.error(err); + } + } const stringify = JSON.stringify(newUserCookie); diff --git a/server/src/index.js b/server/src/index.js index 01fe2ee..e0b35b4 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -19,7 +19,7 @@ import adminUpdateRoutes from "./admin/routes/updates.js"; import studentRoutes from "./students/routes/student.js"; import jobRoutes from "./recruiter/routes/jobs.js"; import recruiterRoutes from "./recruiter/routes/recruiter.js"; - +import jobExpiryNotifier from "./recruiter/notifier.js"; const app = express(); setupSwagger(app); @@ -53,7 +53,6 @@ app.use("/api/v1/students", studentRoutes); // app.use("/api/v1/recruiters", verifyJWT, recruiterGuard, recruiterRoutes); app.use("/api/v1/recruiters", recruiterRoutes); app.use("/api/v1/job", jobRoutes); - app.use("/api/v1/admin/bugs", bugRoutes); // test route @@ -65,5 +64,6 @@ app.get("/ping", (req, res) => { app.use(errorHandler); app.listen(data.PORT, async () => { await connectToDb(); + jobExpiryNotifier(); logger.info(`Server is running on ${data.PORT}`); }); diff --git a/server/src/recruiter/controllers/jobs.js b/server/src/recruiter/controllers/jobs.js index a440758..9cbd206 100644 --- a/server/src/recruiter/controllers/jobs.js +++ b/server/src/recruiter/controllers/jobs.js @@ -3,12 +3,13 @@ import Student from "../../students/models/student.js"; import logger from "../../utils/logger.js"; import recruiter from "../models/recruiter.js"; import axios from "axios"; -import mongoose from "mongoose"; +import mongoose from "mongoose"; const createJob = async (req, res) => { try { const data = req.body; - const recruiter_data = await recruiter.findById(req?.user?.connection_id); + // console.log(data); + const recruiter_data = await recruiter.findById(data?.recruiter); // if (recruiter_data.isVerified === false) { // return res.status(400).json({ @@ -18,15 +19,22 @@ const createJob = async (req, res) => { // }); // } const job = await Jobs.create(data); - // const response = await axios.post( - // `${process.env.NOTIFICATION_URL}/create-students`, - // { - // title: "New Job", - // message: `A new internship opportunity has been posted by ${recruiter_data.name}.\nClick on "View More" to know more about the internship.`, - // link: `/internships/internship/${job._id}`, - // } - // ); - // console.log(response.data); + + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/create-students`, + { + title: "New Job", + message: `A new internship opportunity has been posted by ${recruiter_data.name}.\nClick on "View More" to know more about the internship.`, + link: `/student/internships/internship/${job._id}`, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info("Notification sent successfully:", notificationResponse.data); + return res.status(201).json({ message: "Job created successfully", data: job, @@ -41,9 +49,6 @@ const createJob = async (req, res) => { } }; - - - const getAllJobsOfRecruiter = async (req, res) => { try { const { recruiter_id } = req.params; @@ -138,7 +143,7 @@ const stopAcceptingApplications = async (req, res) => { await axios.post(`${process.env.NOTIFICATION_URL}/create-students`, { title: "Changes in Application Criteria", message: `${recruiter_data.name} has stopped accepting applications for the internship.`, - link: `/internships/internship/${job._id}`, + link: `/student/internships/internship/${job._id}`, }); return res.status(200).json({ @@ -165,11 +170,22 @@ const reopenApplications = async (req, res) => { .json({ message: "Job not found", data: null, status: "error" }); } const recruiter_data = await recruiter.findById(job.recruiter); - await axios.post(`${process.env.NOTIFICATION_URL}/create-students`, { - title: "Application Reopened", - message: `The application period of internship created by ${recruiter_data.name} has been reopened.\nClick on "View More" to know more about the internship.`, - link: `/internships/internship/${job._id}`, - }); + + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/createOne`, + { + title: "Application Reopened", + message: `The application period of internship created by ${recruiter_data.name} has been reopened.\nClick on "View More" to know more about the internship.`, + link: `/student/internships/internship/${job._id}`, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info("Notification sent successfully:", notificationResponse.data); + return res.status(200).json({ message: "Job applications reopened", data: job, @@ -177,6 +193,7 @@ const reopenApplications = async (req, res) => { }); } catch (error) { logger.error(error); + return res .status(500) .json({ message: "Server Error", data: null, status: "error" }); @@ -195,11 +212,21 @@ const updateJob = async (req, res) => { } const recruiter_data = await recruiter.findById(job.recruiter); console.log(recruiter_data); - // await axios.post(`${process.env.NOTIFICATION_URL}/create-students`, { - // title: "Changes in Application Criteria", - // message: `${recruiter_data.name} has changed the application criteria for the internship.\nClick on "View More" to know more about the internship.`, - // link: `/internships/internship/${job._id}`, - // }); + + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/create-students`, + { + title: "Changes in Application Criteria", + message: `${recruiter_data.name} has changed the application criteria for the internship.\nClick on "View More" to know more about the internship.`, + link: `/student/internships/internship/${job._id}`, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info("Notification sent successfully:", notificationResponse.data); return res.status(200).json({ message: "Job updated successfully", @@ -238,15 +265,14 @@ const updateJob = async (req, res) => { // ); // } // console.log('applicationsData=',applicantsData); - + // return res.status(200).json({ // message: "Job retrieved successfully", // data: applicantsData, // status: "success", // }); // } - - + // catch (error) { // logger.error(error); // console.log(error); @@ -284,10 +310,16 @@ const getAllStudentsOfJob = async (req, res) => { let applicantId; // 🧠 Decide format: Object or direct ID - if (typeof applicantEntry === 'string' || typeof applicantEntry === 'object' && applicantEntry.toString) { + if ( + typeof applicantEntry === "string" || + (typeof applicantEntry === "object" && applicantEntry.toString) + ) { // Case: applicantEntry is a direct ID applicantId = applicantEntry; - } else if (typeof applicantEntry === 'object' && applicantEntry.applicant) { + } else if ( + typeof applicantEntry === "object" && + applicantEntry.applicant + ) { // Case: applicantEntry is { applicant: 'studentId' } applicantId = applicantEntry.applicant; } else { @@ -313,10 +345,9 @@ const getAllStudentsOfJob = async (req, res) => { return res.status(200).json({ message: "Job retrieved successfully", - data: applicantsData.filter(Boolean), + data: applicantsData.filter(Boolean), status: "success", }); - } catch (error) { console.error("Server Error in getAllStudentsOfJob:", error); return res.status(500).json({ @@ -327,7 +358,6 @@ const getAllStudentsOfJob = async (req, res) => { } }; - const getJobByfilter = async (req, res) => { try { const data = req.body; @@ -348,36 +378,86 @@ const getJobByfilter = async (req, res) => { const applyForJob = async (req, res) => { try { const { job_id, user_id } = req.body; - let user = await Student.findById(user_id); - let job = await Jobs.findById(job_id); - let jobRequirement = job.requirements; - - if ( - jobRequirement.cpi <= user.cpi && - jobRequirement.department == user.department && - new Date(job.last_date) >= new Date() - ) { - const apply = await Jobs.findByIdAndUpdate(job_id, { - $push: { applicants: user_id }, + const [student, job] = await Promise.all([ + Student.findById(user_id), + Jobs.findById(job_id), + ]); + + if (!student) { + return res.status(404).json({ + status: "error", + message: "Invalid Student ID", + data: null, + }); + } + if (!job) { + return res.status(404).json({ + status: "error", + message: "Invalid Job ID", + data: null, + }); + } + + if (student.applications.includes(job_id)) { + return res.status(400).json({ + status: "error", + message: "Already applied", + data: null, }); - if (!apply) { - return res.status(404).json({ message: "Something went wrong" }); - } - return res - .status(200) - .json({ message: "Successfully applied for the job" }); - } else { - return res.status(404).json({ message: "Requirements did't match" }); } + + const jobReq = job.requirements; + const deadline = new Date(job.last_date) < new Date(); + const cpiReq = jobReq.cpi > student.cpi; + const deptReq = !jobReq.department.includes(student.department); + if (cpiReq || deptReq || deadline) { + console.log(deadline, cpiReq, deptReq); + return res.status(400).json({ + status: "error", + message: "Requirements didn't match or application deadline passed", + data: null, + }); + } + + student.applications.push(job._id); + job.applicants.push(student._id); + await Promise.all([ + student.save({ validateBeforeSave: false }), + job.save({ validateBeforeSave: false }), + ]); + + const recruiterData = await recruiter.findById(job.recruiter); + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/createOne`, + { + title: "Application Submitted Successfully!", + message: `Your application for the internship "${job.title}" by ${recruiterData.name} has been submitted successfully.`, + link: `/student/internships/internship/${job._id}`, + userIds: [user_id], + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info("Notification sent successfully:", notificationResponse.data); + + return res.status(200).json({ + status: "success", + message: "Successfully applied for the internship", + data: null, + }); } catch (error) { logger.error(error); - return res - .status(500) - .json({ message: "Server Error", error: error.message }); + return res.status(500).json({ + status: "error", + message: "Internal Server Error", + data: error.message, + }); } }; - export { getJob, getJobById, diff --git a/server/src/recruiter/controllers/recruiter.js b/server/src/recruiter/controllers/recruiter.js index affc6c4..d9bbcd2 100644 --- a/server/src/recruiter/controllers/recruiter.js +++ b/server/src/recruiter/controllers/recruiter.js @@ -6,7 +6,7 @@ import logger from "../../utils/logger.js"; import { User } from "../../users/model.js"; import jobs from "../models/jobs.js"; import student from "../../students/models/student.js"; - +import axios from "axios"; const createRecuiter = async (req, res) => { try { const { name, email } = req.body; @@ -201,7 +201,7 @@ const deleteRecruiter = async (req, res) => { }; const acceptStudentForJob = async (req, res) => { - console.log('hi job is hit') + console.log("hi job is hit"); try { const { job_id, student_id } = req.body; const job = await jobs.findById(job_id); @@ -221,11 +221,20 @@ const acceptStudentForJob = async (req, res) => { job.selected_student.push(student_id); job.save(); - // await axios.post(`${process.env.NOTIFICATION_URL}/createOne`, { - // title: "Selected for Internship", - // message: `Congratulations! You have been selected for the internship posted by ${recruiter_data.name}. An email has been sent to your Outlook email with the details of the further procedure.`, - // userIds: [student_id], - // }); + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/createOne`, + { + title: "Selected for Internship", + message: `Congratulations! You have been selected for the internship posted by ${recruiter_data.name}. An email has been sent to your Outlook email with the details of the further procedure.`, + userIds: [student_id], + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info("Notification sent successfully:", notificationResponse.data); // await axios.post(`${process.env.EMAIL_URL}/send-email`, { // emails: [student_data.email], @@ -268,11 +277,20 @@ const rejectStudentForJob = async (req, res) => { job.rejected_student.push(student_id); job.save(); - // await axios.post(`${process.env.NOTIFICATION_URL}/createOne`, { - // title: "Application Rejected", - // message: `Your application for the internship created by ${recruiter_data.name} has been rejected.`, - // userIds: [student_id], - // }); + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/createOne`, + { + title: "Application Rejected", + message: `Your application for the internship created by ${recruiter_data.name} has been rejected.`, + userIds: [student_id], + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info("Notification sent successfully:", notificationResponse.data); // await axios.post(`${process.env.EMAIL_URL}/send-email`, { // emails: [student_data.email], diff --git a/server/src/recruiter/models/jobs.js b/server/src/recruiter/models/jobs.js index 2e5627f..28954e6 100644 --- a/server/src/recruiter/models/jobs.js +++ b/server/src/recruiter/models/jobs.js @@ -7,7 +7,7 @@ const requirementSchema = new mongoose.Schema({ }, department: { type: [String], - default:"Computer Science", + default: "Computer Science", // required: true, }, study_year: { @@ -60,8 +60,8 @@ const JobSchema = new mongoose.Schema({ type: Number, required: true, }, - applicants:{ - type: [String] + applicants: { + type: [String], }, selected_student: { diff --git a/server/src/recruiter/notifier.js b/server/src/recruiter/notifier.js new file mode 100644 index 0000000..e26932c --- /dev/null +++ b/server/src/recruiter/notifier.js @@ -0,0 +1,53 @@ +import cron from "node-cron"; +import axios from "axios"; +import Jobs from "./models/jobs.js"; +import recruiter from "./models/recruiter.js"; +import logger from "../utils/logger.js"; + +export default function jobExpiryNotifier() { + logger.info("jobExpiryNotifier started"); + + // Runs everyday at 12 noon + cron.schedule("0 12 * * *", async () => { + logger.info("Checking for jobs expiring within 24 hours..."); + + const now = new Date(); + const after24hrs = new Date(now.getTime() + 24 * 60 * 60 * 1000); + + try { + const expiringJobs = await Jobs.find({ + accepting: true, + last_date: { + $gte: now, + $lte: after24hrs, + }, + }); + + for (const job of expiringJobs) { + const recruiter_data = await recruiter.findById(job.recruiter); + const notificationResponse = await axios.post( + `${process.env.NOTIFICATION_URL}/create-students`, + { + title: `Internship is closing soon!`, + message: `Applications for "${job.title}" by ${recruiter_data.name} will close within 24 hours.\nClick on "View More" to know more about the internship.`, + link: `/student/internships/internship/${job._id}`, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + logger.info( + "Notification sent successfully:", + notificationResponse.data + ); + } + } catch (err) { + logger.error( + "jobExpiryNotifier error:", + err?.response?.data || err.message + ); + } + }); +} diff --git a/server/src/students/controllers/student.js b/server/src/students/controllers/student.js index 683b572..0c42bd9 100644 --- a/server/src/students/controllers/student.js +++ b/server/src/students/controllers/student.js @@ -104,7 +104,6 @@ const updateStudent = async (req, res) => { } }; - // const deleteStudent = async (req, res) => { // try { // const id = req.params.id; @@ -295,62 +294,6 @@ const getStudentsApplicationById = async (req, res) => { } }; -const addStudentsApplications = async (req, res) => { - try { - console.log(req.params); - const { id, internId } = req.params; - //assumed for now that the id for the document of intern post is sent is params - - //checking if the id's sent are true or not - const [student, intern] = await Promise.all([ - Student.findById(id), - Jobs.findById(internId), - ]); - - if (!student) { - return res.status(404).json({ - status: "error", - message: "Invalid Student Id", - data: null, - }); - } - if (!intern) { - return res.status(404).json({ - status: "error", - message: "Invalid Intern Id", - data: null, - }); - } - - const applicationsList = student.applications; - if (applicationsList.includes(internId)) { - return res.status(400).json({ - status: "error", - message: "Already applied", - data: null, - }); - } - - student.applications.push(intern._id); - // intern.applicants.push({ applicant: id, enum: "pending" }); - intern.applicants.push(id); - await student.save({ validateBeforeSave: false }); - await intern.save({ validateBeforeSave: false }); - return res.status(200).json({ - status: "success", - message: "Intern Applied", - data: null, - }); - } catch (error) { - logger.error(error); - return res.status(500).json({ - status: "error", - message: "Internal Server Error Occurred", - data: null, - }); - } -}; - const logoutStudent = async (req, res) => { try { const options = { @@ -384,7 +327,5 @@ export { getStudentByInterests, getStudentsByFilter, getStudentsApplicationById, - addStudentsApplications, logoutStudent, }; - \ No newline at end of file diff --git a/server/src/students/routes/student.js b/server/src/students/routes/student.js index 289efba..631ca86 100644 --- a/server/src/students/routes/student.js +++ b/server/src/students/routes/student.js @@ -9,8 +9,7 @@ import { updateStudent, createStudent, getStudentsApplicationById, - addStudentsApplications, - logoutStudent + logoutStudent, } from "../controllers/student.js"; import { upload, uploadFile } from "../upload/onedrive.upload.js"; import verifyJWT from "../../middlewares/token-verify.js"; @@ -54,7 +53,7 @@ const router = express.Router(); * description: Internal server error */ router.get("/", getStudents); -router.post("/upload", upload.single("file"),verifyJWT, uploadFile); +router.post("/upload", upload.single("file"), verifyJWT, uploadFile); /** * @swagger @@ -219,47 +218,6 @@ router.get("/search-interest", getStudentByInterests); */ router.get("/:id/intern-applied", getStudentsApplicationById); -/** - * @swagger - * /api/v1/students/{id}/intern-apply/{internId}: - * post: - * summary: Apply for an internship - * tags: [Students] - * parameters: - * - in: path - * name: id - * required: true - * description: The ID of the student - * schema: - * type: string - * - in: path - * name: internId - * required: true - * description: The ID of the internship - * schema: - * type: string - * responses: - * 200: - * description: Successfully applied for the internship - * content: - * application/json: - * schema: - * type: object - * properties: - * status: - * type: string - * example: success - * message: - * type: string - * example: Intern Applied - * 400: - * description: Already applied or invalid IDs - * 500: - * description: Internal server error - */ - -router.post("/:id/intern-apply/:internId", addStudentsApplications); - /** * @swagger * /api/v1/students/{id}: @@ -338,7 +296,7 @@ router.post("/:id/intern-apply/:internId", addStudentsApplications); */ router.put("/:id", updateStudent); router.post("/create", createStudent); -router.get("/logout/:id" , logoutStudent) +router.get("/logout/:id", logoutStudent); //router.delete('/:id', deleteStudent); export default router; diff --git a/server/src/users/controller.js b/server/src/users/controller.js index f7b7298..5fa3e1d 100644 --- a/server/src/users/controller.js +++ b/server/src/users/controller.js @@ -7,8 +7,13 @@ import logger from "../utils/logger.js"; import Admin from "../admin/models/admin.js"; import axios from "axios"; import { adminList } from "./admin-list.js"; +import jwt from "jsonwebtoken"; // import Admin from "../admin/models/updates.js" +const EMAIL_VERIFICATION_SECRET = + process.env.EMAIL_VERIFICATION_SECRET || "email_verification_secret"; +const EMAIL_VERIFICATION_EXPIRY = "1h"; // 1 hour + export const createUser = async (data) => { try { const { name, email, typeOfUser } = data; @@ -33,18 +38,17 @@ export const createUser = async (data) => { } else if (typeOfUser === roles.ADMIN) { // console.log(adminList.includes(email)); - if(!adminList.includes(email)){ + if (!adminList.includes(email)) { throw new Error("You are not authorized to create an admin account"); } // if(adminList.includes(email)){ - user = await Admin.create({ - name, - email, - createdAt: new Date(), - updatedAt: new Date(), - }); - + user = await Admin.create({ + name, + email, + createdAt: new Date(), + updatedAt: new Date(), + }); } const appUser = await User.create({ @@ -114,3 +118,35 @@ export const getSavedJobs = async (req, res) => { return res.status(500).json({ message: "Internal server error" }); } }; + +export const generateEmailVerificationToken = (user) => { + const token = jwt.sign( + { userId: user._id, email: user.email }, + EMAIL_VERIFICATION_SECRET, + { expiresIn: EMAIL_VERIFICATION_EXPIRY } + ); + // console.log(`the token is: ${token}`); + return token; +}; + +export const verifyEmail = async (req, res) => { + const { token } = req.query; + if (!token) { + return res.status(400).json({ message: "Verification token is required" }); + } + try { + const decoded = jwt.verify(token, EMAIL_VERIFICATION_SECRET); + const user = await User.findById(decoded.userId); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + if (user.isVerified) { + return res.status(200).json({ message: "Email already verified" }); + } + user.isVerified = true; + await user.save(); + return res.status(200).json({ message: "Email verified successfully" }); + } catch (err) { + return res.status(400).json({ message: "Invalid or expired token" }); + } +}; diff --git a/server/src/users/model.js b/server/src/users/model.js index 925a960..3fc389f 100644 --- a/server/src/users/model.js +++ b/server/src/users/model.js @@ -41,6 +41,10 @@ const UserSchema = new mongoose.Schema({ type: mongoose.Schema.Types.ObjectId, ref: "Jobs", // Assuming you have a 'Job' model }], + isVerified: { + type: Boolean, + default: false, + }, }); export const User = mongoose.model("User", UserSchema); diff --git a/server/src/users/routers.js b/server/src/users/routers.js index 8e893d1..b213019 100644 --- a/server/src/users/routers.js +++ b/server/src/users/routers.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { createUser, getSavedJobs } from "./controller"; +import { createUser, getSavedJobs, verifyEmail } from "./controller.js"; const router = Router(); /** @@ -37,7 +37,23 @@ const router = Router(); * description: Internal server error */ router.post("/", createUser); - +/** + * @swagger + * /users/verify: + * get: + * tags: + * - Users + * summary: Verify email + * description: Verify the email of a user + * responses: + * 200: + * description: Email verified successfully + * 400: + * description: Invalid request parameters + * 500: + * description: Internal server error + */ +router.get("/verify", verifyEmail); /** * @swagger * /users/{userId}/saved-jobs: