Introduced more classes for event mails
This commit is contained in:
		
							parent
							
								
									e37dd017c8
								
							
						
					
					
						commit
						04e0b55b51
					
				
					 9 changed files with 262 additions and 200 deletions
				
			
		|  | @ -324,8 +324,8 @@ const getBusyTimes = (withCredentials, dateFrom, dateTo) => Promise.all( | |||
|     (results) => results.reduce((acc, availability) => acc.concat(availability), []) | ||||
| ); | ||||
| 
 | ||||
| const createEvent = async (credential, calEvent: CalendarEvent): Promise<any> => { | ||||
|     const mail = new EventOwnerMail(calEvent); | ||||
| const createEvent = async (credential, calEvent: CalendarEvent, hashUID: string): Promise<any> => { | ||||
|     const mail = new EventOwnerMail(calEvent, hashUID); | ||||
|     const sentMail = await mail.sendEmail(); | ||||
| 
 | ||||
|     const creationResult = credential ? await calendars([credential])[0].createEvent(calEvent) : null; | ||||
|  |  | |||
							
								
								
									
										55
									
								
								lib/emails/EventAttendeeMail.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								lib/emails/EventAttendeeMail.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| import dayjs, {Dayjs} from "dayjs"; | ||||
| import EventMail from "./EventMail"; | ||||
| 
 | ||||
| export default class EventAttendeeMail extends EventMail { | ||||
|   /** | ||||
|    * Returns the email text as HTML representation. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getHtmlRepresentation(): string { | ||||
|     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 />` : '' | ||||
|       ) + | ||||
|       `<strong>Additional notes:</strong><br />
 | ||||
|       ${this.calEvent.description} | ||||
|       ` + this.getAdditionalFooter() + ` | ||||
|     </div> | ||||
|   `;
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the payload object for the nodemailer. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getNodeMailerPayload(): Object { | ||||
|     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')}`, | ||||
|       html: this.getHtmlRepresentation(), | ||||
|       text: this.getPlainTextRepresentation(), | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   protected printNodeMailerError(error: string): void { | ||||
|     console.error("SEND_BOOKING_CONFIRMATION_ERROR", this.calEvent.attendees[0].email, error); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the inviteeStart value used at multiple points. | ||||
|    * | ||||
|    * @private | ||||
|    */ | ||||
|   private getInviteeStart(): Dayjs { | ||||
|     return <Dayjs>dayjs(this.calEvent.startTime).tz(this.calEvent.attendees[0].timeZone); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										135
									
								
								lib/emails/EventMail.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								lib/emails/EventMail.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,135 @@ | |||
| import {CalendarEvent} from "../calendarClient"; | ||||
| import {serverConfig} from "../serverConfig"; | ||||
| import nodemailer from 'nodemailer'; | ||||
| 
 | ||||
| export default abstract class EventMail { | ||||
|   calEvent: CalendarEvent; | ||||
|   uid: string; | ||||
| 
 | ||||
|   /** | ||||
|    * An EventMail always consists of a CalendarEvent | ||||
|    * that stores the very basic data of the event (like date, title etc). | ||||
|    * It also needs the UID of the stored booking in our database. | ||||
|    * | ||||
|    * @param calEvent | ||||
|    * @param uid | ||||
|    */ | ||||
|   constructor(calEvent: CalendarEvent, uid: string) { | ||||
|     this.calEvent = calEvent; | ||||
|     this.uid = uid; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the email text as HTML representation. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected abstract getHtmlRepresentation(): string; | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the email text in a plain text representation | ||||
|    * by stripping off the HTML tags. | ||||
|    * | ||||
|    * @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, ''); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the payload object for the nodemailer. | ||||
|    * @protected | ||||
|    */ | ||||
|   protected abstract getNodeMailerPayload(): Object; | ||||
| 
 | ||||
|   /** | ||||
|    * Sends the email to the event attendant and returns a Promise. | ||||
|    */ | ||||
|   public sendEmail(): Promise<any> { | ||||
|     return 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); | ||||
|         } | ||||
|       })); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gathers the required provider information from the config. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getMailerOptions(): any { | ||||
|     return { | ||||
|       transport: serverConfig.transport, | ||||
|       from: serverConfig.from, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Can be used to include additional HTML or plain text | ||||
|    * content into the mail body. Leave it to an empty | ||||
|    * string if not desired. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getAdditionalBody(): string { | ||||
|     return ""; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Prints out the desired information when an error | ||||
|    * occured while sending the mail. | ||||
|    * @param error | ||||
|    * @protected | ||||
|    */ | ||||
|   protected abstract printNodeMailerError(error: string): void; | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a link to reschedule the given booking. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getRescheduleLink(): string { | ||||
|     return process.env.BASE_URL + '/reschedule/' + this.uid; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a link to cancel the given booking. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getCancelLink(): string { | ||||
|     return process.env.BASE_URL + '/cancel/' + this.uid; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Defines a footer that will be appended to the email. | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getAdditionalFooter(): string { | ||||
|     return ` | ||||
|       <br/> | ||||
|       Need to change this event?<br /> | ||||
|       Cancel: <a href="${this.getCancelLink()}">${this.getCancelLink()}</a><br /> | ||||
|       Reschedule: <a href="${this.getRescheduleLink()}">${this.getRescheduleLink()}</a> | ||||
|     `;
 | ||||
|   } | ||||
| } | ||||
|  | @ -1,22 +1,8 @@ | |||
| import {CalendarEvent} from "../calendarClient"; | ||||
| import {createEvent} from "ics"; | ||||
| import dayjs, {Dayjs} from "dayjs"; | ||||
| import {serverConfig} from "../serverConfig"; | ||||
| import nodemailer from 'nodemailer'; | ||||
| 
 | ||||
| export default class EventOwnerMail { | ||||
|   calEvent: CalendarEvent; | ||||
| 
 | ||||
|   /** | ||||
|    * An EventOwnerMail always consists of a CalendarEvent | ||||
|    * that stores the very basic data of the event (like date, title etc). | ||||
|    * | ||||
|    * @param calEvent | ||||
|    */ | ||||
|   constructor(calEvent: CalendarEvent) { | ||||
|     this.calEvent = calEvent; | ||||
|   } | ||||
| import EventMail from "./EventMail"; | ||||
| 
 | ||||
| export default class EventOwnerMail extends EventMail { | ||||
|   /** | ||||
|    * Returns the instance's event as an iCal event in string representation. | ||||
|    * @protected | ||||
|  | @ -27,7 +13,7 @@ export default class EventOwnerMail { | |||
|       startInputType: 'utc', | ||||
|       productId: 'calendso/ics', | ||||
|       title: `${this.calEvent.type} with ${this.calEvent.attendees[0].name}`, | ||||
|       description: this.calEvent.description + this.stripHtml(this.getAdditionalBody()), | ||||
|       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})), | ||||
|  | @ -69,82 +55,33 @@ export default class EventOwnerMail { | |||
|         <br /> | ||||
|         <strong>Additional notes:</strong><br /> | ||||
|         ${this.calEvent.description} | ||||
|       ` + this.getAdditionalFooter() + `    | ||||
|       </div> | ||||
|     `;
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the email text in a plain text representation | ||||
|    * by stripping off the HTML tags. | ||||
|    * Returns the payload object for the nodemailer. | ||||
|    * | ||||
|    * @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, ''); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sends the email to the event attendant and returns a Promise. | ||||
|    */ | ||||
|   public sendEmail(): Promise<any> { | ||||
|     const options = this.getMailerOptions(); | ||||
|     const {transport, from} = options; | ||||
|   protected getNodeMailerPayload(): Object { | ||||
|     const organizerStart: Dayjs = <Dayjs>dayjs(this.calEvent.startTime).tz(this.calEvent.organizer.timeZone); | ||||
| 
 | ||||
|     return new Promise((resolve, reject) => nodemailer.createTransport(transport).sendMail( | ||||
|       { | ||||
|         icalEvent: { | ||||
|           filename: 'event.ics', | ||||
|           content: this.getiCalEventAsString(), | ||||
|         }, | ||||
|         from: `Calendso <${from}>`, | ||||
|         to: this.calEvent.organizer.email, | ||||
|         subject: `New event: ${this.calEvent.attendees[0].name} - ${organizerStart.format('LT dddd, LL')} - ${this.calEvent.type}`, | ||||
|         html: this.getHtmlRepresentation(), | ||||
|         text: this.getPlainTextRepresentation(), | ||||
|       }, | ||||
|       (error, info) => { | ||||
|         if (error) { | ||||
|           console.error("SEND_NEW_EVENT_NOTIFICATION_ERROR", this.calEvent.organizer.email, error); | ||||
|           reject(new Error(error)); | ||||
|         } else { | ||||
|           resolve(info); | ||||
|         } | ||||
|       })); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gathers the required provider information from the config. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getMailerOptions(): any { | ||||
|     return { | ||||
|       transport: serverConfig.transport, | ||||
|       from: serverConfig.from, | ||||
|       icalEvent: { | ||||
|         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}`, | ||||
|       html: this.getHtmlRepresentation(), | ||||
|       text: this.getPlainTextRepresentation(), | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Can be used to include additional HTML or plain text | ||||
|    * content into the mail body and calendar event description. | ||||
|    * Leave it to an empty string if not desired. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getAdditionalBody(): string { | ||||
|     return ""; | ||||
|   protected printNodeMailerError(error: string): void { | ||||
|     console.error("SEND_NEW_EVENT_NOTIFICATION_ERROR", this.calEvent.organizer.email, error); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										45
									
								
								lib/emails/VideoEventAttendeeMail.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/emails/VideoEventAttendeeMail.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| import {VideoCallData} from "./confirm-booked"; | ||||
| import {CalendarEvent} from "../calendarClient"; | ||||
| import EventAttendeeMail from "./EventAttendeeMail"; | ||||
| 
 | ||||
| export default class VideoEventAttendeeMail extends EventAttendeeMail { | ||||
|   videoCallData: VideoCallData; | ||||
| 
 | ||||
|   constructor(calEvent: CalendarEvent, uid: string, videoCallData: VideoCallData) { | ||||
|     super(calEvent, uid); | ||||
|     this.videoCallData = videoCallData; | ||||
|   } | ||||
| 
 | ||||
|   private getIntegrationName(): string { | ||||
|     //TODO: When there are more complex integration type strings, we should consider using an extra field in the DB for that.
 | ||||
|     const nameProto = this.videoCallData.type.split("_")[0]; | ||||
|     return nameProto.charAt(0).toUpperCase() + nameProto.slice(1); | ||||
|   } | ||||
| 
 | ||||
|   private getFormattedMeetingId(): string { | ||||
|     switch(this.videoCallData.type) { | ||||
|       case 'zoom_video': | ||||
|         const strId = this.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; | ||||
|       default: | ||||
|         return this.videoCallData.id.toString(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Adds the video call information to the mail body. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getAdditionalBody(): string { | ||||
|     return ` | ||||
|       <strong>Video call provider:</strong> ${this.getIntegrationName()}<br /> | ||||
|       <strong>Meeting ID:</strong> ${this.getFormattedMeetingId()}<br /> | ||||
|       <strong>Meeting Password:</strong> ${this.videoCallData.password}<br /> | ||||
|       <strong>Meeting URL:</strong> <a href="${this.videoCallData.url}">${this.videoCallData.url}</a><br /> | ||||
|     `;
 | ||||
|   } | ||||
| } | ||||
|  | @ -5,8 +5,8 @@ import {formattedId, integrationTypeToName, VideoCallData} from "./confirm-booke | |||
| export default class VideoEventOwnerMail extends EventOwnerMail { | ||||
|   videoCallData: VideoCallData; | ||||
| 
 | ||||
|   constructor(calEvent: CalendarEvent, videoCallData: VideoCallData) { | ||||
|     super(calEvent); | ||||
|   constructor(calEvent: CalendarEvent, uid: string, videoCallData: VideoCallData) { | ||||
|     super(calEvent, uid); | ||||
|     this.videoCallData = videoCallData; | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,99 +0,0 @@ | |||
| 
 | ||||
| import nodemailer from 'nodemailer'; | ||||
| import dayjs, { Dayjs } from "dayjs"; | ||||
| 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 { createEvent } from 'ics'; | ||||
| import { CalendarEvent } from '../calendarClient'; | ||||
| import { serverConfig } from '../serverConfig'; | ||||
| 
 | ||||
| dayjs.extend(localizedFormat); | ||||
| dayjs.extend(utc); | ||||
| dayjs.extend(timezone); | ||||
| dayjs.extend(toArray); | ||||
| 
 | ||||
| export default function createNewEventEmail(calEvent: CalendarEvent, options: any = {}) { | ||||
|   return sendEmail(calEvent, { | ||||
|     provider: { | ||||
|       transport: serverConfig.transport, | ||||
|       from: serverConfig.from, | ||||
|     }, | ||||
|     ...options | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| const icalEventAsString = (calEvent: CalendarEvent): string => { | ||||
|   const icsEvent = createEvent({ | ||||
|     start: dayjs(calEvent.startTime).utc().toArray().slice(0, 6), | ||||
|     startInputType: 'utc', | ||||
|     productId: 'calendso/ics', | ||||
|     title: `${calEvent.type} with ${calEvent.attendees[0].name}`, | ||||
|     description: calEvent.description, | ||||
|     duration: { minutes: dayjs(calEvent.endTime).diff(dayjs(calEvent.startTime), 'minute') }, | ||||
|     organizer: { name: calEvent.organizer.name, email: calEvent.organizer.email }, | ||||
|     attendees: calEvent.attendees.map( (attendee: any) => ({ name: attendee.name, email: attendee.email }) ), | ||||
|     status: "CONFIRMED", | ||||
|   }); | ||||
|   if (icsEvent.error) { | ||||
|     throw icsEvent.error; | ||||
|   } | ||||
|   return icsEvent.value; | ||||
| } | ||||
| 
 | ||||
| const sendEmail = (calEvent: CalendarEvent, { | ||||
|   provider, | ||||
| }) => new Promise( (resolve, reject) => { | ||||
|   const { transport, from } = provider; | ||||
|   const organizerStart: Dayjs = <Dayjs>dayjs(calEvent.startTime).tz(calEvent.organizer.timeZone); | ||||
|   nodemailer.createTransport(transport).sendMail( | ||||
|     { | ||||
|       icalEvent: { | ||||
|         filename: 'event.ics', | ||||
|         content: icalEventAsString(calEvent), | ||||
|       }, | ||||
|       from: `Calendso <${from}>`, | ||||
|       to: calEvent.organizer.email, | ||||
|       subject: `New event: ${calEvent.attendees[0].name} - ${organizerStart.format('LT dddd, LL')} - ${calEvent.type}`, | ||||
|       html: html(calEvent), | ||||
|       text: text(calEvent), | ||||
|     }, | ||||
|     (error) => { | ||||
|       if (error) { | ||||
|         console.error("SEND_NEW_EVENT_NOTIFICATION_ERROR", calEvent.organizer.email, error); | ||||
|         return reject(new Error(error)); | ||||
|       } | ||||
|       return resolve(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| const html = (evt: CalendarEvent) => ` | ||||
|   <div> | ||||
|     Hi ${evt.organizer.name},<br /> | ||||
|     <br /> | ||||
|     A new event has been scheduled.<br /> | ||||
|     <br /> | ||||
|     <strong>Event Type:</strong><br /> | ||||
|     ${evt.type}<br /> | ||||
|     <br /> | ||||
|     <strong>Invitee Email:</strong><br /> | ||||
|     <a href="mailto:${evt.attendees[0].email}">${evt.attendees[0].email}</a><br /> | ||||
|     <br />` +
 | ||||
|     ( | ||||
|       evt.location ? ` | ||||
|         <strong>Location:</strong><br /> | ||||
|         ${evt.location}<br /> | ||||
|         <br /> | ||||
|       ` : ''
 | ||||
|     ) + | ||||
|     `<strong>Invitee Time Zone:</strong><br />
 | ||||
|     ${evt.attendees[0].timeZone}<br /> | ||||
|     <br /> | ||||
|     <strong>Additional notes:</strong><br /> | ||||
|     ${evt.description} | ||||
|   </div> | ||||
| `;
 | ||||
| 
 | ||||
| // just strip all HTML and convert <br /> to \n
 | ||||
| const text = (evt: CalendarEvent) => html(evt).replace('<br />', "\n").replace(/<[^>]+>/g, ''); | ||||
|  | @ -176,7 +176,7 @@ const getBusyTimes = (withCredentials, dateFrom, dateTo) => Promise.all( | |||
|     (results) => results.reduce((acc, availability) => acc.concat(availability), []) | ||||
| ); | ||||
| 
 | ||||
| const createMeeting = async (credential, calEvent: CalendarEvent): Promise<any> => { | ||||
| const createMeeting = async (credential, calEvent: CalendarEvent, hashUID: string): Promise<any> => { | ||||
|     if(!credential) { | ||||
|         throw new Error("Credentials must be set! Video platforms are optional, so this method shouldn't even be called."); | ||||
|     } | ||||
|  | @ -190,7 +190,7 @@ const createMeeting = async (credential, calEvent: CalendarEvent): Promise<any> | |||
|         url: creationResult.join_url, | ||||
|     }; | ||||
| 
 | ||||
|     const mail = new VideoEventOwnerMail(calEvent, videoCallData); | ||||
|     const mail = new VideoEventOwnerMail(calEvent, hashUID, videoCallData); | ||||
|     const sentMail = await mail.sendEmail(); | ||||
| 
 | ||||
|     return { | ||||
|  |  | |||
|  | @ -44,17 +44,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|   }; | ||||
| 
 | ||||
|   const hashUID: string = translator.fromUUID(uuidv5(JSON.stringify(evt), uuidv5.URL)); | ||||
|   const cancelLink: string = process.env.BASE_URL + '/cancel/' + hashUID; | ||||
|   const rescheduleLink:string = process.env.BASE_URL + '/reschedule/' + hashUID; | ||||
|   const appendLinksToEvents = (event: CalendarEvent) => { | ||||
|     const eventCopy = {...event}; | ||||
|     eventCopy.description += "\n\n" | ||||
|       + "Need to change this event?\n" | ||||
|       + "Cancel: " + cancelLink + "\n" | ||||
|       + "Reschedule:" + rescheduleLink; | ||||
| 
 | ||||
|     return eventCopy; | ||||
|   } | ||||
| 
 | ||||
|   const eventType = await prisma.eventType.findFirst({ | ||||
|     where: { | ||||
|  | @ -90,12 +79,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|     // Use all integrations
 | ||||
|     results = results.concat(await async.mapLimit(calendarCredentials, 5, async (credential) => { | ||||
|       const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid; | ||||
|       return await updateEvent(credential, bookingRefUid, appendLinksToEvents(evt)) | ||||
|       return await updateEvent(credential, bookingRefUid, evt) | ||||
|     })); | ||||
| 
 | ||||
|     results = results.concat(await async.mapLimit(videoCredentials, 5, async (credential) => { | ||||
|       const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid; | ||||
|       return await updateMeeting(credential, bookingRefUid, evt)  // TODO Maybe append links?
 | ||||
|       return await updateMeeting(credential, bookingRefUid, evt) | ||||
|     })); | ||||
| 
 | ||||
|     // Clone elements
 | ||||
|  | @ -126,7 +115,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|   } else { | ||||
|     // Schedule event
 | ||||
|     results = results.concat(await async.mapLimit(calendarCredentials, 5, async (credential) => { | ||||
|       const response = await createEvent(credential, appendLinksToEvents(evt)); | ||||
|       const response = await createEvent(credential, evt, hashUID); | ||||
|       return { | ||||
|         type: credential.type, | ||||
|         response | ||||
|  | @ -134,7 +123,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|     })); | ||||
| 
 | ||||
|     results = results.concat(await async.mapLimit(videoCredentials, 5, async (credential) => { | ||||
|       const response = await createMeeting(credential, evt); | ||||
|       const response = await createMeeting(credential, evt, hashUID); | ||||
|       return { | ||||
|         type: credential.type, | ||||
|         response | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 nicolas
						nicolas