diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts index 44453219..d0752699 100644 --- a/lib/calendarClient.ts +++ b/lib/calendarClient.ts @@ -111,7 +111,7 @@ interface Person { timeZone: string; } -interface CalendarEvent { +export interface CalendarEvent { type: string; title: string; startTime: string; @@ -123,18 +123,18 @@ interface CalendarEvent { conferenceData?: ConferenceData; } -interface ConferenceData { +export interface ConferenceData { createRequest: any; } -interface IntegrationCalendar { +export interface IntegrationCalendar { integration: string; primary: boolean; externalId: string; name: string; } -interface CalendarApiAdapter { +export interface CalendarApiAdapter { createEvent(event: CalendarEvent): Promise; updateEvent(uid: string, event: CalendarEvent); @@ -512,8 +512,22 @@ const createEvent = async (credential, calEvent: CalendarEvent): Promise => const creationResult = credential ? await calendars([credential])[0].createEvent(calEvent) : null; - const organizerMail = new EventOrganizerMail(calEvent, uid); - const attendeeMail = new EventAttendeeMail(calEvent, uid); + const maybeHangoutLink = creationResult?.hangoutLink; + const maybeEntryPoints = creationResult?.entryPoints; + const maybeConferenceData = creationResult?.conferenceData; + + const organizerMail = new EventOrganizerMail(calEvent, uid, { + hangoutLink: maybeHangoutLink, + conferenceData: maybeConferenceData, + entryPoints: maybeEntryPoints, + }); + + const attendeeMail = new EventAttendeeMail(calEvent, uid, { + hangoutLink: maybeHangoutLink, + conferenceData: maybeConferenceData, + entryPoints: maybeEntryPoints, + }); + try { await organizerMail.sendEmail(); } catch (e) { @@ -571,12 +585,4 @@ const deleteEvent = (credential, uid: string): Promise => { return Promise.resolve({}); }; -export { - getBusyCalendarTimes, - createEvent, - updateEvent, - deleteEvent, - CalendarEvent, - listCalendars, - IntegrationCalendar, -}; +export { getBusyCalendarTimes, createEvent, updateEvent, deleteEvent, listCalendars }; diff --git a/lib/emails/EventAttendeeMail.ts b/lib/emails/EventAttendeeMail.ts index 55f231f9..4a1b3d10 100644 --- a/lib/emails/EventAttendeeMail.ts +++ b/lib/emails/EventAttendeeMail.ts @@ -1,9 +1,9 @@ -import dayjs, {Dayjs} from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import EventMail from "./EventMail"; -import utc from 'dayjs/plugin/utc'; -import timezone from 'dayjs/plugin/timezone'; -import localizedFormat from 'dayjs/plugin/localizedFormat'; +import utc from "dayjs/plugin/utc"; +import timezone from "dayjs/plugin/timezone"; +import localizedFormat from "dayjs/plugin/localizedFormat"; dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(localizedFormat); @@ -15,20 +15,60 @@ export default class EventAttendeeMail extends EventMail { * @protected */ protected getHtmlRepresentation(): string { - return ` + return ( + `
Hi ${this.calEvent.attendees[0].name},

- Your ${this.calEvent.type} with ${this.calEvent.organizer.name} at ${this.getInviteeStart().format('h:mma')} - (${this.calEvent.attendees[0].timeZone}) on ${this.getInviteeStart().format('dddd, LL')} is scheduled.
-
` + this.getAdditionalBody() + ( - this.calEvent.location ? `Location: ${this.calEvent.location}

` : '' - ) + + Your ${this.calEvent.type} with ${this.calEvent.organizer.name} at ${this.getInviteeStart().format( + "h:mma" + )} + (${this.calEvent.attendees[0].timeZone}) on ${this.getInviteeStart().format( + "dddd, LL" + )} is scheduled.
+
` + + this.getAdditionalBody() + + "
" + `Additional notes:
${this.calEvent.description}
- ` + this.getAdditionalFooter() + ` + ` + + this.getAdditionalFooter() + + `
- `; + ` + ); + } + + /** + * Adds the video call information to the mail body. + * + * @protected + */ + protected getLocation(): string { + if (this.additionInformation?.hangoutLink) { + return `Location: ${this.additionInformation?.hangoutLink}
`; + } + + if (this.additionInformation?.entryPoints && this.additionInformation?.entryPoints.length > 0) { + const locations = this.additionInformation?.entryPoints + .map((entryPoint) => { + return ` + Join by ${entryPoint.entryPointType}:
+ ${entryPoint.label}
+ `; + }) + .join("
"); + + return `Locations:
${locations}`; + } + + return this.calEvent.location ? `Location: ${this.calEvent.location}

` : ""; + } + + protected getAdditionalBody(): string { + return ` + ${this.getLocation()} + `; } /** @@ -36,12 +76,14 @@ export default class EventAttendeeMail extends EventMail { * * @protected */ - protected getNodeMailerPayload(): Object { + protected getNodeMailerPayload() { return { to: `${this.calEvent.attendees[0].name} <${this.calEvent.attendees[0].email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, - subject: `Confirmed: ${this.calEvent.type} with ${this.calEvent.organizer.name} on ${this.getInviteeStart().format('dddd, LL')}`, + subject: `Confirmed: ${this.calEvent.type} with ${ + this.calEvent.organizer.name + } on ${this.getInviteeStart().format("dddd, LL")}`, html: this.getHtmlRepresentation(), text: this.getPlainTextRepresentation(), }; @@ -59,4 +101,4 @@ export default class EventAttendeeMail extends EventMail { protected getInviteeStart(): Dayjs { return dayjs(this.calEvent.startTime).tz(this.calEvent.attendees[0].timeZone); } -} \ No newline at end of file +} diff --git a/lib/emails/EventMail.ts b/lib/emails/EventMail.ts index de1c0507..f7194d12 100644 --- a/lib/emails/EventMail.ts +++ b/lib/emails/EventMail.ts @@ -1,10 +1,28 @@ -import {CalendarEvent} from "../calendarClient"; -import {serverConfig} from "../serverConfig"; -import nodemailer from 'nodemailer'; +import { CalendarEvent, ConferenceData } from "../calendarClient"; +import { serverConfig } from "../serverConfig"; +import nodemailer from "nodemailer"; + +interface EntryPoint { + entryPointType?: string; + uri?: string; + label?: string; + pin?: string; + accessCode?: string; + meetingCode?: string; + passcode?: string; + password?: string; +} + +interface AdditionInformation { + conferenceData?: ConferenceData; + entryPoints?: EntryPoint[]; + hangoutLink?: string; +} export default abstract class EventMail { calEvent: CalendarEvent; uid: string; + additionInformation?: AdditionInformation; /** * An EventMail always consists of a CalendarEvent @@ -14,9 +32,10 @@ export default abstract class EventMail { * @param calEvent * @param uid */ - constructor(calEvent: CalendarEvent, uid: string) { + constructor(calEvent: CalendarEvent, uid: string, additionInformation: AdditionInformation = null) { this.calEvent = calEvent; this.uid = uid; + this.additionInformation = additionInformation; } /** @@ -43,31 +62,30 @@ export default abstract class EventMail { * @protected */ protected stripHtml(html: string): string { - return html - .replace('
', "\n") - .replace(/<[^>]+>/g, ''); + return html.replace("
", "\n").replace(/<[^>]+>/g, ""); } /** * Returns the payload object for the nodemailer. * @protected */ - protected abstract getNodeMailerPayload(): Object; + protected abstract getNodeMailerPayload(); /** * Sends the email to the event attendant and returns a Promise. */ public sendEmail(): Promise { - new Promise((resolve, reject) => nodemailer.createTransport(this.getMailerOptions().transport).sendMail( - this.getNodeMailerPayload(), - (error, info) => { - if (error) { - this.printNodeMailerError(error); - reject(new Error(error)); - } else { - resolve(info); - } - }) + new Promise((resolve, reject) => + nodemailer + .createTransport(this.getMailerOptions().transport) + .sendMail(this.getNodeMailerPayload(), (error, info) => { + if (error) { + this.printNodeMailerError(error); + reject(new Error(error)); + } else { + resolve(info); + } + }) ).catch((e) => console.error("sendEmail", e)); return new Promise((resolve) => resolve("send mail async")); } @@ -95,6 +113,8 @@ export default abstract class EventMail { return ""; } + protected abstract getLocation(): string; + /** * Prints out the desired information when an error * occured while sending the mail. @@ -109,7 +129,7 @@ export default abstract class EventMail { * @protected */ protected getRescheduleLink(): string { - return process.env.BASE_URL + '/reschedule/' + this.uid; + return process.env.BASE_URL + "/reschedule/" + this.uid; } /** @@ -118,10 +138,9 @@ export default abstract class EventMail { * @protected */ protected getCancelLink(): string { - return process.env.BASE_URL + '/cancel/' + this.uid; + return process.env.BASE_URL + "/cancel/" + this.uid; } - /** * Defines a footer that will be appended to the email. * @protected diff --git a/lib/emails/EventOrganizerMail.ts b/lib/emails/EventOrganizerMail.ts index 6d3060b6..cc429ed1 100644 --- a/lib/emails/EventOrganizerMail.ts +++ b/lib/emails/EventOrganizerMail.ts @@ -1,11 +1,11 @@ -import {createEvent} from "ics"; -import dayjs, {Dayjs} from "dayjs"; +import { createEvent } from "ics"; +import dayjs, { Dayjs } from "dayjs"; import EventMail from "./EventMail"; -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 utc from "dayjs/plugin/utc"; +import timezone from "dayjs/plugin/timezone"; +import toArray from "dayjs/plugin/toArray"; +import localizedFormat from "dayjs/plugin/localizedFormat"; dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(toArray); @@ -18,14 +18,24 @@ export default class EventOrganizerMail extends EventMail { */ protected getiCalEventAsString(): string { const icsEvent = createEvent({ - start: dayjs(this.calEvent.startTime).utc().toArray().slice(0, 6).map((v, i) => i === 1 ? v + 1 : v), - startInputType: 'utc', - productId: 'calendso/ics', + start: dayjs(this.calEvent.startTime) + .utc() + .toArray() + .slice(0, 6) + .map((v, i) => (i === 1 ? v + 1 : v)), + startInputType: "utc", + productId: "calendso/ics", title: `${this.calEvent.type} with ${this.calEvent.attendees[0].name}`, - description: this.calEvent.description + this.stripHtml(this.getAdditionalBody()) + this.stripHtml(this.getAdditionalFooter()), - duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), 'minute') }, + description: + this.calEvent.description + + this.stripHtml(this.getAdditionalBody()) + + this.stripHtml(this.getAdditionalFooter()), + duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") }, organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email }, - attendees: this.calEvent.attendees.map( (attendee: any) => ({ name: attendee.name, email: attendee.email }) ), + attendees: this.calEvent.attendees.map((attendee: any) => ({ + name: attendee.name, + email: attendee.email, + })), status: "CONFIRMED", }); if (icsEvent.error) { @@ -40,7 +50,8 @@ export default class EventOrganizerMail extends EventMail { * @protected */ protected getHtmlRepresentation(): string { - return ` + return ( + `
Hi ${this.calEvent.organizer.name},

@@ -51,40 +62,71 @@ export default class EventOrganizerMail extends EventMail {
Invitee Email:
${this.calEvent.attendees[0].email}
-
` + this.getAdditionalBody() + - ( - this.calEvent.location ? ` - Location:
- ${this.calEvent.location}
-
- ` : '' - ) + +
` + + this.getAdditionalBody() + + "
" + `Invitee Time Zone:
- ${this.calEvent.attendees[0].timeZone}
-
- Additional notes:
- ${this.calEvent.description} - ` + this.getAdditionalFooter() + ` + ${this.calEvent.attendees[0].timeZone}
+
+ Additional notes:
+ ${this.calEvent.description} + ` + + this.getAdditionalFooter() + + `
- `; + ` + ); } + /** + * Adds the video call information to the mail body. + * + * @protected + */ + protected getLocation(): string { + if (this.additionInformation?.hangoutLink) { + return `Location: ${this.additionInformation?.hangoutLink}
`; + } + + if (this.additionInformation?.entryPoints && this.additionInformation?.entryPoints.length > 0) { + const locations = this.additionInformation?.entryPoints + .map((entryPoint) => { + return ` + Join by ${entryPoint.entryPointType}:
+ ${entryPoint.label}
+ `; + }) + .join("
"); + + return `Locations:
${locations}`; + } + + return this.calEvent.location ? `Location: ${this.calEvent.location}

` : ""; + } + + protected getAdditionalBody(): string { + return ` + ${this.getLocation()} + `; + } /** * Returns the payload object for the nodemailer. * * @protected */ - protected getNodeMailerPayload(): Object { + protected getNodeMailerPayload() { const organizerStart: Dayjs = dayjs(this.calEvent.startTime).tz(this.calEvent.organizer.timeZone); return { icalEvent: { - filename: 'event.ics', + filename: "event.ics", content: this.getiCalEventAsString(), }, from: `Calendso <${this.getMailerOptions().from}>`, to: this.calEvent.organizer.email, - subject: `New event: ${this.calEvent.attendees[0].name} - ${organizerStart.format('LT dddd, LL')} - ${this.calEvent.type}`, + subject: `New event: ${this.calEvent.attendees[0].name} - ${organizerStart.format("LT dddd, LL")} - ${ + this.calEvent.type + }`, html: this.getHtmlRepresentation(), text: this.getPlainTextRepresentation(), }; @@ -93,4 +135,4 @@ export default class EventOrganizerMail extends EventMail { protected printNodeMailerError(error: string): void { console.error("SEND_NEW_EVENT_NOTIFICATION_ERROR", this.calEvent.organizer.email, error); } -} \ No newline at end of file +}