Prepares the email system for Calendso Teams (#586)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
This commit is contained in:
parent
19f5b9d6c6
commit
d9aff72220
7 changed files with 41 additions and 61 deletions
|
@ -1,5 +1,4 @@
|
|||
import EventOrganizerMail from "./emails/EventOrganizerMail";
|
||||
import EventAttendeeMail from "./emails/EventAttendeeMail";
|
||||
import EventOrganizerRescheduledMail from "./emails/EventOrganizerRescheduledMail";
|
||||
import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail";
|
||||
import prisma from "./prisma";
|
||||
|
@ -120,6 +119,10 @@ export interface CalendarEvent {
|
|||
startTime: string;
|
||||
endTime: string;
|
||||
description?: string;
|
||||
team?: {
|
||||
name: string;
|
||||
members: string[];
|
||||
};
|
||||
location?: string;
|
||||
organizer: Person;
|
||||
attendees: Person[];
|
||||
|
@ -574,25 +577,11 @@ const createEvent = async (
|
|||
entryPoints: maybeEntryPoints,
|
||||
});
|
||||
|
||||
const attendeeMail = new EventAttendeeMail(calEvent, uid, {
|
||||
hangoutLink: maybeHangoutLink,
|
||||
conferenceData: maybeConferenceData,
|
||||
entryPoints: maybeEntryPoints,
|
||||
});
|
||||
|
||||
try {
|
||||
await organizerMail.sendEmail();
|
||||
} catch (e) {
|
||||
console.error("organizerMail.sendEmail failed", e);
|
||||
}
|
||||
|
||||
if (!creationResult || !creationResult.disableConfirmationEmail) {
|
||||
try {
|
||||
await attendeeMail.sendEmail();
|
||||
} catch (e) {
|
||||
console.error("attendeeMail.sendEmail failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -65,7 +65,13 @@ export default class EventAttendeeMail extends EventMail {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Who</td>
|
||||
<td>${this.calEvent.organizer.name}<br /><small>${this.calEvent.organizer.email}</small></td>
|
||||
<td>
|
||||
${this.calEvent.team?.name || this.calEvent.organizer.name}<br />
|
||||
<small>
|
||||
${this.calEvent.organizer.email && !this.calEvent.team ? this.calEvent.organizer.email : ""}
|
||||
${this.calEvent.team ? this.calEvent.team.members.join(", ") : ""}
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Where</td>
|
||||
|
@ -122,6 +128,10 @@ export default class EventAttendeeMail extends EventMail {
|
|||
return ``;
|
||||
}
|
||||
|
||||
protected getAdditionalFooter(): string {
|
||||
return this.parser.getChangeEventFooterHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payload object for the nodemailer.
|
||||
*
|
||||
|
@ -133,7 +143,7 @@ export default class EventAttendeeMail extends EventMail {
|
|||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: this.calEvent.organizer.email,
|
||||
subject: `Confirmed: ${this.calEvent.type} with ${
|
||||
this.calEvent.organizer.name
|
||||
this.calEvent.team?.name || this.calEvent.organizer.name
|
||||
} on ${this.getInviteeStart().format("LT dddd, LL")}`,
|
||||
html: this.getHtmlRepresentation(),
|
||||
text: this.getPlainTextRepresentation(),
|
||||
|
|
|
@ -13,8 +13,8 @@ export default class EventAttendeeRescheduledMail extends EventAttendeeMail {
|
|||
Hi ${this.calEvent.attendees[0].name},<br />
|
||||
<br />
|
||||
Your ${this.calEvent.type} with ${
|
||||
this.calEvent.organizer.name
|
||||
} has been rescheduled to ${this.getInviteeStart().format("h:mma")}
|
||||
this.calEvent.team?.name || this.calEvent.organizer.name
|
||||
} has been rescheduled to ${this.getInviteeStart().format("h:mma")}
|
||||
(${this.calEvent.attendees[0].timeZone}) on ${this.getInviteeStart().format("dddd, LL")}.<br />
|
||||
` +
|
||||
this.getAdditionalFooter() +
|
||||
|
|
|
@ -69,7 +69,7 @@ export default abstract class EventMail {
|
|||
/**
|
||||
* Sends the email to the event attendant and returns a Promise.
|
||||
*/
|
||||
public sendEmail(): Promise<any> {
|
||||
public sendEmail() {
|
||||
new Promise((resolve, reject) =>
|
||||
nodemailer
|
||||
.createTransport(this.getMailerOptions().transport)
|
||||
|
@ -90,7 +90,7 @@ export default abstract class EventMail {
|
|||
*
|
||||
* @protected
|
||||
*/
|
||||
protected getMailerOptions(): any {
|
||||
protected getMailerOptions() {
|
||||
return {
|
||||
transport: serverConfig.transport,
|
||||
from: serverConfig.from,
|
||||
|
@ -135,12 +135,4 @@ export default abstract class EventMail {
|
|||
protected getCancelLink(): string {
|
||||
return this.parser.getCancelLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a footer that will be appended to the email.
|
||||
* @protected
|
||||
*/
|
||||
protected getAdditionalFooter(): string {
|
||||
return this.parser.getChangeEventFooterHtml();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,8 +50,10 @@ export default class EventOrganizerMail extends EventMail {
|
|||
return "A new event has been scheduled.";
|
||||
}
|
||||
|
||||
protected getBodyText(): string {
|
||||
return "You and any other attendees have been emailed with this information.";
|
||||
protected getAdditionalFooter(): string {
|
||||
return `<p style="color: #4b5563; margin-top: 20px;">Need to make a change? <a href=${
|
||||
process.env.BASE_URL + "/bookings"
|
||||
} style="color: #161e2e;">Manage my bookings</a></p>`;
|
||||
}
|
||||
|
||||
protected getImage(): string {
|
||||
|
@ -94,7 +96,6 @@ export default class EventOrganizerMail extends EventMail {
|
|||
>
|
||||
${this.getImage()}
|
||||
<h1 style="font-weight: 500; color: #161e2e;">${this.getBodyHeader()}</h1>
|
||||
<p style="color: #4b5563; margin-bottom: 30px;">${this.getBodyText()}</p>
|
||||
<hr />
|
||||
<table style="border-spacing: 20px; color: #161e2e; margin-bottom: 10px;">
|
||||
<colgroup>
|
||||
|
|
|
@ -6,6 +6,7 @@ import prisma from "@lib/prisma";
|
|||
import { LocationType } from "@lib/location";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import merge from "lodash.merge";
|
||||
import EventAttendeeMail from "@lib/emails/EventAttendeeMail";
|
||||
|
||||
export interface EventResult {
|
||||
type: string;
|
||||
|
@ -65,10 +66,25 @@ export default class EventManager {
|
|||
|
||||
// First, create all calendar events. If this is a dedicated integration event, don't send a mail right here.
|
||||
const results: Array<EventResult> = await this.createAllCalendarEvents(event, isDedicated, maybeUid);
|
||||
|
||||
// If and only if event type is a dedicated meeting, create a dedicated video meeting as well.
|
||||
if (isDedicated) {
|
||||
results.push(await this.createVideoEvent(event, maybeUid));
|
||||
} else {
|
||||
if (!results.length || !results.some((eRes) => eRes.createdEvent.disableConfirmationEmail)) {
|
||||
const metadata: { hangoutLink?: string; conferenceData?: unknown; entryPoints?: unknown } = {};
|
||||
if (results.length) {
|
||||
// TODO: Handle created event metadata more elegantly
|
||||
metadata.hangoutLink = results[0].createdEvent?.hangoutLink;
|
||||
metadata.conferenceData = results[0].createdEvent?.conferenceData;
|
||||
metadata.entryPoints = results[0].createdEvent?.entryPoints;
|
||||
}
|
||||
const attendeeMail = new EventAttendeeMail(event, maybeUid, metadata);
|
||||
try {
|
||||
await attendeeMail.sendEmail();
|
||||
} catch (e) {
|
||||
console.error("attendeeMail.sendEmail failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const referencesToCreate: Array<PartialReference> = results.map((result) => {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { CalendarEvent, getBusyCalendarTimes } from "@lib/calendarClient";
|
|||
import { v5 as uuidv5 } from "uuid";
|
||||
import short from "short-uuid";
|
||||
import { getBusyVideoTimes } from "@lib/videoClient";
|
||||
import EventAttendeeMail from "../../../lib/emails/EventAttendeeMail";
|
||||
import { getEventName } from "@lib/event";
|
||||
import dayjs from "dayjs";
|
||||
import logger from "../../../lib/logger";
|
||||
|
@ -15,7 +14,6 @@ import utc from "dayjs/plugin/utc";
|
|||
import timezone from "dayjs/plugin/timezone";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import dayjsBusinessDays from "dayjs-business-days";
|
||||
import { Exception } from "handlebars";
|
||||
import EventOrganizerRequestMail from "@lib/emails/EventOrganizerRequestMail";
|
||||
|
||||
dayjs.extend(dayjsBusinessDays);
|
||||
|
@ -81,25 +79,6 @@ function isOutOfBounds(
|
|||
}
|
||||
}
|
||||
|
||||
export async function handleLegacyConfirmationMail(
|
||||
results: Array<EventResult>,
|
||||
eventType: EventType,
|
||||
evt: CalendarEvent,
|
||||
hashUID: string
|
||||
): Promise<{ error: Exception; message: string | null }> {
|
||||
if (results.length === 0 && !eventType.requiresConfirmation) {
|
||||
// Legacy as well, as soon as we have a separate email integration class. Just used
|
||||
// to send an email even if there is no integration at all.
|
||||
try {
|
||||
const mail = new EventAttendeeMail(evt, hashUID);
|
||||
await mail.sendEmail();
|
||||
} catch (e) {
|
||||
return { error: e, message: "Booking failed" };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
||||
const { user } = req.query;
|
||||
log.debug(`Booking ${user} started`);
|
||||
|
@ -311,13 +290,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
results.length > 0 ? results[0].uid : translator.fromUUID(uuidv5(JSON.stringify(evt), uuidv5.URL));
|
||||
// TODO Should just be set to the true case as soon as we have a "bare email" integration class.
|
||||
// UID generation should happen in the integration itself, not here.
|
||||
const legacyMailError = await handleLegacyConfirmationMail(results, eventType, evt, hashUID);
|
||||
if (legacyMailError) {
|
||||
log.error("Sending legacy event mail failed", legacyMailError.error);
|
||||
log.error(`Booking ${user} failed`);
|
||||
res.status(500).json({ message: legacyMailError.message });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.booking.create({
|
||||
|
|
Loading…
Reference in a new issue