Introduced CalEventParser to acquire rich descriptions for events in integrations
This commit is contained in:
		
							parent
							
								
									3aa1e1716d
								
							
						
					
					
						commit
						098b95ef55
					
				
					 8 changed files with 268 additions and 115 deletions
				
			
		
							
								
								
									
										108
									
								
								lib/CalEventParser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								lib/CalEventParser.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| import { CalendarEvent } from "./calendarClient"; | ||||
| import { v5 as uuidv5 } from "uuid"; | ||||
| import short from "short-uuid"; | ||||
| import { stripHtml } from "./emails/helpers"; | ||||
| 
 | ||||
| const translator = short(); | ||||
| 
 | ||||
| export default class CalEventParser { | ||||
|   calEvent: CalendarEvent; | ||||
| 
 | ||||
|   constructor(calEvent: CalendarEvent) { | ||||
|     this.calEvent = calEvent; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a link to reschedule the given booking. | ||||
|    */ | ||||
|   public getRescheduleLink(): string { | ||||
|     return process.env.BASE_URL + "/reschedule/" + this.getUid(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a link to cancel the given booking. | ||||
|    */ | ||||
|   public getCancelLink(): string { | ||||
|     return process.env.BASE_URL + "/cancel/" + this.getUid(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a unique identifier for the given calendar event. | ||||
|    */ | ||||
|   public getUid(): string { | ||||
|     return translator.fromUUID(uuidv5(JSON.stringify(this.calEvent), uuidv5.URL)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a footer section with links to change the event (as HTML). | ||||
|    */ | ||||
|   public getChangeEventFooterHtml(): string { | ||||
|     return ` | ||||
|       <br/> | ||||
|       <br/> | ||||
|       <strong>Need to change this event?</strong><br /> | ||||
|       Cancel: <a href="${this.getCancelLink()}">${this.getCancelLink()}</a><br /> | ||||
|       Reschedule: <a href="${this.getRescheduleLink()}">${this.getRescheduleLink()}</a> | ||||
|     `;
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a footer section with links to change the event (as plain text). | ||||
|    */ | ||||
|   public getChangeEventFooter(): string { | ||||
|     return stripHtml(this.getChangeEventFooterHtml()); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns an extended description with all important information (as HTML). | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   public getRichDescriptionHtml(): string { | ||||
|     return ( | ||||
|       ` | ||||
|       <div> | ||||
|         <strong>Event Type:</strong><br /> | ||||
|         ${this.calEvent.type}<br /> | ||||
|         <br /> | ||||
|         <strong>Invitee Email:</strong><br /> | ||||
|         <a href="mailto:${this.calEvent.attendees[0].email}">${this.calEvent.attendees[0].email}</a><br /> | ||||
|         <br />` +
 | ||||
|       (this.calEvent.location | ||||
|         ? ` | ||||
|             <strong>Location:</strong><br /> | ||||
|             ${this.calEvent.location}<br /> | ||||
|             <br /> | ||||
|           ` | ||||
|         : "") + | ||||
|       `<strong>Invitee Time Zone:</strong><br />
 | ||||
|         ${this.calEvent.attendees[0].timeZone}<br /> | ||||
|         <br /> | ||||
|         <strong>Additional notes:</strong><br /> | ||||
|         ${this.calEvent.description} | ||||
|       ` +
 | ||||
|       this.getChangeEventFooterHtml() + | ||||
|       `   
 | ||||
|       </div> | ||||
|     ` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns an extended description with all important information (as plain text). | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   public getRichDescription(): string { | ||||
|     return stripHtml(this.getRichDescriptionHtml()); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a calendar event with rich description. | ||||
|    */ | ||||
|   public asRichEvent(): CalendarEvent { | ||||
|     const eventCopy: CalendarEvent = { ...this.calEvent }; | ||||
|     eventCopy.description = this.getRichDescription(); | ||||
|     return eventCopy; | ||||
|   } | ||||
| } | ||||
|  | @ -1,15 +1,12 @@ | |||
| import EventOrganizerMail from "./emails/EventOrganizerMail"; | ||||
| import EventAttendeeMail from "./emails/EventAttendeeMail"; | ||||
| import { v5 as uuidv5 } from "uuid"; | ||||
| import short from "short-uuid"; | ||||
| import EventOrganizerRescheduledMail from "./emails/EventOrganizerRescheduledMail"; | ||||
| import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail"; | ||||
| 
 | ||||
| const translator = short(); | ||||
| import prisma from "./prisma"; | ||||
| import CalEventParser from "./CalEventParser"; | ||||
| 
 | ||||
| // eslint-disable-next-line @typescript-eslint/no-var-requires
 | ||||
| const { google } = require("googleapis"); | ||||
| import prisma from "./prisma"; | ||||
| 
 | ||||
| const googleAuth = (credential) => { | ||||
|   const { client_secret, client_id, redirect_uris } = JSON.parse(process.env.GOOGLE_API_CREDENTIALS).web; | ||||
|  | @ -105,12 +102,14 @@ const o365Auth = (credential) => { | |||
|   }; | ||||
| }; | ||||
| 
 | ||||
| // eslint-disable-next-line
 | ||||
| interface Person { | ||||
|   name?: string; | ||||
|   email: string; | ||||
|   timeZone: string; | ||||
| } | ||||
| 
 | ||||
| // eslint-disable-next-line
 | ||||
| interface CalendarEvent { | ||||
|   type: string; | ||||
|   title: string; | ||||
|  | @ -123,10 +122,12 @@ interface CalendarEvent { | |||
|   conferenceData?: ConferenceData; | ||||
| } | ||||
| 
 | ||||
| // eslint-disable-next-line
 | ||||
| interface ConferenceData { | ||||
|   createRequest: any; | ||||
| } | ||||
| 
 | ||||
| // eslint-disable-next-line
 | ||||
| interface IntegrationCalendar { | ||||
|   integration: string; | ||||
|   primary: boolean; | ||||
|  | @ -134,6 +135,7 @@ interface IntegrationCalendar { | |||
|   name: string; | ||||
| } | ||||
| 
 | ||||
| // eslint-disable-next-line
 | ||||
| interface CalendarApiAdapter { | ||||
|   createEvent(event: CalendarEvent): Promise<any>; | ||||
| 
 | ||||
|  | @ -507,9 +509,11 @@ const listCalendars = (withCredentials) => | |||
|   ); | ||||
| 
 | ||||
| const createEvent = async (credential, calEvent: CalendarEvent): Promise<any> => { | ||||
|   const uid: string = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL)); | ||||
|   const parser: CalEventParser = new CalEventParser(calEvent); | ||||
|   const uid: string = parser.getUid(); | ||||
|   const richEvent: CalendarEvent = parser.asRichEvent(); | ||||
| 
 | ||||
|   const creationResult = credential ? await calendars([credential])[0].createEvent(calEvent) : null; | ||||
|   const creationResult = credential ? await calendars([credential])[0].createEvent(richEvent) : null; | ||||
| 
 | ||||
|   const organizerMail = new EventOrganizerMail(calEvent, uid); | ||||
|   const attendeeMail = new EventAttendeeMail(calEvent, uid); | ||||
|  | @ -534,10 +538,12 @@ const createEvent = async (credential, calEvent: CalendarEvent): Promise<any> => | |||
| }; | ||||
| 
 | ||||
| const updateEvent = async (credential, uidToUpdate: string, calEvent: CalendarEvent): Promise<any> => { | ||||
|   const newUid: string = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL)); | ||||
|   const parser: CalEventParser = new CalEventParser(calEvent); | ||||
|   const newUid: string = parser.getUid(); | ||||
|   const richEvent: CalendarEvent = parser.asRichEvent(); | ||||
| 
 | ||||
|   const updateResult = credential | ||||
|     ? await calendars([credential])[0].updateEvent(uidToUpdate, calEvent) | ||||
|     ? await calendars([credential])[0].updateEvent(uidToUpdate, richEvent) | ||||
|     : null; | ||||
| 
 | ||||
|   const organizerMail = new EventOrganizerRescheduledMail(calEvent, newUid); | ||||
|  |  | |||
|  | @ -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,28 @@ export default class EventAttendeeMail extends EventMail { | |||
|    * @protected | ||||
|    */ | ||||
|   protected getHtmlRepresentation(): string { | ||||
|     return ` | ||||
|     return ( | ||||
|       ` | ||||
|     <div> | ||||
|       Hi ${this.calEvent.attendees[0].name},<br /> | ||||
|       <br /> | ||||
|       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.<br /> | ||||
|       <br />` + this.getAdditionalBody() + (
 | ||||
|         this.calEvent.location ? `<strong>Location:</strong> ${this.calEvent.location}<br /><br />` : '' | ||||
|       ) + | ||||
|       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.<br /> | ||||
|       <br />` +
 | ||||
|       this.getAdditionalBody() + | ||||
|       (this.calEvent.location ? `<strong>Location:</strong> ${this.calEvent.location}<br /><br />` : "") + | ||||
|       `<strong>Additional notes:</strong><br />
 | ||||
|       ${this.calEvent.description}<br /> | ||||
|       ` + this.getAdditionalFooter() + ` | ||||
|       ` +
 | ||||
|       this.getAdditionalFooter() + | ||||
|       ` | ||||
|     </div> | ||||
|   `;
 | ||||
|   ` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -36,12 +44,14 @@ export default class EventAttendeeMail extends EventMail { | |||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getNodeMailerPayload(): Object { | ||||
|   protected getNodeMailerPayload(): Record<string, unknown> { | ||||
|     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 +69,4 @@ export default class EventAttendeeMail extends EventMail { | |||
|   protected getInviteeStart(): Dayjs { | ||||
|     return <Dayjs>dayjs(this.calEvent.startTime).tz(this.calEvent.attendees[0].timeZone); | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -7,15 +7,21 @@ export default class EventAttendeeRescheduledMail extends EventAttendeeMail { | |||
|    * @protected | ||||
|    */ | ||||
|   protected getHtmlRepresentation(): string { | ||||
|     return ` | ||||
|     return ( | ||||
|       ` | ||||
|     <div> | ||||
|       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.attendees[0].timeZone}) on ${this.getInviteeStart().format('dddd, LL')}.<br /> | ||||
|       ` + this.getAdditionalFooter() + ` | ||||
|       Your ${this.calEvent.type} with ${ | ||||
|         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() + | ||||
|       ` | ||||
|     </div> | ||||
|   `;
 | ||||
|   ` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -23,12 +29,14 @@ export default class EventAttendeeRescheduledMail extends EventAttendeeMail { | |||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getNodeMailerPayload(): Object { | ||||
|   protected getNodeMailerPayload(): Record<string, unknown> { | ||||
|     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: `Rescheduled: ${this.calEvent.type} with ${this.calEvent.organizer.name} on ${this.getInviteeStart().format('dddd, LL')}`, | ||||
|       subject: `Rescheduled: ${this.calEvent.type} with ${ | ||||
|         this.calEvent.organizer.name | ||||
|       } on ${this.getInviteeStart().format("dddd, LL")}`,
 | ||||
|       html: this.getHtmlRepresentation(), | ||||
|       text: this.getPlainTextRepresentation(), | ||||
|     }; | ||||
|  | @ -37,4 +45,4 @@ export default class EventAttendeeRescheduledMail extends EventAttendeeMail { | |||
|   protected printNodeMailerError(error: string): void { | ||||
|     console.error("SEND_RESCHEDULE_CONFIRMATION_ERROR", this.calEvent.attendees[0].email, error); | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +1,12 @@ | |||
| import {CalendarEvent} from "../calendarClient"; | ||||
| import {serverConfig} from "../serverConfig"; | ||||
| import nodemailer from 'nodemailer'; | ||||
| import { CalendarEvent } from "../calendarClient"; | ||||
| import { serverConfig } from "../serverConfig"; | ||||
| import nodemailer from "nodemailer"; | ||||
| import CalEventParser from "../CalEventParser"; | ||||
| import { stripHtml } from "./helpers"; | ||||
| 
 | ||||
| export default abstract class EventMail { | ||||
|   calEvent: CalendarEvent; | ||||
|   parser: CalEventParser; | ||||
|   uid: string; | ||||
| 
 | ||||
|   /** | ||||
|  | @ -17,6 +20,7 @@ export default abstract class EventMail { | |||
|   constructor(calEvent: CalendarEvent, uid: string) { | ||||
|     this.calEvent = calEvent; | ||||
|     this.uid = uid; | ||||
|     this.parser = new CalEventParser(calEvent); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -33,41 +37,30 @@ export default abstract class EventMail { | |||
|    * @protected | ||||
|    */ | ||||
|   protected getPlainTextRepresentation(): string { | ||||
|     return this.stripHtml(this.getHtmlRepresentation()); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Strips off all HTML tags and leaves plain text. | ||||
|    * | ||||
|    * @param html | ||||
|    * @protected | ||||
|    */ | ||||
|   protected stripHtml(html: string): string { | ||||
|     return html | ||||
|       .replace('<br />', "\n") | ||||
|       .replace(/<[^>]+>/g, ''); | ||||
|     return stripHtml(this.getHtmlRepresentation()); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the payload object for the nodemailer. | ||||
|    * @protected | ||||
|    */ | ||||
|   protected abstract getNodeMailerPayload(): Object; | ||||
|   protected abstract getNodeMailerPayload(): Record<string, unknown>; | ||||
| 
 | ||||
|   /** | ||||
|    * Sends the email to the event attendant and returns a Promise. | ||||
|    */ | ||||
|   public sendEmail(): Promise<any> { | ||||
|     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")); | ||||
|   } | ||||
|  | @ -109,7 +102,7 @@ export default abstract class EventMail { | |||
|    * @protected | ||||
|    */ | ||||
|   protected getRescheduleLink(): string { | ||||
|     return process.env.BASE_URL + '/reschedule/' + this.uid; | ||||
|     return this.parser.getRescheduleLink(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -118,21 +111,14 @@ export default abstract class EventMail { | |||
|    * @protected | ||||
|    */ | ||||
|   protected getCancelLink(): string { | ||||
|     return process.env.BASE_URL + '/cancel/' + this.uid; | ||||
|     return this.parser.getCancelLink(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Defines a footer that will be appended to the email. | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getAdditionalFooter(): string { | ||||
|     return ` | ||||
|       <br/> | ||||
|       <br/> | ||||
|       <strong>Need to change this event?</strong><br /> | ||||
|       Cancel: <a href="${this.getCancelLink()}">${this.getCancelLink()}</a><br /> | ||||
|       Reschedule: <a href="${this.getRescheduleLink()}">${this.getRescheduleLink()}</a> | ||||
|     `;
 | ||||
|     return this.parser.getChangeEventFooterHtml(); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,11 +1,13 @@ | |||
| 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"; | ||||
| import { stripHtml } from "./helpers"; | ||||
| 
 | ||||
| dayjs.extend(utc); | ||||
| dayjs.extend(timezone); | ||||
| dayjs.extend(toArray); | ||||
|  | @ -18,14 +20,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 + | ||||
|         stripHtml(this.getAdditionalBody()) + | ||||
|         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 +52,8 @@ export default class EventOrganizerMail extends EventMail { | |||
|    * @protected | ||||
|    */ | ||||
|   protected getHtmlRepresentation(): string { | ||||
|     return ` | ||||
|     return ( | ||||
|       ` | ||||
|       <div> | ||||
|         Hi ${this.calEvent.organizer.name},<br /> | ||||
|         <br /> | ||||
|  | @ -51,22 +64,26 @@ export default class EventOrganizerMail extends EventMail { | |||
|         <br /> | ||||
|         <strong>Invitee Email:</strong><br /> | ||||
|         <a href="mailto:${this.calEvent.attendees[0].email}">${this.calEvent.attendees[0].email}</a><br /> | ||||
|         <br />` + this.getAdditionalBody() +
 | ||||
|       ( | ||||
|         this.calEvent.location ? ` | ||||
|         <br />` +
 | ||||
|       this.getAdditionalBody() + | ||||
|       (this.calEvent.location | ||||
|         ? ` | ||||
|             <strong>Location:</strong><br /> | ||||
|             ${this.calEvent.location}<br /> | ||||
|             <br /> | ||||
|           ` : ''
 | ||||
|       ) + | ||||
|           ` | ||||
|         : "") + | ||||
|       `<strong>Invitee Time Zone:</strong><br />
 | ||||
|         ${this.calEvent.attendees[0].timeZone}<br /> | ||||
|         <br /> | ||||
|         <strong>Additional notes:</strong><br /> | ||||
|         ${this.calEvent.description} | ||||
|       ` + this.getAdditionalFooter() + `    | ||||
|       ` +
 | ||||
|       this.getAdditionalFooter() + | ||||
|       `   
 | ||||
|       </div> | ||||
|     `;
 | ||||
|     ` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -74,17 +91,19 @@ export default class EventOrganizerMail extends EventMail { | |||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getNodeMailerPayload(): Object { | ||||
|   protected getNodeMailerPayload(): Record<string, unknown> { | ||||
|     const organizerStart: Dayjs = <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 +112,4 @@ export default class EventOrganizerMail extends EventMail { | |||
|   protected printNodeMailerError(error: string): void { | ||||
|     console.error("SEND_NEW_EVENT_NOTIFICATION_ERROR", this.calEvent.organizer.email, error); | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import dayjs, {Dayjs} from "dayjs"; | ||||
| import dayjs, { Dayjs } from "dayjs"; | ||||
| import EventOrganizerMail from "./EventOrganizerMail"; | ||||
| 
 | ||||
| export default class EventOrganizerRescheduledMail extends EventOrganizerMail { | ||||
|  | @ -8,7 +8,8 @@ export default class EventOrganizerRescheduledMail extends EventOrganizerMail { | |||
|    * @protected | ||||
|    */ | ||||
|   protected getHtmlRepresentation(): string { | ||||
|     return ` | ||||
|     return ( | ||||
|       ` | ||||
|       <div> | ||||
|         Hi ${this.calEvent.organizer.name},<br /> | ||||
|         <br /> | ||||
|  | @ -19,22 +20,26 @@ export default class EventOrganizerRescheduledMail extends EventOrganizerMail { | |||
|         <br /> | ||||
|         <strong>Invitee Email:</strong><br /> | ||||
|         <a href="mailto:${this.calEvent.attendees[0].email}">${this.calEvent.attendees[0].email}</a><br /> | ||||
|         <br />` + this.getAdditionalBody() +
 | ||||
|       ( | ||||
|         this.calEvent.location ? ` | ||||
|         <br />` +
 | ||||
|       this.getAdditionalBody() + | ||||
|       (this.calEvent.location | ||||
|         ? ` | ||||
|             <strong>Location:</strong><br /> | ||||
|             ${this.calEvent.location}<br /> | ||||
|             <br /> | ||||
|           ` : ''
 | ||||
|       ) + | ||||
|           ` | ||||
|         : "") + | ||||
|       `<strong>Invitee Time Zone:</strong><br />
 | ||||
|         ${this.calEvent.attendees[0].timeZone}<br /> | ||||
|         <br /> | ||||
|         <strong>Additional notes:</strong><br /> | ||||
|         ${this.calEvent.description} | ||||
|       ` + this.getAdditionalFooter() + `    | ||||
|       ` +
 | ||||
|       this.getAdditionalFooter() + | ||||
|       `   
 | ||||
|       </div> | ||||
|     `;
 | ||||
|     ` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -42,17 +47,19 @@ export default class EventOrganizerRescheduledMail extends EventOrganizerMail { | |||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getNodeMailerPayload(): Object { | ||||
|   protected getNodeMailerPayload(): Record<string, unknown> { | ||||
|     const organizerStart: Dayjs = <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: `Rescheduled event: ${this.calEvent.attendees[0].name} - ${organizerStart.format('LT dddd, LL')} - ${this.calEvent.type}`, | ||||
|       subject: `Rescheduled event: ${this.calEvent.attendees[0].name} - ${organizerStart.format( | ||||
|         "LT dddd, LL" | ||||
|       )} - ${this.calEvent.type}`,
 | ||||
|       html: this.getHtmlRepresentation(), | ||||
|       text: this.getPlainTextRepresentation(), | ||||
|     }; | ||||
|  | @ -61,4 +68,4 @@ export default class EventOrganizerRescheduledMail extends EventOrganizerMail { | |||
|   protected printNodeMailerError(error: string): void { | ||||
|     console.error("SEND_RESCHEDULE_EVENT_NOTIFICATION_ERROR", this.calEvent.organizer.email, error); | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import {VideoCallData} from "../videoClient"; | ||||
| import { VideoCallData } from "../videoClient"; | ||||
| 
 | ||||
| export function getIntegrationName(videoCallData: VideoCallData): string { | ||||
|   //TODO: When there are more complex integration type strings, we should consider using an extra field in the DB for that.
 | ||||
|  | @ -6,15 +6,24 @@ export function getIntegrationName(videoCallData: VideoCallData): string { | |||
|   return nameProto.charAt(0).toUpperCase() + nameProto.slice(1); | ||||
| } | ||||
| 
 | ||||
| function extractZoom(videoCallData: VideoCallData): string { | ||||
|   const strId = videoCallData.id.toString(); | ||||
|   const part1 = strId.slice(0, 3); | ||||
|   const part2 = strId.slice(3, 7); | ||||
|   const part3 = strId.slice(7, 11); | ||||
| 
 | ||||
|   return part1 + " " + part2 + " " + part3; | ||||
| } | ||||
| 
 | ||||
| export function getFormattedMeetingId(videoCallData: VideoCallData): string { | ||||
|   switch(videoCallData.type) { | ||||
|     case 'zoom_video': | ||||
|       const strId = videoCallData.id.toString(); | ||||
|       const part1 = strId.slice(0, 3); | ||||
|       const part2 = strId.slice(3, 7); | ||||
|       const part3 = strId.slice(7, 11); | ||||
|       return part1 + " " + part2 + " " + part3; | ||||
|   switch (videoCallData.type) { | ||||
|     case "zoom_video": | ||||
|       return extractZoom(videoCallData); | ||||
|     default: | ||||
|       return videoCallData.id.toString(); | ||||
|   } | ||||
| } | ||||
| } | ||||
| 
 | ||||
| export function stripHtml(html: string): string { | ||||
|   return html.replace("<br />", "\n").replace(/<[^>]+>/g, ""); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 nicolas
						nicolas