added reminder emails for opt-in bookings

pull/371/head
Malte Delfs 2021-07-18 21:12:35 +02:00
parent a2bf242c9e
commit f5516ed427
5 changed files with 126 additions and 0 deletions

View File

@ -32,3 +32,5 @@ EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER='<office365_emailAddress>'
# Keep in mind that if you have 2FA enabled, you will need to provision an App Password.
EMAIL_SERVER_PASSWORD='<office365_password>'
# ApiKey for cronjobs
CRON_API_KEY='0cc0e6c35519bba620c9360cfe3e68d0'

View File

@ -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}`;
}
}

View File

@ -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 });
}
}

View File

@ -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")
);

View File

@ -176,3 +176,15 @@ model ResetPasswordRequest {
email String
expires DateTime
}
enum ReminderType {
PENDING_BOOKING_CONFIRMATION
}
model ReminderMail {
id Int @id @default(autoincrement())
referenceId Int
reminderType ReminderType
elapsedMinutes Int
createdAt DateTime @default(now())
}