Implemented reschedule mail and fixed bug that rescheduling weren't saved
This commit is contained in:
		
							parent
							
								
									a11641d7b9
								
							
						
					
					
						commit
						869ba9b97c
					
				
					 6 changed files with 440 additions and 297 deletions
				
			
		|  | @ -2,366 +2,379 @@ import EventOwnerMail from "./emails/EventOwnerMail"; | |||
| import EventAttendeeMail from "./emails/EventAttendeeMail"; | ||||
| import {v5 as uuidv5} from 'uuid'; | ||||
| import short from 'short-uuid'; | ||||
| import EventOwnerRescheduledMail from "./emails/EventOwnerRescheduledMail"; | ||||
| import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail"; | ||||
| 
 | ||||
| const translator = short(); | ||||
| 
 | ||||
| const {google} = require('googleapis'); | ||||
| 
 | ||||
| const googleAuth = () => { | ||||
|     const {client_secret, client_id, redirect_uris} = JSON.parse(process.env.GOOGLE_API_CREDENTIALS).web; | ||||
|     return new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]); | ||||
|   const {client_secret, client_id, redirect_uris} = JSON.parse(process.env.GOOGLE_API_CREDENTIALS).web; | ||||
|   return new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]); | ||||
| }; | ||||
| 
 | ||||
| function handleErrorsJson(response) { | ||||
|     if (!response.ok) { | ||||
|         response.json().then(console.log); | ||||
|         throw Error(response.statusText); | ||||
|     } | ||||
|     return response.json(); | ||||
|   if (!response.ok) { | ||||
|     response.json().then(console.log); | ||||
|     throw Error(response.statusText); | ||||
|   } | ||||
|   return response.json(); | ||||
| } | ||||
| 
 | ||||
| function handleErrorsRaw(response) { | ||||
|     if (!response.ok) { | ||||
|         response.text().then(console.log); | ||||
|         throw Error(response.statusText); | ||||
|     } | ||||
|     return response.text(); | ||||
|   if (!response.ok) { | ||||
|     response.text().then(console.log); | ||||
|     throw Error(response.statusText); | ||||
|   } | ||||
|   return response.text(); | ||||
| } | ||||
| 
 | ||||
