// API endpoint to process email user actions - urls we send in emails to users import { getToken } from "next-auth/jwt"; import type { NextApiRequest, NextApiResponse } from 'next'; import { createRouter, expressWrapper } from "next-connect"; const common = require('../../src/helpers/common'); const data = require('../../src/helpers/data'); const emailHelper = require('../../src/helpers/email'); const { v4: uuidv4 } = require('uuid'); const CON = require("../../src/helpers/const"); import { EventLogType } from "@prisma/client"; const logger = require('../../src/logger'); import fs from 'fs'; import path from 'path'; const handlebars = require("handlebars"); const router = createRouter(); //action to accept coverme request from email /** * * @param req import { NextApiRequest, NextApiResponse } from 'next' * @param res import { NextApiRequest, NextApiResponse } from 'next' */ export default async function handler(req, res) { const prisma = common.getPrismaClient(); const action = req.query.action; const emailaction = req.query.emailaction; // Retrieve and validate the JWT token //response is a special action that does not require a token //PUBLIC if (action == "email_response" || action == "account") { switch (emailaction) { case "coverMeAccept": //validate shiftId and assignmentId let shiftId = req.query.shiftId; let userId = req.query.userId; let publisher = await prisma.publisher.findUnique({ where: { id: userId } }); // Update the user status to accepted console.log("User: " + publisher.firstName + " " + publisher.lastName + " accepted the CoverMe request"); logger.info("" + publisher.firstName + " " + publisher.lastName + " accepted the CoverMe request for shift " + shiftId + " PID: " + req.query.assignmentPID + ""); let assignmentPID = req.query.assignmentPID; if (!shiftId) { return res.status(400).json({ message: "Shift ID is not provided" }); } if (!assignmentPID) { return res.status(400).json({ message: "Assignment PID is not provided" }); } //check if the assignment request is still open const assignment = await prisma.assignment.findFirst({ where: { publicGuid: assignmentPID, shiftId: parseInt(shiftId), isConfirmed: false }, include: { shift: { include: { cartEvent: { include: { location: true } }, assignments: { include: { publisher: true // { // include: { // email: true, // firstName: true, // lastName: true // } // } } } } }, publisher: true } }); if (!assignment) { const messagePageUrl = `/message?message=${encodeURIComponent('Благодаря за желанието, но някой е отговорил на тази заявка за заместване и тя вече е неактивна')}&type=info&caption=${encodeURIComponent('Някой вече те изпревари. Заявката е вече обработена')}`; res.redirect(messagePageUrl); return; } let originalPublisher = assignment.publisher; let to = assignment.shift.assignments.map(a => a.publisher.email); to.push(publisher.email); // update the assignment. clear the guid, isConfirmed to true await prisma.assignment.update({ where: { id: assignment.id }, data: { publisherId: userId, publicGuid: null, // if this exists, we consider the request open isConfirmed: true } }); const newAssignment = await prisma.assignment.findFirst({ where: { shiftId: parseInt(shiftId), isConfirmed: true }, include: { shift: { include: { cartEvent: { include: { location: true } }, assignments: { include: { publisher: true } } } } } }); await prisma.eventLog.create({ data: { date: new Date(), publisher: { connect: { id: publisher.id } }, shift: { connect: { id: assignment.shiftId } }, type: EventLogType.AssignmentReplacementAccepted, content: `Заявката за заместване на ${originalPublisher.firstName} ${originalPublisher.lastName} е приета от ${publisher.firstName} ${publisher.lastName}` } }); const shiftStr = `${CON.weekdaysBG[assignment.shift.startTime.getDay()]} ${CON.GetDateFormat(assignment.shift.startTime)} at ${assignment.shift.cartEvent.location.name} from ${CON.GetTimeFormat(assignment.shift.startTime)} to ${CON.GetTimeFormat(assignment.shift.endTime)}`; const newPubs = newAssignment.shift.assignments.map(a => ({ name: `${a.publisher.firstName} ${a.publisher.lastName}`, phone: a.publisher.phone })); let model = { user: publisher, shiftStr: shiftStr, shiftId: assignment.shiftId, prefix: publisher.isMale ? "Брат" : "Сестра", oldPubName: assignment.publisher.firstName + " " + assignment.publisher.lastName, firstName: publisher.firstName, lastName: publisher.lastName, newPubs: newPubs, placeName: assignment.shift.cartEvent.location.name, dateStr: common.getDateFormated(assignment.shift.startTime), time: common.getTimeFormatted(assignment.shift.startTime), sentDate: common.getDateFormated(new Date()) }; emailHelper.SendEmailHandlebars(to, "coverMeAccepted", model); const messagePageUrl = `/message?message=${encodeURIComponent('Вашата заявка за замстване е обработена успешно')}&type=info&caption=${encodeURIComponent('Благодаря!')}`; res.redirect(messagePageUrl); break; //POST case "send_report": //we can send report form in the emails to the user. process the POSTED data here // Send report form to the user //get from POST data: locationId, date, placementCount, videoCount, returnVisitInfoCount, conversationCount let locationId = req.body.locationId; let date = req.body.date; let placementCount = req.body.placementCount; let videoCount = req.body.videoCount; let returnVisitInfoCount = req.body.returnVisitInfoCount; let conversationCount = req.body.conversationCount; console.log("User: " + user.email + " sent a report: " + locationId + " " + date + " " + placementCount + " " + videoCount + " " + returnVisitInfoCount + " " + conversationCount); //save the report in the database await prisma.report.create({ data: { userId: parseInt(userId), locationId: parseInt(locationId), date: date, placementCount: parseInt(placementCount), videoCount: parseInt(videoCount), returnVisitInfoCount: parseInt(returnVisitInfoCount), conversationCount: parseInt(conversationCount) } }); break; case "resetPassword": // Send password reset form to the user //parse the request body let email = req.body.email || req.query.email; let actualUser = await prisma.publisher.findUnique({ where: { email: email } }); if (!actualUser) { return res.status(200).json({ message: "Няма потребител с този имейл" }); } else { let requestGuid = req.query.guid; if (!requestGuid) { console.log("User: " + email + " requested a password reset"); let requestGuid = uuidv4(); //save the request in the database as EventLog let eventLog = await prisma.eventLog.create({ data: { date: new Date(), publisher: { connect: { id: actualUser.id } }, type: EventLogType.PasswordResetRequested, content: JSON.stringify({ guid: requestGuid }) } }); logger.info("User: " + email + " requested a password reset. EventLogId: " + eventLog.id + ""); let model = { email: email, firstName: actualUser.firstName, lastName: actualUser.lastName, resetUrl: process.env.NEXTAUTH_URL + "/api/email?action=email_response&emailaction=resetPassword&guid=" + requestGuid + "&email=" + email, sentDate: common.getDateFormated(new Date()) }; emailHelper.SendEmailHandlebars(to, "resetPassword", model); res.status(200).json({ message: "Password reset request sent" }); } else { //1. validate the guid let eventLog = await prisma.eventLog.findFirst({ where: {//can we query "{ guid: requestGuid }"? type: EventLogType.PasswordResetRequested, publisherId: actualUser.id, date: { gt: new Date(new Date().getTime() - 24 * 60 * 60 * 1000) //24 hours } } }); if (!eventLog) { return res.status(400).json({ message: "Invalid or expired password reset request" }); } else { let eventLog = await prisma.eventLog.update({ where: { id: parseInt(requestGuid) }, data: { type: EventLogType.PasswordResetEmailConfirmed } }); //2. redirect to the password reset page const messagePageUrl = `/auth/reset-password?email=${email}&resetToken=${requestGuid}`; res.redirect(messagePageUrl); } //2.login the user //3. redirect to the password reset page } } break; } // //send email response to the user // const emailResponse = await common.sendEmail(user.email, "Email Action Processed", // "Your email action was processed successfully"); } else { const token = await getToken({ req: req }); if (!token) { // If no token or invalid token, return unauthorized status return res.status(401).json({ message: "Unauthorized to call this API endpoint" }); } const publisher = await prisma.publisher.findUnique({ where: { email: token.email } }); //PRIVATE ACTIONS switch (action) { case "sendCoverMeRequestByEmail": // Send CoverMe request to the users //get from POST data: shiftId, assignmentId, date //let shiftId = req.body.shiftId; let assignmentId = req.body.assignmentId; let toSubscribed = req.body.toSubscribed; let toAvailable = req.body.toAvailable; let assignment = await prisma.assignment.findUnique({ where: { id: parseInt(assignmentId) }, include: { shift: { include: { cartEvent: { include: { location: true } } } } } }); // update the assignment. generate new publicGuid, isConfirmed to false let newPublicGuid = uuidv4(); await prisma.assignment.update({ where: { id: parseInt(assignmentId) }, data: { publicGuid: newPublicGuid, // if this exists, we consider the request open isConfirmed: false } }); let targetEmails = await data.getCoverMePublisherEmails(assignment.shift.id); if (!toSubscribed) { targetEmails.subscribedPublishers = []; } if (!toAvailable) { targetEmails.availablePublishers = []; } //concat and remove duplicate emails let pubsToSend = targetEmails.subscribedPublishers.concat(targetEmails.availablePublishers). filter((item, index, self) => index === self.findIndex((t) => ( t.email === item.email && item.email !== publisher.email//and exclude the user himself )) ); console.log("Sending CoverMe request to " + pubsToSend.length + " publishers"); let eventLog = await prisma.eventLog.create({ data: { date: new Date(), publisher: { connect: { id: publisher.id } }, shift: { connect: { id: assignment.shift.id } }, type: EventLogType.AssignmentReplacementRequested, content: "Заявка за заместване от " + publisher.firstName + " " + publisher.lastName + " до: " + pubsToSend.map(p => p.firstName + " " + p.lastName + "<" + p.email + ">").join(", "), } }); logger.info("User: " + publisher.email + " sent a 'CoverMe' request for his assignment " + assignmentId + " - " + assignment.shift.cartEvent.location.name + " " + assignment.shift.startTime.toISOString() + " to " + pubsToSend.map(p => p.firstName + " " + p.lastName + "<" + p.email + ">").join(", ") + ". EventLogId: " + eventLog.id + ""); //send email to all subscribed publishers for (let i = 0; i < pubsToSend.length; i++) { //send email to subscribed publisher let acceptUrl = process.env.NEXTAUTH_URL + "/api/email?action=email_response&emailaction=coverMeAccept&userId=" + pubsToSend[i].id + "&shiftId=" + assignment.shift.id + "&assignmentPID=" + newPublicGuid; publisher.prefix = publisher.isMale ? "Брат" : "Сестра"; let model = { user: publisher, shiftId: assignment.shift.id, acceptUrl: acceptUrl, firstName: pubsToSend[i].firstName, lastName: pubsToSend[i].lastName, email: pubsToSend[i].email, placeName: assignment.shift.cartEvent.location.name, dateStr: common.getDateFormated(assignment.shift.startTime), time: common.getTimeFormatted(assignment.shift.startTime), sentDate: common.getDateFormated(new Date()) }; let results = emailHelper.SendEmailHandlebars( { name: pubsToSend[i].firstName + " " + pubsToSend[i].lastName, email: pubsToSend[i].email }, "coverMe", model); // if (results) { // console.log("Error sending email: " + error); // return res.status(500).json({ message: "Error sending email:" + error }); //} if (results) { console.log("Email sent to: " + pubsToSend[i].email); } } res.status(200).json({ message: "CoverMe request sent" }); break; default: return res.status(400).json({ message: "Invalid action" }); } } } router.use(expressWrapper(handler));