diff --git a/src/index.ts b/src/index.ts old mode 100644 new mode 100755 index a7b3ce6..cc50b3d --- a/src/index.ts +++ b/src/index.ts @@ -190,7 +190,7 @@ const server = new ApolloServer({ try { authToken = req.headers.authorization && - req.headers.authorization.startsWith("Bearer ") + req.headers.authorization.startsWith("Bearer ") ? req.headers.authorization.split(" ")[1] : req.headers.authorization; if (authToken) { @@ -209,4 +209,4 @@ const server = new ApolloServer({ connect().then(() => { console.log("Database connected!"); server.listen(PORT).then(({ url }) => console.info(`App on ${url}`)); -}); \ No newline at end of file +}); diff --git a/src/models/dismissedStageSchema.ts b/src/models/dismissedStageSchema.ts index 6d355dc..9796d83 100644 --- a/src/models/dismissedStageSchema.ts +++ b/src/models/dismissedStageSchema.ts @@ -29,5 +29,5 @@ const rejectedSchema = new Schema({ timestamps: true }); -const Rejected = mongoose.model("Rejected", rejectedSchema); +const Rejected = mongoose.model("Dismissed", rejectedSchema); export default Rejected; diff --git a/src/resolvers/applicationStageResolver.ts b/src/resolvers/applicationStageResolver.ts index 60a6d14..32b49f4 100644 --- a/src/resolvers/applicationStageResolver.ts +++ b/src/resolvers/applicationStageResolver.ts @@ -7,9 +7,12 @@ import Rejected from "../models/dismissedStageSchema"; import Admitted from "../models/admittedStageSchema"; import InterviewAssessment from "../models/InterviewAssessmentStageSchema"; import TechnicalAssessment from "../models/technicalAssessmentStage"; -import mongoose from "mongoose"; -import { traineEAttributes } from "../models/traineeAttribute"; +import { sendEmailTemplate } from "../helpers/bulkyMails"; +import { ApplicantNotificationsModel } from "../models/applicantNotifications"; import { LoggedUserModel } from "../models/AuthUser"; +import { pusher } from "../helpers/pusher"; +import { traineEAttributes } from "../models/traineeAttribute"; +import mongoose from "mongoose"; const validStages = [ "Shortlisted", @@ -31,7 +34,7 @@ async function getApplicantsByModel(model: any) { async function updateApplicantAfterDismissed(model: any, applicantId: string) { await model.updateOne({ applicantId }, { $set: { status: "Rejected" } }); } -async function updateApplicantAfterAdmitted(model: any, applicantId: string) { +async function updateApplicantAfterAdmitted(model: any,applicantId:string) { await model.updateOne({ applicantId }, { $set: { status: "Admitted" } }); } export const applicationStageResolvers: any = { @@ -179,6 +182,9 @@ export const applicationStageResolvers: any = { context: any ) => { try { + const applicant = await TraineeApplicant.findById(applicantId); + const user = await LoggedUserModel.findOne({ email: applicant!.email }); + if (!context.currentUser) { throw new CustomGraphQLError( "You must be logged in to perform this action" @@ -208,6 +214,8 @@ export const applicationStageResolvers: any = { ); } + let scoreDetails = ""; + if (nextStage === "Interview Assessment") { const technicalScore = await TechnicalAssessment.findOne({ applicantId, @@ -221,6 +229,7 @@ export const applicationStageResolvers: any = { "Technical assessment score is required before moving to the Interview Assessment, Please add score😎." ); } + scoreDetails = `Your Technical Assessment Score: ${technicalScore.score}`; } // If moving to Admitted, ensure Interview Assessment has a score @@ -237,6 +246,7 @@ export const applicationStageResolvers: any = { "Interview assessment score is required before moving to Admitted, Please add score😎." ); } + scoreDetails = `Your Interview Assessment Score: ${interviewScore.interviewScore}`; } if (!stageTracking) { @@ -291,8 +301,36 @@ export const applicationStageResolvers: any = { await TraineeApplicant.updateOne( { _id: applicantId }, { $set: { applicationPhase: nextStage, status: "No action" } } + ); + message = `You have advanced to the ${nextStage} stage.`; + const notification = await ApplicantNotificationsModel.create({ + userId: user!._id, + message, + eventType: "general", + }); + + await sendEmailTemplate( + user!.email, + "Application Update", + `Hello ${user!.email.split("@")[0]}, `, + `Your application has been moved to ${nextStage} stage. +
+ You will hear from us very soon. +
+ Thank you for your patience. + ` ); - message = `Applicant advanced to ${nextStage} stage.`; + + await pusher + .trigger(`notifications-${user!._id}`, "new-notification", { + message: notification.message, + id: notification._id, + createdAt: notification.createdAt, + read: notification.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); break; case "Interview Assessment": @@ -311,7 +349,38 @@ export const applicationStageResolvers: any = { comments, status: "No action", }); - message = `Applicant advanced to ${nextStage} stage.`; + message = `You have advanced to the ${nextStage} stage.`; + const notification1 = await ApplicantNotificationsModel.create({ + userId: user!._id, + message, + eventType: "general", + }); + + await sendEmailTemplate( + user!.email, + "Application Update", + `Hello ${user!.email.split("@")[0]}, `, + `Your application has been moved to ${nextStage} stage. +
+ ${scoreDetails} +
+
+ You will hear from us very soon. +
+ Thank you for your patience. + ` + ); + + await pusher + .trigger(`notifications-${user!._id}`, "new-notification", { + message: notification1.message, + id: notification1._id, + createdAt: notification1.createdAt, + read: notification1.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); break; case "Admitted": @@ -333,8 +402,7 @@ export const applicationStageResolvers: any = { comments, status: "Passed", }); - message = `Applicant passed the application stage✅.`; - + message = `You have passed the application stage✅.`; await TraineeApplicant.updateOne( { _id: applicantId }, { @@ -345,29 +413,38 @@ export const applicationStageResolvers: any = { }, } ); + + const notification2 = await ApplicantNotificationsModel.create({ + userId: user!._id, + message, + eventType: "general", + }); - const updatedApplicant = await TraineeApplicant.findOne({ - _id: applicantId, - }) - .populate("email") - .lean(); - - const email = updatedApplicant?.email; - - if (email) { - await LoggedUserModel.updateOne( - { email }, - { - $set: { - applicationPhase: nextStage, - status: "Admitted", - role: traineeRole._id, - }, - } - ); - } else { - throw new Error("Email not found for the provided applicant ID"); - } + await sendEmailTemplate( + user!.email, + "Application Update", + `Hello ${user!.email.split("@")[0]}, `, + `Your application has successfully passed the application stage. +
+ ${scoreDetails} +
+
+ You will hear from us very soon. +
+ Thank you for your patience. + ` + ); + + await pusher + .trigger(`notifications-${user!._id}`, "new-notification", { + message: notification2.message, + id: notification2._id, + createdAt: notification2.createdAt, + read: notification2.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); break; case "Rejected": @@ -394,7 +471,36 @@ export const applicationStageResolvers: any = { { _id: applicantId }, { $set: { applicationPhase: "Rejected", status: "Rejected" } } ); - message = `Applicant Rejected from the ${stageDismissedFrom?.applicationPhase} stage.`; + message = `You have been rejected from the ${stageDismissedFrom?.applicationPhase} stage.`; + + const notification3 = await ApplicantNotificationsModel.create({ + userId: user!._id, + message, + eventType: "general", + }); + + await sendEmailTemplate( + user!.email, + "Application Update", + `Hello ${user!.email.split("@")[0]}, `, + `We are sorry to inform you that + your application has been rejected from the ${stageDismissedFrom?.applicationPhase} stage. +
+
+ You can always apply again. + ` + ); + + await pusher + .trigger(`notifications-${user!._id}`, "new-notification", { + message: notification3.message, + id: notification3._id, + createdAt: notification3.createdAt, + read: notification3.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); break; case "Shortlisted": @@ -414,7 +520,35 @@ export const applicationStageResolvers: any = { { _id: applicantId }, { $set: { applicationPhase: nextStage, status: "No action" } } ); - message = `Applicant advanced to ${nextStage} stage.`; + message = `You have advanced to the ${nextStage} stage.`; + const notification4 = await ApplicantNotificationsModel.create({ + userId: user!._id, + message, + eventType: "general", + }); + + await sendEmailTemplate( + user!.email, + "Application Update", + `Hello ${user!.email.split("@")[0]}, `, + `Your application has been moved to ${nextStage} stage. +
+ You will hear from us very soon. +
+ Thank you for your patience. + ` + ); + + await pusher + .trigger(`notifications-${user!._id}`, "new-notification", { + message: notification4.message, + id: notification4._id, + createdAt: notification4.createdAt, + read: notification4.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); break; } diff --git a/src/resolvers/ticketResolver.ts b/src/resolvers/ticketResolver.ts index 09028c7..7bdb97e 100644 --- a/src/resolvers/ticketResolver.ts +++ b/src/resolvers/ticketResolver.ts @@ -1,6 +1,11 @@ import { ticketModel } from "../models/ticketModel"; import { LoggedUserModel } from "../models/AuthUser"; import { CustomGraphQLError } from "../utils/customErrorHandler"; +import { ApplicantNotificationsModel } from "../models/applicantNotifications"; +import { RoleModel } from "../models/roleModel"; +import { pusher } from "../helpers/pusher"; +import { sendEmailTemplate } from "../helpers/bulkyMails"; +import { publishNotification } from "./adminNotificationsResolver"; export const ticketResolver = { Query: { @@ -83,6 +88,43 @@ export const ticketResolver = { adminReplies: [], applicantReplies: [] }); + + const message = `Your ticket "${args.title}" has been submitted successfully.`; + const notification = await ApplicantNotificationsModel.create({ + userId: user._id, + message, + eventType: "general", + }); + + await sendEmailTemplate( + user.email, + "Ticket Received", + `Hello ${user.email.split("@")[0]}, `, + `Your Ticket titled: + ${args.title} + has been received and will be reviewed soon. +
+
+ Thank you for your patience. + ` + ); + + await publishNotification( + `${user!.firstname} ${user.lastname} has sent a new ticket.`, + "new_Ticket" + ); + + await pusher + .trigger(`notifications-${user._id}`, "new-notification", { + message: notification.message, + id: notification._id, + createdAt: notification.createdAt, + read: notification.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); + return newTicket.populate('author'); } catch(error: any) { throw new CustomGraphQLError(error.message); @@ -122,6 +164,38 @@ export const ticketResolver = { if(!updatedTicket){ throw new CustomGraphQLError("Ticket not found"); } + await sendEmailTemplate( + user.email, + "Ticket Update", + `Hello ${user.email.split("@")[0]},`, + `Your ticket titled "${updatedTicket.title}" has been received.
+

New response: ${body}

+

Thank you for your patience.
` + ); + + const message = `Your ticket "${updatedTicket.title}" has been updated.`; + const notification = await ApplicantNotificationsModel.create({ + userId: updatedTicket.author._id, + message, + eventType: "general", + }); + + await publishNotification( + `${user!.firstname} ${user.lastname} has sent a new ticket.`, + "new_Ticket" + ); + + await pusher + .trigger(`notifications-${updatedTicket.author._id}`, "new-notification", { + message: notification.message, + id: notification._id, + createdAt: notification.createdAt, + read: notification.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); + return updatedTicket; } catch(err: any){ throw new CustomGraphQLError(err.message); @@ -170,12 +244,40 @@ export const ticketResolver = { if(!resolvedTicket){ throw new CustomGraphQLError("Ticket not found"); } + const message = `Your ticket "${resolvedTicket.title}" has been resolved.`; + const notification = await ApplicantNotificationsModel.create({ + userId: resolvedTicket.author._id, + message, + eventType: "general", + }); + + await pusher + .trigger(`notifications-${resolvedTicket.author._id}`, "new-notification", { + message: notification.message, + id: notification._id, + createdAt: notification.createdAt, + read: notification.read, + }) + .catch((error) => { + console.error("Error with Pusher trigger:", error); + }); + + if (user) { + await sendEmailTemplate( + user.email, + "Ticket Resolved", + `Hello ${user.email.split("@")[0]},`, + `Your ticket titled "${resolvedTicket.title}" has been resolved by our team.
+

Response from Admin: ${adminResponse}

+

Thank you for your patience.
`, + ); + } + return resolvedTicket; } catch(err: any){ throw new CustomGraphQLError(err.message); } } - }, - + }, } \ No newline at end of file diff --git a/src/schema/applicationStage.ts b/src/schema/applicationStage.ts index 5d9f3b1..bc1e9c2 100644 --- a/src/schema/applicationStage.ts +++ b/src/schema/applicationStage.ts @@ -154,4 +154,4 @@ type allStages { applicantStage: String! score: Float!): response! } -`; \ No newline at end of file +`; diff --git a/src/schema/ticketSchema.ts b/src/schema/ticketSchema.ts index 6dd5fd3..673ab27 100644 --- a/src/schema/ticketSchema.ts +++ b/src/schema/ticketSchema.ts @@ -6,7 +6,7 @@ export const ticketSchema = gql` title: String! body: String! status: TicketStatus! - author: User! + author: User createdAt: String! updatedAt: String! adminReplies: [AdminReply!] @@ -16,14 +16,14 @@ export const ticketSchema = gql` type ApplicantReply { id: ID! body: String! - repliedBy: User! + repliedBy: User createdAt: String! } type AdminReply { id: ID! body: String! - repliedBy: User! + repliedBy: User createdAt: String! } @@ -38,7 +38,7 @@ export const ticketSchema = gql` id: ID! body: String! respondedAt: String! - respondedBy: User! + respondedBy: User } enum TicketStatus {