| const o365Auth = (credential) => { | ||||
| 
 | ||||
|     const isExpired = (expiryDate) => expiryDate < +(new Date()); | ||||
|   const isExpired = (expiryDate) => expiryDate < +(new Date()); | ||||
| 
 | ||||
|     const refreshAccessToken = (refreshToken) => fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { | ||||
|         method: 'POST', | ||||
|         headers: {'Content-Type': 'application/x-www-form-urlencoded'}, | ||||
|         body: new URLSearchParams({ | ||||
|             'scope': 'User.Read Calendars.Read Calendars.ReadWrite', | ||||
|             'client_id': process.env.MS_GRAPH_CLIENT_ID, | ||||
|             'refresh_token': refreshToken, | ||||
|             'grant_type': 'refresh_token', | ||||
|             'client_secret': process.env.MS_GRAPH_CLIENT_SECRET, | ||||
|         }) | ||||
|   const refreshAccessToken = (refreshToken) => fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { | ||||
|     method: 'POST', | ||||
|     headers: {'Content-Type': 'application/x-www-form-urlencoded'}, | ||||
|     body: new URLSearchParams({ | ||||
|       'scope': 'User.Read Calendars.Read Calendars.ReadWrite', | ||||
|       'client_id': process.env.MS_GRAPH_CLIENT_ID, | ||||
|       'refresh_token': refreshToken, | ||||
|       'grant_type': 'refresh_token', | ||||
|       'client_secret': process.env.MS_GRAPH_CLIENT_SECRET, | ||||
|     }) | ||||
|   }) | ||||
|     .then(handleErrorsJson) | ||||
|     .then((responseBody) => { | ||||
|       credential.key.access_token = responseBody.access_token; | ||||
|       credential.key.expiry_date = Math.round((+(new Date()) / 1000) + responseBody.expires_in); | ||||
|       return credential.key.access_token; | ||||
|     }) | ||||
|         .then(handleErrorsJson) | ||||
|         .then((responseBody) => { | ||||
|             credential.key.access_token = responseBody.access_token; | ||||
|             credential.key.expiry_date = Math.round((+(new Date()) / 1000) + responseBody.expires_in); | ||||
|             return credential.key.access_token; | ||||
|         }) | ||||
| 
 | ||||
|     return { | ||||
|         getToken: () => !isExpired(credential.key.expiry_date) ? Promise.resolve(credential.key.access_token) : refreshAccessToken(credential.key.refresh_token) | ||||
|     }; | ||||
|   return { | ||||
|     getToken: () => !isExpired(credential.key.expiry_date) ? Promise.resolve(credential.key.access_token) : refreshAccessToken(credential.key.refresh_token) | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| interface Person { | ||||
|     name?: string, | ||||
|     email: string, | ||||
|     timeZone: string | ||||
|   name?: string, | ||||
|   email: string, | ||||
|   timeZone: string | ||||
| } | ||||
| 
 | ||||
| interface CalendarEvent { | ||||
|     type: string; | ||||
|     title: string; | ||||
|     startTime: string; | ||||
|     endTime: string; | ||||
|     description?: string; | ||||
|     location?: string; | ||||
|     organizer: Person; | ||||
|     attendees: Person[]; | ||||
|   type: string; | ||||
|   title: string; | ||||
|   startTime: string; | ||||
|   endTime: string; | ||||
|   description?: string; | ||||
|   location?: string; | ||||
|   organizer: Person; | ||||
|   attendees: Person[]; | ||||
| }; | ||||
| 
 | ||||
| interface CalendarApiAdapter { | ||||
|     createEvent(event: CalendarEvent): Promise<any>; | ||||
|   createEvent(event: CalendarEvent): Promise<any>; | ||||
| 
 | ||||
|     updateEvent(uid: String, event: CalendarEvent); | ||||
|   updateEvent(uid: String, event: CalendarEvent); | ||||
| 
 | ||||
|     deleteEvent(uid: String); | ||||
|   deleteEvent(uid: String); | ||||
| 
 | ||||
|     getAvailability(dateFrom, dateTo): Promise<any>; | ||||
|   getAvailability(dateFrom, dateTo): Promise<any>; | ||||
| } | ||||
| 
 | ||||
| const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => { | ||||
| 
 | ||||
|     const auth = o365Auth(credential); | ||||
|   const auth = o365Auth(credential); | ||||
| 
 | ||||
|     const translateEvent = (event: CalendarEvent) => { | ||||
|   const translateEvent = (event: CalendarEvent) => { | ||||
| 
 | ||||
|         let optional = {}; | ||||
|         if (event.location) { | ||||
|             optional.location = {displayName: event.location}; | ||||
|         } | ||||
| 
 | ||||
|         return { | ||||
|             subject: event.title, | ||||
|             body: { | ||||
|                 contentType: 'HTML', | ||||
|                 content: event.description, | ||||
|             }, | ||||
|             start: { | ||||
|                 dateTime: event.startTime, | ||||
|                 timeZone: event.organizer.timeZone, | ||||
|             }, | ||||
|             end: { | ||||
|                 dateTime: event.endTime, | ||||
|                 timeZone: event.organizer.timeZone, | ||||
|             }, | ||||
|             attendees: event.attendees.map(attendee => ({ | ||||
|                 emailAddress: { | ||||
|                     address: attendee.email, | ||||
|                     name: attendee.name | ||||
|                 }, | ||||
|                 type: "required" | ||||
|             })), | ||||
|             ...optional | ||||
|         } | ||||
|     }; | ||||
|     let optional = {}; | ||||
|     if (event.location) { | ||||
|       optional.location = {displayName: event.location}; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|         getAvailability: (dateFrom, dateTo) => { | ||||
|             const payload = { | ||||
|                 schedules: [credential.key.email], | ||||
|                 startTime: { | ||||
|                     dateTime: dateFrom, | ||||
|                     timeZone: 'UTC', | ||||
|                 }, | ||||
|                 endTime: { | ||||
|                     dateTime: dateTo, | ||||
|                     timeZone: 'UTC', | ||||
|                 }, | ||||
|                 availabilityViewInterval: 60 | ||||
|             }; | ||||
| 
 | ||||
|             return auth.getToken().then( | ||||
|                 (accessToken) => fetch('https://graph.microsoft.com/v1.0/me/calendar/getSchedule', { | ||||
|                     method: 'post', | ||||
|                     headers: { | ||||
|                         'Authorization': 'Bearer ' + accessToken, | ||||
|                         'Content-Type': 'application/json' | ||||
|                     }, | ||||
|                     body: JSON.stringify(payload) | ||||
|                 }) | ||||
|                     .then(handleErrorsJson) | ||||
|                     .then(responseBody => { | ||||
|                         return responseBody.value[0].scheduleItems.map((evt) => ({ | ||||
|                             start: evt.start.dateTime + 'Z', | ||||
|                             end: evt.end.dateTime + 'Z' | ||||
|                         })) | ||||
|                     }) | ||||
|             ).catch((err) => { | ||||
|                 console.log(err); | ||||
|             }); | ||||
|       subject: event.title, | ||||
|       body: { | ||||
|         contentType: 'HTML', | ||||
|         content: event.description, | ||||
|       }, | ||||
|       start: { | ||||
|         dateTime: event.startTime, | ||||
|         timeZone: event.organizer.timeZone, | ||||
|       }, | ||||
|       end: { | ||||
|         dateTime: event.endTime, | ||||
|         timeZone: event.organizer.timeZone, | ||||
|       }, | ||||
|       attendees: event.attendees.map(attendee => ({ | ||||
|         emailAddress: { | ||||
|           address: attendee.email, | ||||
|           name: attendee.name | ||||
|         }, | ||||
|         createEvent: (event: CalendarEvent) => auth.getToken().then(accessToken => fetch('https://graph.microsoft.com/v1.0/me/calendar/events', { | ||||
|             method: 'POST', | ||||
|             headers: { | ||||
|                 'Authorization': 'Bearer ' + accessToken, | ||||
|                 'Content-Type': 'application/json', | ||||
|             }, | ||||
|             body: JSON.stringify(translateEvent(event)) | ||||
|         }).then(handleErrorsJson).then((responseBody) => ({ | ||||
|             ...responseBody, | ||||
|             disableConfirmationEmail: true, | ||||
|         }))), | ||||
|         deleteEvent: (uid: String) => auth.getToken().then(accessToken => fetch('https://graph.microsoft.com/v1.0/me/calendar/events/' + uid, { | ||||
|             method: 'DELETE', | ||||
|             headers: { | ||||
|                 'Authorization': 'Bearer ' + accessToken | ||||
|             } | ||||
|         }).then(handleErrorsRaw)), | ||||
|         updateEvent: (uid: String, event: CalendarEvent) => auth.getToken().then(accessToken => fetch('https://graph.microsoft.com/v1.0/me/calendar/events/' + uid, { | ||||
|             method: 'PATCH', | ||||
|             headers: { | ||||
|                 'Authorization': 'Bearer ' + accessToken, | ||||
|                 'Content-Type': 'application/json' | ||||
|             }, | ||||
|             body: JSON.stringify(translateEvent(event)) | ||||
|         }).then(handleErrorsRaw)), | ||||
|         type: "required" | ||||
|       })), | ||||
|       ...optional | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   return { | ||||
|     getAvailability: (dateFrom, dateTo) => { | ||||
|       const payload = { | ||||
|         schedules: [credential.key.email], | ||||
|         startTime: { | ||||
|           dateTime: dateFrom, | ||||
|           timeZone: 'UTC', | ||||
|         }, | ||||
|         endTime: { | ||||
|           dateTime: dateTo, | ||||
|           timeZone: 'UTC', | ||||
|         }, | ||||
|         availabilityViewInterval: 60 | ||||
|       }; | ||||
| 
 | ||||
|       return auth.getToken().then( | ||||
|         (accessToken) => fetch('https://graph.microsoft.com/v1.0/me/calendar/getSchedule', { | ||||
|           method: 'post', | ||||
|           headers: { | ||||
|             'Authorization': 'Bearer ' + accessToken, | ||||
|             'Content-Type': 'application/json' | ||||
|           }, | ||||
|           body: JSON.stringify(payload) | ||||
|         }) | ||||
|           .then(handleErrorsJson) | ||||
|           .then(responseBody => { | ||||
|             return responseBody.value[0].scheduleItems.map((evt) => ({ | ||||
|               start: evt.start.dateTime + 'Z', | ||||
|               end: evt.end.dateTime + 'Z' | ||||
|             })) | ||||
|           }) | ||||
|       ).catch((err) => { | ||||
|         console.log(err); | ||||
|       }); | ||||
|     }, | ||||
|     createEvent: (event: CalendarEvent) => auth.getToken().then(accessToken => fetch('https://graph.microsoft.com/v1.0/me/calendar/events', { | ||||
|       method: 'POST', | ||||
|       headers: { | ||||
|         'Authorization': 'Bearer ' + accessToken, | ||||
|         'Content-Type': 'application/json', | ||||
|       }, | ||||
|       body: JSON.stringify(translateEvent(event)) | ||||
|     }).then(handleErrorsJson).then((responseBody) => ({ | ||||
|       ...responseBody, | ||||
|       disableConfirmationEmail: true, | ||||
|     }))), | ||||
|     deleteEvent: (uid: String) => auth.getToken().then(accessToken => fetch('https://graph.microsoft.com/v1.0/me/calendar/events/' + uid, { | ||||
|       method: 'DELETE', | ||||
|       headers: { | ||||
|         'Authorization': 'Bearer ' + accessToken | ||||
|       } | ||||
|     }).then(handleErrorsRaw)), | ||||
|     updateEvent: (uid: String, event: CalendarEvent) => auth.getToken().then(accessToken => fetch('https://graph.microsoft.com/v1.0/me/calendar/events/' + uid, { | ||||
|       method: 'PATCH', | ||||
|       headers: { | ||||
|         'Authorization': 'Bearer ' + accessToken, | ||||
|         'Content-Type': 'application/json' | ||||
|       }, | ||||
|       body: JSON.stringify(translateEvent(event)) | ||||
|     }).then(handleErrorsRaw)), | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const GoogleCalendar = (credential): CalendarApiAdapter => { | ||||
|     const myGoogleAuth = googleAuth(); | ||||
|     myGoogleAuth.setCredentials(credential.key); | ||||
|     return { | ||||
|         getAvailability: (dateFrom, dateTo) => new Promise((resolve, reject) => { | ||||
|             const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|             calendar.calendarList | ||||
|                 .list() | ||||
|                 .then(cals => { | ||||
|                     calendar.freebusy.query({ | ||||
|                         requestBody: { | ||||
|                             timeMin: dateFrom, | ||||
|                             timeMax: dateTo, | ||||
|                             items: cals.data.items | ||||
|                         } | ||||
|                     }, (err, apires) => { | ||||
|                         if (err) { | ||||
|                             reject(err); | ||||
|                         } | ||||
|                         resolve( | ||||
|                             Object.values(apires.data.calendars).flatMap( | ||||
|                                 (item) => item["busy"] | ||||
|                             ) | ||||
|                         ) | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch((err) => { | ||||
|                     reject(err); | ||||
|                 }); | ||||
| 
 | ||||
|         }), | ||||
|         createEvent: (event: CalendarEvent) => new Promise((resolve, reject) => { | ||||
|             const payload = { | ||||
|                 summary: event.title, | ||||
|                 description: event.description, | ||||
|                 start: { | ||||
|                     dateTime: event.startTime, | ||||
|                     timeZone: event.organizer.timeZone, | ||||
|                 }, | ||||
|                 end: { | ||||
|                     dateTime: event.endTime, | ||||
|                     timeZone: event.organizer.timeZone, | ||||
|                 }, | ||||
|                 attendees: event.attendees, | ||||
|                 reminders: { | ||||
|                     useDefault: false, | ||||
|                     overrides: [ | ||||
|                         {'method': 'email', 'minutes': 60} | ||||
|                     ], | ||||
|                 }, | ||||
|             }; | ||||
| 
 | ||||
|             if (event.location) { | ||||
|                 payload['location'] = event.location; | ||||
|   const myGoogleAuth = googleAuth(); | ||||
|   myGoogleAuth.setCredentials(credential.key); | ||||
|   return { | ||||
|     getAvailability: (dateFrom, dateTo) => new Promise((resolve, reject) => { | ||||
|       const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|       calendar.calendarList | ||||
|         .list() | ||||
|         .then(cals => { | ||||
|           calendar.freebusy.query({ | ||||
|             requestBody: { | ||||
|               timeMin: dateFrom, | ||||
|               timeMax: dateTo, | ||||
|               items: cals.data.items | ||||
|             } | ||||
| 
 | ||||
|             const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|             calendar.events.insert({ | ||||
|                 auth: myGoogleAuth, | ||||
|                 calendarId: 'primary', | ||||
|                 resource: payload, | ||||
|             }, function (err, event) { | ||||
|                 if (err) { | ||||
|                     console.log('There was an error contacting the Calendar service: ' + err); | ||||
|                     return reject(err); | ||||
|                 } | ||||
|                 return resolve(event.data); | ||||
|             }); | ||||
|         }), | ||||
|         updateEvent: (uid: String, event: CalendarEvent) => new Promise((resolve, reject) => { | ||||
|             const payload = { | ||||
|                 summary: event.title, | ||||
|                 description: event.description, | ||||
|                 start: { | ||||
|                     dateTime: event.startTime, | ||||
|                     timeZone: event.organizer.timeZone, | ||||
|                 }, | ||||
|                 end: { | ||||
|                     dateTime: event.endTime, | ||||
|                     timeZone: event.organizer.timeZone, | ||||
|                 }, | ||||
|                 attendees: event.attendees, | ||||
|                 reminders: { | ||||
|                     useDefault: false, | ||||
|                     overrides: [ | ||||
|                         {'method': 'email', 'minutes': 60} | ||||
|                     ], | ||||
|                 }, | ||||
|             }; | ||||
| 
 | ||||
|             if (event.location) { | ||||
|                 payload['location'] = event.location; | ||||
|           }, (err, apires) => { | ||||
|             if (err) { | ||||
|               reject(err); | ||||
|             } | ||||
| 
 | ||||
|             const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|             calendar.events.update({ | ||||
|                 auth: myGoogleAuth, | ||||
|                 calendarId: 'primary', | ||||
|                 eventId: uid, | ||||
|                 sendNotifications: true, | ||||
|                 sendUpdates: 'all', | ||||
|                 resource: payload | ||||
|             }, function (err, event) { | ||||
|                 if (err) { | ||||
|                     console.log('There was an error contacting the Calendar service: ' + err); | ||||
|                     return reject(err); | ||||
|                 } | ||||
|                 return resolve(event.data); | ||||
|             }); | ||||
|         }), | ||||
|         deleteEvent: (uid: String) => new Promise( (resolve, reject) => { | ||||
|             const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|             calendar.events.delete({ | ||||
|                 auth: myGoogleAuth, | ||||
|                 calendarId: 'primary', | ||||
|                 eventId: uid, | ||||
|                 sendNotifications: true, | ||||
|                 sendUpdates: 'all', | ||||
|             }, function (err, event) { | ||||
|                 if (err) { | ||||
|                     console.log('There was an error contacting the Calendar service: ' + err); | ||||
|                     return reject(err); | ||||
|                 } | ||||
|                 return resolve(event.data); | ||||
|             }); | ||||
|             resolve( | ||||
|               Object.values(apires.data.calendars).flatMap( | ||||
|                 (item) => item["busy"] | ||||
|               ) | ||||
|             ) | ||||
|           }); | ||||
|         }) | ||||
|     }; | ||||
|         .catch((err) => { | ||||
|           reject(err); | ||||
|         }); | ||||
| 
 | ||||
|     }), | ||||
|     createEvent: (event: CalendarEvent) => new Promise((resolve, reject) => { | ||||
|       const payload = { | ||||
|         summary: event.title, | ||||
|         description: event.description, | ||||
|         start: { | ||||
|           dateTime: event.startTime, | ||||
|           timeZone: event.organizer.timeZone, | ||||
|         }, | ||||
|         end: { | ||||
|           dateTime: event.endTime, | ||||
|           timeZone: event.organizer.timeZone, | ||||
|         }, | ||||
|         attendees: event.attendees, | ||||
|         reminders: { | ||||
|           useDefault: false, | ||||
|           overrides: [ | ||||
|             {'method': 'email', 'minutes': 60} | ||||
|           ], | ||||
|         }, | ||||
|       }; | ||||
| 
 | ||||
|       if (event.location) { | ||||
|         payload['location'] = event.location; | ||||
|       } | ||||
| 
 | ||||
|       const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|       calendar.events.insert({ | ||||
|         auth: myGoogleAuth, | ||||
|         calendarId: 'primary', | ||||
|         resource: payload, | ||||
|       }, function (err, event) { | ||||
|         if (err) { | ||||
|           console.log('There was an error contacting the Calendar service: ' + err); | ||||
|           return reject(err); | ||||
|         } | ||||
|         return resolve(event.data); | ||||
|       }); | ||||
|     }), | ||||
|     updateEvent: (uid: String, event: CalendarEvent) => new Promise((resolve, reject) => { | ||||
|       const payload = { | ||||
|         summary: event.title, | ||||
|         description: event.description, | ||||
|         start: { | ||||
|           dateTime: event.startTime, | ||||
|           timeZone: event.organizer.timeZone, | ||||
|         }, | ||||
|         end: { | ||||
|           dateTime: event.endTime, | ||||
|           timeZone: event.organizer.timeZone, | ||||
|         }, | ||||
|         attendees: event.attendees, | ||||
|         reminders: { | ||||
|           useDefault: false, | ||||
|           overrides: [ | ||||
|             {'method': 'email', 'minutes': 60} | ||||
|           ], | ||||
|         }, | ||||
|       }; | ||||
| 
 | ||||
|       if (event.location) { | ||||
|         payload['location'] = event.location; | ||||
|       } | ||||
| 
 | ||||
|       const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|       calendar.events.update({ | ||||
|         auth: myGoogleAuth, | ||||
|         calendarId: 'primary', | ||||
|         eventId: uid, | ||||
|         sendNotifications: true, | ||||
|         sendUpdates: 'all', | ||||
|         resource: payload | ||||
|       }, function (err, event) { | ||||
|         if (err) { | ||||
|           console.log('There was an error contacting the Calendar service: ' + err); | ||||
|           return reject(err); | ||||
|         } | ||||
|         return resolve(event.data); | ||||
|       }); | ||||
|     }), | ||||
|     deleteEvent: (uid: String) => new Promise((resolve, reject) => { | ||||
|       const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); | ||||
|       calendar.events.delete({ | ||||
|         auth: myGoogleAuth, | ||||
|         calendarId: 'primary', | ||||
|         eventId: uid, | ||||
|         sendNotifications: true, | ||||
|         sendUpdates: 'all', | ||||
|       }, function (err, event) { | ||||
|         if (err) { | ||||
|           console.log('There was an error contacting the Calendar service: ' + err); | ||||
|           return reject(err); | ||||
|         } | ||||
|         return resolve(event.data); | ||||
|       }); | ||||
|     }) | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| // factory
 | ||||
| const calendars = (withCredentials): CalendarApiAdapter[] => withCredentials.map((cred) => { | ||||
|     switch (cred.type) { | ||||
|         case 'google_calendar': | ||||
|             return GoogleCalendar(cred); | ||||
|         case 'office365_calendar': | ||||
|             return MicrosoftOffice365Calendar(cred); | ||||
|         default: | ||||
|             return; // unknown credential, could be legacy? In any case, ignore
 | ||||
|     } | ||||
|   switch (cred.type) { | ||||
|     case 'google_calendar': | ||||
|       return GoogleCalendar(cred); | ||||
|     case 'office365_calendar': | ||||
|       return MicrosoftOffice365Calendar(cred); | ||||
|     default: | ||||
|       return; // unknown credential, could be legacy? In any case, ignore
 | ||||
|   } | ||||
| }).filter(Boolean); | ||||
| 
 | ||||
| 
 | ||||
| const getBusyCalendarTimes = (withCredentials, dateFrom, dateTo) => Promise.all( | ||||
|     calendars(withCredentials).map(c => c.getAvailability(dateFrom, dateTo)) | ||||
|   calendars(withCredentials).map(c => c.getAvailability(dateFrom, dateTo)) | ||||
| ).then( | ||||
|     (results) => results.reduce((acc, availability) => acc.concat(availability), []) | ||||
|   (results) => results.reduce((acc, availability) => acc.concat(availability), []) | ||||
| ); | ||||
| 
 | ||||
| const createEvent = async (credential, calEvent: CalendarEvent): Promise<any> => { | ||||
|     const uid: string = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL)); | ||||
|   const uid: string = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL)); | ||||
| 
 | ||||
|     const creationResult = credential ? await calendars([credential])[0].createEvent(calEvent) : null; | ||||
|   const creationResult = credential ? await calendars([credential])[0].createEvent(calEvent) : null; | ||||
| 
 | ||||
|     const ownerMail = new EventOwnerMail(calEvent, uid); | ||||
|     const attendeeMail = new EventAttendeeMail(calEvent, uid); | ||||
|     await ownerMail.sendEmail(); | ||||
|   const ownerMail = new EventOwnerMail(calEvent, uid); | ||||
|   const attendeeMail = new EventAttendeeMail(calEvent, uid); | ||||
|   await ownerMail.sendEmail(); | ||||
| 
 | ||||
|     if(!creationResult || !creationResult.disableConfirmationEmail) { | ||||
|         await attendeeMail.sendEmail(); | ||||
|     } | ||||
|   if (!creationResult || !creationResult.disableConfirmationEmail) { | ||||
|     await attendeeMail.sendEmail(); | ||||
|   } | ||||
| 
 | ||||
|     return { | ||||
|         uid, | ||||
|         createdEvent: creationResult | ||||
|     }; | ||||
|   return { | ||||
|     uid, | ||||
|     createdEvent: creationResult | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const updateEvent = (credential, uid: String, calEvent: CalendarEvent): Promise<any> => { | ||||
|     if (credential) { | ||||
|         return calendars([credential])[0].updateEvent(uid, calEvent); | ||||
|     } | ||||
| const updateEvent = async (credential, uidToUpdate: String, calEvent: CalendarEvent): Promise<any> => { | ||||
|   const newUid: string = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL)); | ||||
| 
 | ||||
|     return Promise.resolve({}); | ||||
|   const updateResult = credential ? await calendars([credential])[0].updateEvent(uidToUpdate, calEvent) : null; | ||||
| 
 | ||||
|   const ownerMail = new EventOwnerRescheduledMail(calEvent, newUid); | ||||
|   const attendeeMail = new EventAttendeeRescheduledMail(calEvent, newUid); | ||||
|   await ownerMail.sendEmail(); | ||||
| 
 | ||||
|   if (!updateResult || !updateResult.disableConfirmationEmail) { | ||||
|     await attendeeMail.sendEmail(); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     uid: newUid, | ||||
|     updatedEvent: updateResult | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const deleteEvent = (credential, uid: String): Promise<any> => { | ||||
|     if (credential) { | ||||
|         return calendars([credential])[0].deleteEvent(uid); | ||||
|     } | ||||
|   if (credential) { | ||||
|     return calendars([credential])[0].deleteEvent(uid); | ||||
|   } | ||||
| 
 | ||||
|     return Promise.resolve({}); | ||||
|   return Promise.resolve({}); | ||||
| }; | ||||
| 
 | ||||
| export {getBusyCalendarTimes, createEvent, updateEvent, deleteEvent, CalendarEvent}; | ||||
|  |  | |||
|  | @ -49,7 +49,7 @@ export default class EventAttendeeMail extends EventMail { | |||
|    * | ||||
|    * @private | ||||
|    */ | ||||
|   private getInviteeStart(): Dayjs { | ||||
|   protected getInviteeStart(): Dayjs { | ||||
|     return <Dayjs>dayjs(this.calEvent.startTime).tz(this.calEvent.attendees[0].timeZone); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										40
									
								
								lib/emails/EventAttendeeRescheduledMail.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/emails/EventAttendeeRescheduledMail.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| import EventAttendeeMail from "./EventAttendeeMail"; | ||||
| 
 | ||||
| export default class EventAttendeeRescheduledMail extends EventAttendeeMail { | ||||
|   /** | ||||
|    * 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} has been rescheduled to ${this.getInviteeStart().format('h:mma')}  | ||||
|       (${this.calEvent.attendees[0].timeZone}) on ${this.getInviteeStart().format('dddd, LL')}.<br /> | ||||
|       ` + 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: `Rescheduled: ${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_RESCHEDULE_CONFIRMATION_ERROR", this.calEvent.attendees[0].email, error); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										64
									
								
								lib/emails/EventOwnerRescheduledMail.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/emails/EventOwnerRescheduledMail.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| import dayjs, {Dayjs} from "dayjs"; | ||||
| import EventOwnerMail from "./EventOwnerMail"; | ||||
| 
 | ||||
| export default class EventOwnerRescheduledMail extends EventOwnerMail { | ||||
|   /** | ||||
|    * Returns the email text as HTML representation. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getHtmlRepresentation(): string { | ||||
|     return ` | ||||
|       <div> | ||||
|         Hi ${this.calEvent.organizer.name},<br /> | ||||
|         <br /> | ||||
|         Your event has been rescheduled.<br /> | ||||
|         <br /> | ||||
|         <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.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() + `    | ||||
|       </div> | ||||
|     `;
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the payload object for the nodemailer. | ||||
|    * | ||||
|    * @protected | ||||
|    */ | ||||
|   protected getNodeMailerPayload(): Object { | ||||
|     const organizerStart: Dayjs = <Dayjs>dayjs(this.calEvent.startTime).tz(this.calEvent.organizer.timeZone); | ||||
| 
 | ||||
|     return { | ||||
|       icalEvent: { | ||||
|         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}`, | ||||
|       html: this.getHtmlRepresentation(), | ||||
|       text: this.getPlainTextRepresentation(), | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   protected printNodeMailerError(error: string): void { | ||||
|     console.error("SEND_RESCHEDULE_EVENT_NOTIFICATION_ERROR", this.calEvent.organizer.email, error); | ||||
|   } | ||||
| } | ||||
|  | @ -4,6 +4,8 @@ import VideoEventOwnerMail from "./emails/VideoEventOwnerMail"; | |||
| import VideoEventAttendeeMail from "./emails/VideoEventAttendeeMail"; | ||||
| import {v5 as uuidv5} from 'uuid'; | ||||
| import short from 'short-uuid'; | ||||
| import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail"; | ||||
| import EventOwnerRescheduledMail from "./emails/EventOwnerRescheduledMail"; | ||||
| 
 | ||||
| const translator = short(); | ||||
| 
 | ||||
|  | @ -203,12 +205,27 @@ const createMeeting = async (credential, calEvent: CalendarEvent): Promise<any> | |||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const updateMeeting = (credential, uid: String, event: CalendarEvent): Promise<any> => { | ||||
|   if (credential) { | ||||
|     return videoIntegrations([credential])[0].updateMeeting(uid, event); | ||||
| const updateMeeting = async (credential, uidToUpdate: String, calEvent: CalendarEvent): Promise<any> => { | ||||
|   const newUid: string = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL)); | ||||
| 
 | ||||
|   if (!credential) { | ||||
|     throw new Error("Credentials must be set! Video platforms are optional, so this method shouldn't even be called when no video credentials are set."); | ||||
|   } | ||||
| 
 | ||||
|   return Promise.resolve({}); | ||||
|   const updateResult = credential ? await videoIntegrations([credential])[0].updateMeeting(uidToUpdate, calEvent) : null; | ||||
| 
 | ||||
|   const ownerMail = new EventOwnerRescheduledMail(calEvent, newUid); | ||||
|   const attendeeMail = new EventAttendeeRescheduledMail(calEvent, newUid); | ||||
|   await ownerMail.sendEmail(); | ||||
| 
 | ||||
|   if (!updateResult || !updateResult.disableConfirmationEmail) { | ||||
|     await attendeeMail.sendEmail(); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     uid: newUid, | ||||
|     updatedEvent: updateResult | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const deleteMeeting = (credential, uid: String): Promise<any> => { | ||||
|  |  | |||
|  | @ -78,12 +78,21 @@ 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, evt) | ||||
|       const response = await updateEvent(credential, bookingRefUid, evt); | ||||
| 
 | ||||
|       return { | ||||
|         type: credential.type, | ||||
|         response | ||||
|       }; | ||||
|     })); | ||||
| 
 | ||||
|     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) | ||||
|       const response = await updateMeeting(credential, bookingRefUid, evt); | ||||
|       return { | ||||
|         type: credential.type, | ||||
|         response | ||||
|       }; | ||||
|     })); | ||||
| 
 | ||||
|     // Clone elements
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 nicolas
						nicolas