From b376e9e5a4e3dd1ceaffa474aad6989b536092e2 Mon Sep 17 00:00:00 2001 From: nicolas Date: Mon, 7 Jun 2021 01:10:56 +0200 Subject: [PATCH] Prepared google calendar deletion --- lib/calendarClient.ts | 165 ++++++++++++++++++++++++++------------- pages/api/book/[user].ts | 5 +- pages/api/cancel.ts | 1 + 3 files changed, 115 insertions(+), 56 deletions(-) diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts index 855ed6ea..6df028b9 100644 --- a/lib/calendarClient.ts +++ b/lib/calendarClient.ts @@ -1,4 +1,3 @@ - const {google} = require('googleapis'); import createNewEventEmail from "./emails/new-event"; @@ -9,7 +8,7 @@ const googleAuth = () => { function handleErrors(response) { if (!response.ok) { - response.json().then( console.log ); + response.json().then(console.log); throw Error(response.statusText); } return response.json(); @@ -22,7 +21,7 @@ const o365Auth = (credential) => { const refreshAccessToken = (refreshToken) => fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + 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, @@ -31,19 +30,24 @@ const o365Auth = (credential) => { 'client_secret': process.env.MS_GRAPH_CLIENT_SECRET, }) }) - .then(handleErrors) - .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(handleErrors) + .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) + 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 } +interface Person { + name?: string, + email: string, + timeZone: string +} + interface CalendarEvent { type: string; title: string; @@ -57,6 +61,11 @@ interface CalendarEvent { interface CalendarApiAdapter { createEvent(event: CalendarEvent): Promise; + + updateEvent(uid: String, event: CalendarEvent); + + deleteEvent(uid: String); + getAvailability(dateFrom, dateTo): Promise; } @@ -68,7 +77,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => { let optional = {}; if (event.location) { - optional.location = { displayName: event.location }; + optional.location = {displayName: event.location}; } return { @@ -99,7 +108,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => { return { getAvailability: (dateFrom, dateTo) => { const payload = { - schedules: [ credential.key.email ], + schedules: [credential.key.email], startTime: { dateTime: dateFrom, timeZone: 'UTC', @@ -120,25 +129,34 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => { }, body: JSON.stringify(payload) }) - .then(handleErrors) - .then( responseBody => { - return responseBody.value[0].scheduleItems.map( (evt) => ({ start: evt.start.dateTime + 'Z', end: evt.end.dateTime + 'Z' })) - }) - ).catch( (err) => { + .then(handleErrors) + .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', { + 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(handleErrors).then( (responseBody) => ({ + }).then(handleErrors).then((responseBody) => ({ ...responseBody, disableConfirmationEmail: true, - }))) + }))), + deleteEvent: (uid: String) => { + //TODO Implement + }, + updateEvent: (uid: String, event: CalendarEvent) => { + //TODO Implement + }, } }; @@ -146,34 +164,34 @@ 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 }); + 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"] - ) - ) + .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); }); - }) - .catch((err) => { - reject(err); - }); }), - createEvent: (event: CalendarEvent) => new Promise( (resolve, reject) => { + createEvent: (event: CalendarEvent) => new Promise((resolve, reject) => { const payload = { summary: event.title, description: event.description, @@ -198,12 +216,31 @@ const GoogleCalendar = (credential): CalendarApiAdapter => { payload['location'] = event.location; } - const calendar = google.calendar({version: 'v3', auth: myGoogleAuth }); + const calendar = google.calendar({version: 'v3', auth: myGoogleAuth}); calendar.events.insert({ auth: myGoogleAuth, calendarId: 'primary', resource: payload, - }, function(err, event) { + }, 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) => { + //TODO implement + }), + 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: true, + }, function (err, event) { if (err) { console.log('There was an error contacting the Calendar service: ' + err); return reject(err); @@ -215,10 +252,12 @@ const GoogleCalendar = (credential): CalendarApiAdapter => { }; // factory -const calendars = (withCredentials): CalendarApiAdapter[] => withCredentials.map( (cred) => { - switch(cred.type) { - case 'google_calendar': return GoogleCalendar(cred); - case 'office365_calendar': return MicrosoftOffice365Calendar(cred); +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 } @@ -226,15 +265,15 @@ const calendars = (withCredentials): CalendarApiAdapter[] => withCredentials.map const getBusyTimes = (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 = (credential, calEvent: CalendarEvent): Promise => { createNewEventEmail( - calEvent, + calEvent, ); if (credential) { @@ -244,4 +283,20 @@ const createEvent = (credential, calEvent: CalendarEvent): Promise => { return Promise.resolve({}); }; -export { getBusyTimes, createEvent, CalendarEvent }; +const updateEvent = (credential, uid: String, calEvent: CalendarEvent): Promise => { + if (credential) { + return calendars([credential])[0].updateEvent(uid, calEvent); + } + + return Promise.resolve({}); +}; + +const deleteEvent = (credential, uid: String): Promise => { + if (credential) { + return calendars([credential])[0].deleteEvent(uid); + } + + return Promise.resolve({}); +}; + +export {getBusyTimes, createEvent, CalendarEvent}; diff --git a/pages/api/book/[user].ts b/pages/api/book/[user].ts index 4289bd31..f7b58a05 100644 --- a/pages/api/book/[user].ts +++ b/pages/api/book/[user].ts @@ -53,7 +53,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) userId: currentUser.id, references: { create: [ - //TODO Create references + { + type: currentUser.credentials[0].type, + uid: result.id + } ] }, eventTypeId: eventType.id, diff --git a/pages/api/cancel.ts b/pages/api/cancel.ts index c5fb5341..54c58791 100644 --- a/pages/api/cancel.ts +++ b/pages/api/cancel.ts @@ -28,6 +28,7 @@ export default async function handler(req, res) { }); //TODO Delete booking from calendar integrations + //TODO Perhaps send emails to user and client to tell about the cancellation const deleteBooking = await prisma.booking.delete({ where: {