added reminder emails for opt-in bookings
parent
a2bf242c9e
commit
f5516ed427
|
@ -32,3 +32,5 @@ EMAIL_SERVER_PORT=587
|
||||||
EMAIL_SERVER_USER='<office365_emailAddress>'
|
EMAIL_SERVER_USER='<office365_emailAddress>'
|
||||||
# Keep in mind that if you have 2FA enabled, you will need to provision an App Password.
|
# Keep in mind that if you have 2FA enabled, you will need to provision an App Password.
|
||||||
EMAIL_SERVER_PASSWORD='<office365_password>'
|
EMAIL_SERVER_PASSWORD='<office365_password>'
|
||||||
|
# ApiKey for cronjobs
|
||||||
|
CRON_API_KEY='0cc0e6c35519bba620c9360cfe3e68d0'
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
import toArray from "dayjs/plugin/toArray";
|
||||||
|
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||||
|
import EventOrganizerRequestMail from "@lib/emails/EventOrganizerRequestMail";
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
dayjs.extend(toArray);
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
|
export default class EventOrganizerRequestReminderMail extends EventOrganizerRequestMail {
|
||||||
|
protected getBodyHeader(): string {
|
||||||
|
return "An event is still waiting for your approval.";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getSubject(): string {
|
||||||
|
const organizerStart: Dayjs = <Dayjs>dayjs(this.calEvent.startTime).tz(this.calEvent.organizer.timeZone);
|
||||||
|
return `Event request is still waiting: ${this.calEvent.attendees[0].name} - ${organizerStart.format(
|
||||||
|
"LT dddd, LL"
|
||||||
|
)} - ${this.calEvent.type}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import prisma from "@lib/prisma";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { ReminderType } from "@prisma/client";
|
||||||
|
import EventOrganizerRequestReminderMail from "@lib/emails/EventOrganizerRequestReminderMail";
|
||||||
|
import { CalendarEvent } from "@lib/calendarClient";
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const apiKey = req.query.apiKey;
|
||||||
|
if (process.env.CRON_API_KEY != apiKey) {
|
||||||
|
return res.status(401).json({ message: "Not authenticated" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method == "POST") {
|
||||||
|
const reminderIntervalMinutes = [48 * 60, 24 * 60, 3 * 60];
|
||||||
|
let notificationsSent = 0;
|
||||||
|
for (const interval of reminderIntervalMinutes) {
|
||||||
|
const bookings = await prisma.booking.findMany({
|
||||||
|
where: {
|
||||||
|
confirmed: false,
|
||||||
|
rejected: false,
|
||||||
|
createdAt: {
|
||||||
|
lte: dayjs().add(-interval, "minutes").toDate(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
title: true,
|
||||||
|
description: true,
|
||||||
|
startTime: true,
|
||||||
|
endTime: true,
|
||||||
|
attendees: true,
|
||||||
|
user: true,
|
||||||
|
id: true,
|
||||||
|
uid: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const reminders = await prisma.reminderMail.findMany({
|
||||||
|
where: {
|
||||||
|
reminderType: ReminderType.PENDING_BOOKING_CONFIRMATION,
|
||||||
|
referenceId: {
|
||||||
|
in: bookings.map((b) => b.id),
|
||||||
|
},
|
||||||
|
elapsedMinutes: {
|
||||||
|
gte: interval,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const booking of bookings.filter((b) => !reminders.some((r) => r.referenceId == b.id))) {
|
||||||
|
const evt: CalendarEvent = {
|
||||||
|
type: booking.title,
|
||||||
|
title: booking.title,
|
||||||
|
description: booking.description,
|
||||||
|
startTime: booking.startTime.toISOString(),
|
||||||
|
endTime: booking.endTime.toISOString(),
|
||||||
|
organizer: { email: booking.user.email, name: booking.user.name, timeZone: booking.user.timeZone },
|
||||||
|
attendees: booking.attendees,
|
||||||
|
};
|
||||||
|
|
||||||
|
await new EventOrganizerRequestReminderMail(evt, booking.uid).sendEmail();
|
||||||
|
await prisma.reminderMail.create({
|
||||||
|
data: {
|
||||||
|
referenceId: booking.id,
|
||||||
|
reminderType: ReminderType.PENDING_BOOKING_CONFIRMATION,
|
||||||
|
elapsedMinutes: interval,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
notificationsSent++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.status(200).json({ notificationsSent });
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "ReminderType" AS ENUM ('PENDING_BOOKING_CONFIRMATION');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ReminderMail" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"referenceId" INTEGER NOT NULL,
|
||||||
|
"reminderType" "ReminderType" NOT NULL,
|
||||||
|
"elapsedMinutes" INTEGER NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
);
|
|
@ -176,3 +176,15 @@ model ResetPasswordRequest {
|
||||||
email String
|
email String
|
||||||
expires DateTime
|
expires DateTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ReminderType {
|
||||||
|
PENDING_BOOKING_CONFIRMATION
|
||||||
|
}
|
||||||
|
|
||||||
|
model ReminderMail {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
referenceId Int
|
||||||
|
reminderType ReminderType
|
||||||
|
elapsedMinutes Int
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue