416 lines
19 KiB
TypeScript
416 lines
19 KiB
TypeScript
// 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<NextApiRequest, NextApiResponse>();
|
||
|
||
|
||
//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));
|
||
|