diff --git a/components/booking/AvailableTimes.tsx b/components/booking/AvailableTimes.tsx
index d44465d0..2e1f5f3e 100644
--- a/components/booking/AvailableTimes.tsx
+++ b/components/booking/AvailableTimes.tsx
@@ -1,38 +1,37 @@
-import dayjs, {Dayjs} from "dayjs";
-import isBetween from 'dayjs/plugin/isBetween';
+import dayjs from "dayjs";
+import isBetween from "dayjs/plugin/isBetween";
dayjs.extend(isBetween);
-import {useEffect, useMemo, useState} from "react";
+import { useEffect, useState } from "react";
import getSlots from "../../lib/slots";
import Link from "next/link";
-import {timeZone} from "../../lib/clock";
-import {useRouter} from "next/router";
-import {ExclamationIcon} from "@heroicons/react/solid";
+import { timeZone } from "../../lib/clock";
+import { useRouter } from "next/router";
+import { ExclamationIcon } from "@heroicons/react/solid";
const AvailableTimes = (props) => {
-
const router = useRouter();
const { user, rescheduleUid } = router.query;
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);
const times = getSlots({
- calendarTimeZone: props.user.timeZone,
- selectedTimeZone: timeZone(),
- eventLength: props.eventType.length,
- selectedDate: props.date,
- dayStartTime: props.user.startTime,
- dayEndTime: props.user.endTime,
- });
+ calendarTimeZone: props.user.timeZone,
+ selectedTimeZone: timeZone(),
+ eventLength: props.eventType.length,
+ selectedDate: props.date,
+ dayStartTime: props.user.startTime,
+ dayEndTime: props.user.endTime,
+ });
const handleAvailableSlots = (busyTimes: []) => {
// Check for conflicts
for (let i = times.length - 1; i >= 0; i -= 1) {
- busyTimes.forEach(busyTime => {
- let startTime = dayjs(busyTime.start);
- let endTime = dayjs(busyTime.end);
+ busyTimes.forEach((busyTime) => {
+ const startTime = dayjs(busyTime.start);
+ const endTime = dayjs(busyTime.end);
// Check if start times are the same
- if (dayjs(times[i]).format('HH:mm') == startTime.format('HH:mm')) {
+ if (dayjs(times[i]).format("HH:mm") == startTime.format("HH:mm")) {
times.splice(i, 1);
}
@@ -42,12 +41,12 @@ const AvailableTimes = (props) => {
}
// Check if slot end time is between start and end time
- if (dayjs(times[i]).add(props.eventType.length, 'minutes').isBetween(startTime, endTime)) {
+ if (dayjs(times[i]).add(props.eventType.length, "minutes").isBetween(startTime, endTime)) {
times.splice(i, 1);
}
// Check if startTime is between slot
- if (startTime.isBetween(dayjs(times[i]), dayjs(times[i]).add(props.eventType.length, 'minutes'))) {
+ if (startTime.isBetween(dayjs(times[i]), dayjs(times[i]).add(props.eventType.length, "minutes"))) {
times.splice(i, 1);
}
});
@@ -60,49 +59,64 @@ const AvailableTimes = (props) => {
useEffect(() => {
setLoaded(false);
setError(false);
- fetch(`/api/availability/${user}?dateFrom=${props.date.startOf('day').utc().format()}&dateTo=${props.date.endOf('day').utc().format()}`)
- .then( res => res.json())
+ fetch(
+ `/api/availability/${user}?dateFrom=${props.date.startOf("day").utc().format()}&dateTo=${props.date
+ .endOf("day")
+ .utc()
+ .format()}`
+ )
+ .then((res) => res.json())
.then(handleAvailableSlots)
- .catch(e => setError(true))
+ .catch((e) => {
+ console.error(e);
+ setError(true);
+ });
}, [props.date]);
return (
-
- {props.date.format("dddd DD MMMM YYYY")}
-
+ {props.date.format("dddd DD MMMM YYYY")}
- {
- !error && loaded && times.map((time) =>
+ {!error &&
+ loaded &&
+ times.map((time) => (
- )
- }
- {!error && !loaded &&
}
- {error &&
-
+ ))}
+ {!error && !loaded &&
}
+ {error && (
+
}
+
+ )}
);
-}
+};
export default AvailableTimes;
diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts
index b830fdfb..4d8d7421 100644
--- a/lib/calendarClient.ts
+++ b/lib/calendarClient.ts
@@ -7,44 +7,51 @@ import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail"
const translator = short();
+// 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;
+ const { client_secret, client_id, redirect_uris } = JSON.parse(process.env.GOOGLE_API_CREDENTIALS).web;
const myGoogleAuth = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
- myGoogleAuth.setCredentials(credential.key);
+ myGoogleAuth.setCredentials(credential.key);
- const isExpired = () => myGoogleAuth.isTokenExpiring();
+ const isExpired = () => myGoogleAuth.isTokenExpiring();
- const refreshAccessToken = () => myGoogleAuth.refreshToken(credential.key.refresh_token).then(res => {
+ const refreshAccessToken = () =>
+ myGoogleAuth
+ .refreshToken(credential.key.refresh_token)
+ .then((res) => {
const token = res.res.data;
credential.key.access_token = token.access_token;
credential.key.expiry_date = token.expiry_date;
- return prisma.credential.update({
+ return prisma.credential
+ .update({
where: {
- id: credential.id
+ id: credential.id,
},
data: {
- key: credential.key
- }
- }).then(() => {
+ key: credential.key,
+ },
+ })
+ .then(() => {
myGoogleAuth.setCredentials(credential.key);
return myGoogleAuth;
- });
- }).catch(err => {
+ });
+ })
+ .catch((err) => {
console.error("Error refreshing google token", err);
return myGoogleAuth;
- });
+ });
- return {
- getToken: () => !isExpired() ? Promise.resolve(myGoogleAuth) : refreshAccessToken()
- };
+ return {
+ getToken: () => (!isExpired() ? Promise.resolve(myGoogleAuth) : refreshAccessToken()),
+ };
};
function handleErrorsJson(response) {
if (!response.ok) {
- response.json().then(e => console.error("O365 Error", e));
+ response.json().then((e) => console.error("O365 Error", e));
throw Error(response.statusText);
}
return response.json();
@@ -52,41 +59,43 @@ function handleErrorsJson(response) {
function handleErrorsRaw(response) {
if (!response.ok) {
- response.text().then(e => console.error("O365 Error", e));
+ response.text().then((e) => console.error("O365 Error", e));
throw Error(response.statusText);
}
return response.text();
}
const o365Auth = (credential) => {
- const isExpired = (expiryDate) => expiryDate < Math.round((+(new Date()) / 1000));
+ const isExpired = (expiryDate) => expiryDate < Math.round(+new Date() / 1000);
const refreshAccessToken = (refreshToken) => {
- return 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 prisma.credential.update({
- where: {
- id: credential.id
- },
- data: {
- key: credential.key
- }
- }).then(() => credential.key.access_token)
+ return 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 prisma.credential
+ .update({
+ where: {
+ id: credential.id,
+ },
+ data: {
+ key: credential.key,
+ },
})
- }
+ .then(() => credential.key.access_token);
+ });
+ };
return {
getToken: () =>
@@ -128,15 +137,11 @@ interface IntegrationCalendar {
interface CalendarApiAdapter {
createEvent(event: CalendarEvent): Promise;
- updateEvent(uid: String, event: CalendarEvent);
+ updateEvent(uid: string, event: CalendarEvent);
- deleteEvent(uid: String);
+ deleteEvent(uid: string);
- getAvailability(
- dateFrom,
- dateTo,
- selectedCalendars: IntegrationCalendar[]
- ): Promise;
+ getAvailability(dateFrom, dateTo, selectedCalendars: IntegrationCalendar[]): Promise;
listCalendars(): Promise;
}
@@ -145,7 +150,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
const auth = o365Auth(credential);
const translateEvent = (event: CalendarEvent) => {
- let optional = {};
+ const optional = {};
if (event.location) {
optional.location = { displayName: event.location };
}
@@ -203,12 +208,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
return {
getAvailability: (dateFrom, dateTo, selectedCalendars) => {
- const filter =
- "?$filter=start/dateTime ge '" +
- dateFrom +
- "' and end/dateTime le '" +
- dateTo +
- "'";
+ const filter = "?$filter=start/dateTime ge '" + dateFrom + "' and end/dateTime le '" + dateTo + "'";
return auth
.getToken()
.then((accessToken) => {
@@ -227,10 +227,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
).then((ids: string[]) => {
const urls = ids.map(
(calendarId) =>
- "https://graph.microsoft.com/v1.0/me/calendars/" +
- calendarId +
- "/events" +
- filter
+ "https://graph.microsoft.com/v1.0/me/calendars/" + calendarId + "/events" + filter
);
return Promise.all(
urls.map((url) =>
@@ -249,9 +246,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
}))
)
)
- ).then((results) =>
- results.reduce((acc, events) => acc.concat(events), [])
- );
+ ).then((results) => results.reduce((acc, events) => acc.concat(events), []));
});
})
.catch((err) => {
@@ -274,7 +269,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
disableConfirmationEmail: true,
}))
),
- deleteEvent: (uid: String) =>
+ deleteEvent: (uid: string) =>
auth.getToken().then((accessToken) =>
fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
method: "DELETE",
@@ -283,7 +278,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
},
}).then(handleErrorsRaw)
),
- updateEvent: (uid: String, event: CalendarEvent) =>
+ updateEvent: (uid: string, event: CalendarEvent) =>
auth.getToken().then((accessToken) =>
fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
method: "PATCH",
@@ -299,162 +294,189 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
};
const GoogleCalendar = (credential): CalendarApiAdapter => {
- const auth = googleAuth(credential);
- const integrationType = "google_calendar";
+ const auth = googleAuth(credential);
+ const integrationType = "google_calendar";
- return {
- getAvailability: (dateFrom, dateTo, selectedCalendars) => new Promise((resolve, reject) => auth.getToken().then(myGoogleAuth => {
- const calendar = google.calendar({version: 'v3', auth: myGoogleAuth});
- const selectedCalendarIds = selectedCalendars.filter(e => e.integration === integrationType).map(e => e.externalId);
- if (selectedCalendarIds.length == 0 && selectedCalendars.length > 0){
- // Only calendars of other integrations selected
- resolve([]);
- return;
- }
+ return {
+ getAvailability: (dateFrom, dateTo, selectedCalendars) =>
+ new Promise((resolve, reject) =>
+ auth.getToken().then((myGoogleAuth) => {
+ const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
+ const selectedCalendarIds = selectedCalendars
+ .filter((e) => e.integration === integrationType)
+ .map((e) => e.externalId);
+ if (selectedCalendarIds.length == 0 && selectedCalendars.length > 0) {
+ // Only calendars of other integrations selected
+ resolve([]);
+ return;
+ }
- (selectedCalendarIds.length == 0
- ? calendar.calendarList.list().then(cals => cals.data.items.map(cal => cal.id))
- : Promise.resolve(selectedCalendarIds)).then(calsIds => {
- calendar.freebusy.query({
- requestBody: {
- timeMin: dateFrom,
- timeMax: dateTo,
- items: calsIds.map(id => ({id: id}))
- }
- }, (err, apires) => {
- if (err) {
- reject(err);
- }
- resolve(
- Object.values(apires.data.calendars).flatMap(
- (item) => item["busy"]
- )
- )
- });
- })
- .catch((err) => {
- console.error('There was an error contacting google calendar service: ', err);
+ (selectedCalendarIds.length == 0
+ ? calendar.calendarList.list().then((cals) => cals.data.items.map((cal) => cal.id))
+ : Promise.resolve(selectedCalendarIds)
+ )
+ .then((calsIds) => {
+ calendar.freebusy.query(
+ {
+ requestBody: {
+ timeMin: dateFrom,
+ timeMax: dateTo,
+ items: calsIds.map((id) => ({ id: id })),
+ },
+ },
+ (err, apires) => {
+ if (err) {
reject(err);
- });
-
- })),
- createEvent: (event: CalendarEvent) => new Promise((resolve, reject) => auth.getToken().then(myGoogleAuth => {
- 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;
- }
-
- if (event.conferenceData) {
- payload["conferenceData"] = event.conferenceData;
- }
-
- const calendar = google.calendar({version: 'v3', auth: myGoogleAuth});
- calendar.events.insert({
- auth: myGoogleAuth,
- calendarId: 'primary',
- resource: payload,
- }, function (err, event) {
- if (err) {
- console.error('There was an error contacting google calendar service: ', err);
- return reject(err);
- }
- return resolve(event.data);
- });
- })),
- updateEvent: (uid: String, event: CalendarEvent) => new Promise((resolve, reject) => auth.getToken().then(myGoogleAuth => {
- 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.error('There was an error contacting google calendar service: ', err);
- return reject(err);
+ }
+ resolve(Object.values(apires.data.calendars).flatMap((item) => item["busy"]));
}
- return resolve(event.data);
+ );
+ })
+ .catch((err) => {
+ console.error("There was an error contacting google calendar service: ", err);
+ reject(err);
});
- })),
- deleteEvent: (uid: String) => new Promise( (resolve, reject) => auth.getToken().then(myGoogleAuth => {
- 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.error('There was an error contacting google calendar service: ', err);
- return reject(err);
- }
- return resolve(event.data);
+ })
+ ),
+ createEvent: (event: CalendarEvent) =>
+ new Promise((resolve, reject) =>
+ auth.getToken().then((myGoogleAuth) => {
+ 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;
+ }
+
+ if (event.conferenceData) {
+ payload["conferenceData"] = event.conferenceData;
+ }
+
+ const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
+ calendar.events.insert(
+ {
+ auth: myGoogleAuth,
+ calendarId: "primary",
+ resource: payload,
+ },
+ function (err, event) {
+ if (err) {
+ console.error("There was an error contacting google calendar service: ", err);
+ return reject(err);
+ }
+ return resolve(event.data);
+ }
+ );
+ })
+ ),
+ updateEvent: (uid: string, event: CalendarEvent) =>
+ new Promise((resolve, reject) =>
+ auth.getToken().then((myGoogleAuth) => {
+ 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.error("There was an error contacting google calendar service: ", err);
+ return reject(err);
+ }
+ return resolve(event.data);
+ }
+ );
+ })
+ ),
+ deleteEvent: (uid: string) =>
+ new Promise((resolve, reject) =>
+ auth.getToken().then((myGoogleAuth) => {
+ 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.error("There was an error contacting google calendar service: ", err);
+ return reject(err);
+ }
+ return resolve(event.data);
+ }
+ );
+ })
+ ),
+ listCalendars: () =>
+ new Promise((resolve, reject) =>
+ auth.getToken().then((myGoogleAuth) => {
+ const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
+ calendar.calendarList
+ .list()
+ .then((cals) => {
+ resolve(
+ cals.data.items.map((cal) => {
+ const calendar: IntegrationCalendar = {
+ externalId: cal.id,
+ integration: integrationType,
+ name: cal.summary,
+ primary: cal.primary,
+ };
+ return calendar;
+ })
+ );
+ })
+ .catch((err) => {
+ console.error("There was an error contacting google calendar service: ", err);
+ reject(err);
});
- })),
- listCalendars: () => new Promise((resolve, reject) => auth.getToken().then(myGoogleAuth => {
- const calendar = google.calendar({version: 'v3', auth: myGoogleAuth});
- calendar.calendarList
- .list()
- .then(cals => {
- resolve(cals.data.items.map(cal => {
- const calendar: IntegrationCalendar = {
- externalId: cal.id, integration: integrationType, name: cal.summary, primary: cal.primary
- }
- return calendar;
- }))
- })
- .catch((err) => {
- console.error('There was an error contacting google calendar service: ', err);
- reject(err);
- });
- }))
- };
+ })
+ ),
+ };
};
// factory
@@ -472,50 +494,36 @@ const calendars = (withCredentials): CalendarApiAdapter[] =>
})
.filter(Boolean);
-const getBusyCalendarTimes = (
- withCredentials,
- dateFrom,
- dateTo,
- selectedCalendars
-) =>
+const getBusyCalendarTimes = (withCredentials, dateFrom, dateTo, selectedCalendars) =>
Promise.all(
- calendars(withCredentials).map((c) =>
- c.getAvailability(dateFrom, dateTo, selectedCalendars)
- )
+ calendars(withCredentials).map((c) => c.getAvailability(dateFrom, dateTo, selectedCalendars))
).then((results) => {
return results.reduce((acc, availability) => acc.concat(availability), []);
});
const listCalendars = (withCredentials) =>
- Promise.all(calendars(withCredentials).map((c) => c.listCalendars())).then(
- (results) => results.reduce((acc, calendars) => acc.concat(calendars), [])
+ Promise.all(calendars(withCredentials).map((c) => c.listCalendars())).then((results) =>
+ results.reduce((acc, calendars) => acc.concat(calendars), [])
);
-const createEvent = async (
- credential,
- calEvent: CalendarEvent
-): Promise => {
- const uid: string = translator.fromUUID(
- uuidv5(JSON.stringify(calEvent), uuidv5.URL)
- );
+const createEvent = async (credential, calEvent: CalendarEvent): Promise => {
+ 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 organizerMail = new EventOrganizerMail(calEvent, uid);
const attendeeMail = new EventAttendeeMail(calEvent, uid);
try {
await organizerMail.sendEmail();
} catch (e) {
- console.error("organizerMail.sendEmail failed", e)
+ console.error("organizerMail.sendEmail failed", e);
}
if (!creationResult || !creationResult.disableConfirmationEmail) {
try {
await attendeeMail.sendEmail();
} catch (e) {
- console.error("attendeeMail.sendEmail failed", e)
+ console.error("attendeeMail.sendEmail failed", e);
}
}
@@ -525,14 +533,8 @@ const createEvent = async (
};
};
-const updateEvent = async (
- credential,
- uidToUpdate: String,
- calEvent: CalendarEvent
-): Promise => {
- const newUid: string = translator.fromUUID(
- uuidv5(JSON.stringify(calEvent), uuidv5.URL)
- );
+const updateEvent = async (credential, uidToUpdate: string, calEvent: CalendarEvent): Promise => {
+ const newUid: string = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL));
const updateResult = credential
? await calendars([credential])[0].updateEvent(uidToUpdate, calEvent)
@@ -543,14 +545,14 @@ const updateEvent = async (
try {
await organizerMail.sendEmail();
} catch (e) {
- console.error("organizerMail.sendEmail failed", e)
+ console.error("organizerMail.sendEmail failed", e);
}
if (!updateResult || !updateResult.disableConfirmationEmail) {
try {
await attendeeMail.sendEmail();
} catch (e) {
- console.error("attendeeMail.sendEmail failed", e)
+ console.error("attendeeMail.sendEmail failed", e);
}
}
@@ -560,7 +562,7 @@ const updateEvent = async (
};
};
-const deleteEvent = (credential, uid: String): Promise => {
+const deleteEvent = (credential, uid: string): Promise => {
if (credential) {
return calendars([credential])[0].deleteEvent(uid);
}
diff --git a/pages/api/book/[user].ts b/pages/api/book/[user].ts
index 386f59ab..db82b3e6 100644
--- a/pages/api/book/[user].ts
+++ b/pages/api/book/[user].ts
@@ -1,38 +1,38 @@
-import type {NextApiRequest, NextApiResponse} from 'next';
-import prisma from '../../../lib/prisma';
-import {CalendarEvent, createEvent, updateEvent} from '../../../lib/calendarClient';
-import async from 'async';
-import {v5 as uuidv5} from 'uuid';
-import short from 'short-uuid';
-import {createMeeting, updateMeeting} from "../../../lib/videoClient";
+import type { NextApiRequest, NextApiResponse } from "next";
+import prisma from "../../../lib/prisma";
+import { CalendarEvent, createEvent, updateEvent } from "../../../lib/calendarClient";
+import async from "async";
+import { v5 as uuidv5 } from "uuid";
+import short from "short-uuid";
+import { createMeeting, updateMeeting } from "../../../lib/videoClient";
import EventAttendeeMail from "../../../lib/emails/EventAttendeeMail";
-import {getEventName} from "../../../lib/event";
-import { LocationType } from '../../../lib/location';
-import merge from "lodash.merge"
+import { getEventName } from "../../../lib/event";
+import { LocationType } from "../../../lib/location";
+import merge from "lodash.merge";
const translator = short();
interface p {
- location: string
+ location: string;
}
-const getLocationRequestFromIntegration = ({location}: p) => {
+const getLocationRequestFromIntegration = ({ location }: p) => {
if (location === LocationType.GoogleMeet.valueOf()) {
- const requestId = uuidv5(location, uuidv5.URL)
+ const requestId = uuidv5(location, uuidv5.URL);
return {
conferenceData: {
createRequest: {
- requestId: requestId
- }
- }
- }
+ requestId: requestId,
+ },
+ },
+ };
}
- return null
-}
+ return null;
+};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
- const {user} = req.query;
+ const { user } = req.query;
const currentUser = await prisma.user.findFirst({
where: {
@@ -44,27 +44,27 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
timeZone: true,
email: true,
name: true,
- }
+ },
});
// Split credentials up into calendar credentials and video credentials
- const calendarCredentials = currentUser.credentials.filter(cred => cred.type.endsWith('_calendar'));
- const videoCredentials = currentUser.credentials.filter(cred => cred.type.endsWith('_video'));
+ const calendarCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar"));
+ const videoCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_video"));
const rescheduleUid = req.body.rescheduleUid;
const selectedEventType = await prisma.eventType.findFirst({
where: {
userId: currentUser.id,
- id: req.body.eventTypeId
+ id: req.body.eventTypeId,
},
select: {
eventName: true,
- title: true
- }
+ title: true,
+ },
});
- let rawLocation = req.body.location
+ const rawLocation = req.body.location;
let evt: CalendarEvent = {
type: selectedEventType.title,
@@ -72,38 +72,35 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
description: req.body.notes,
startTime: req.body.start,
endTime: req.body.end,
- organizer: {email: currentUser.email, name: currentUser.name, timeZone: currentUser.timeZone},
- attendees: [
- {email: req.body.email, name: req.body.name, timeZone: req.body.timeZone}
- ]
+ organizer: { email: currentUser.email, name: currentUser.name, timeZone: currentUser.timeZone },
+ attendees: [{ email: req.body.email, name: req.body.name, timeZone: req.body.timeZone }],
};
// If phone or inPerson use raw location
// set evt.location to req.body.location
- if (!rawLocation?.includes('integration')) {
- evt.location = rawLocation
+ if (!rawLocation?.includes("integration")) {
+ evt.location = rawLocation;
}
-
// If location is set to an integration location
// Build proper transforms for evt object
// Extend evt object with those transformations
- if (rawLocation?.includes('integration')) {
- let maybeLocationRequestObject = getLocationRequestFromIntegration({
- location: rawLocation
- })
-
- evt = merge(evt, maybeLocationRequestObject)
+ if (rawLocation?.includes("integration")) {
+ const maybeLocationRequestObject = getLocationRequestFromIntegration({
+ location: rawLocation,
+ });
+
+ evt = merge(evt, maybeLocationRequestObject);
}
-
+
const eventType = await prisma.eventType.findFirst({
where: {
userId: currentUser.id,
- title: evt.type
+ title: evt.type,
},
select: {
- id: true
- }
+ id: true,
+ },
});
let results = [];
@@ -113,7 +110,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// Reschedule event
const booking = await prisma.booking.findFirst({
where: {
- uid: rescheduleUid
+ uid: rescheduleUid,
},
select: {
id: true,
@@ -121,35 +118,39 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
select: {
id: true,
type: true,
- uid: true
- }
- }
- }
+ uid: true,
+ },
+ },
+ },
});
// 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 updateEvent(credential, bookingRefUid, evt)
- .then(response => ({type: credential.type, success: true, response}))
- .catch(e => {
- console.error("updateEvent failed", e)
- return {type: credential.type, success: false}
- });
- }));
+ results = results.concat(
+ await async.mapLimit(calendarCredentials, 5, async (credential) => {
+ const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
+ return updateEvent(credential, bookingRefUid, evt)
+ .then((response) => ({ type: credential.type, success: true, response }))
+ .catch((e) => {
+ console.error("updateEvent failed", e);
+ return { type: credential.type, success: false };
+ });
+ })
+ );
- results = results.concat(await async.mapLimit(videoCredentials, 5, async (credential) => {
- const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
- return updateMeeting(credential, bookingRefUid, evt)
- .then(response => ({type: credential.type, success: true, response}))
- .catch(e => {
- console.error("updateMeeting failed", e)
- return {type: credential.type, success: false}
- });
- }));
+ results = results.concat(
+ await async.mapLimit(videoCredentials, 5, async (credential) => {
+ const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
+ return updateMeeting(credential, bookingRefUid, evt)
+ .then((response) => ({ type: credential.type, success: true, response }))
+ .catch((e) => {
+ console.error("updateMeeting failed", e);
+ return { type: credential.type, success: false };
+ });
+ })
+ );
- if (results.length > 0 && results.every(res => !res.success)) {
- res.status(500).json({message: "Rescheduling failed"});
+ if (results.length > 0 && results.every((res) => !res.success)) {
+ res.status(500).json({ message: "Rescheduling failed" });
return;
}
@@ -157,86 +158,88 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
referencesToCreate = [...booking.references];
// Now we can delete the old booking and its references.
- let bookingReferenceDeletes = prisma.bookingReference.deleteMany({
+ const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
where: {
- bookingId: booking.id
- }
+ bookingId: booking.id,
+ },
});
- let attendeeDeletes = prisma.attendee.deleteMany({
+ const attendeeDeletes = prisma.attendee.deleteMany({
where: {
- bookingId: booking.id
- }
+ bookingId: booking.id,
+ },
});
- let bookingDeletes = prisma.booking.delete({
+ const bookingDeletes = prisma.booking.delete({
where: {
- uid: rescheduleUid
- }
+ uid: rescheduleUid,
+ },
});
- await Promise.all([
- bookingReferenceDeletes,
- attendeeDeletes,
- bookingDeletes
- ]);
+ await Promise.all([bookingReferenceDeletes, attendeeDeletes, bookingDeletes]);
} else {
// Schedule event
- results = results.concat(await async.mapLimit(calendarCredentials, 5, async (credential) => {
- return createEvent(credential, evt)
- .then(response => ({type: credential.type, success: true, response}))
- .catch(e => {
- console.error("createEvent failed", e)
- return {type: credential.type, success: false}
- });
- }));
+ results = results.concat(
+ await async.mapLimit(calendarCredentials, 5, async (credential) => {
+ return createEvent(credential, evt)
+ .then((response) => ({ type: credential.type, success: true, response }))
+ .catch((e) => {
+ console.error("createEvent failed", e);
+ return { type: credential.type, success: false };
+ });
+ })
+ );
- results = results.concat(await async.mapLimit(videoCredentials, 5, async (credential) => {
- return createMeeting(credential, evt)
- .then(response => ({type: credential.type, success: true, response}))
- .catch(e => {
- console.error("createMeeting failed", e)
- return {type: credential.type, success: false}
- });
- }));
+ results = results.concat(
+ await async.mapLimit(videoCredentials, 5, async (credential) => {
+ return createMeeting(credential, evt)
+ .then((response) => ({ type: credential.type, success: true, response }))
+ .catch((e) => {
+ console.error("createMeeting failed", e);
+ return { type: credential.type, success: false };
+ });
+ })
+ );
- if (results.length > 0 && results.every(res => !res.success)) {
- res.status(500).json({message: "Booking failed"});
+ if (results.length > 0 && results.every((res) => !res.success)) {
+ res.status(500).json({ message: "Booking failed" });
return;
}
- referencesToCreate = results.map((result => {
+ referencesToCreate = results.map((result) => {
return {
type: result.type,
- uid: result.response.createdEvent.id.toString()
+ uid: result.response.createdEvent.id.toString(),
};
- }));
+ });
}
- const hashUID = results.length > 0 ? results[0].response.uid : translator.fromUUID(uuidv5(JSON.stringify(evt), uuidv5.URL));
+ const hashUID =
+ results.length > 0
+ ? results[0].response.uid
+ : translator.fromUUID(uuidv5(JSON.stringify(evt), uuidv5.URL));
// TODO Should just be set to the true case as soon as we have a "bare email" integration class.
// UID generation should happen in the integration itself, not here.
- if(results.length === 0) {
+ if (results.length === 0) {
// Legacy as well, as soon as we have a separate email integration class. Just used
// to send an email even if there is no integration at all.
try {
const mail = new EventAttendeeMail(evt, hashUID);
await mail.sendEmail();
} catch (e) {
- console.error("Sending legacy event mail failed", e)
- res.status(500).json({message: "Booking failed"});
+ console.error("Sending legacy event mail failed", e);
+ res.status(500).json({ message: "Booking failed" });
return;
}
}
- let booking;
try {
- booking = await prisma.booking.create({
- data: {
- uid: hashUID,
- userId: currentUser.id,
- references: {
- create: referencesToCreate
- },
- eventTypeId: eventType.id,
+ await prisma.booking.create({
+ data: {
+ uid: hashUID,
+ userId: currentUser.id,
+ references: {
+ create: referencesToCreate,
+ },
+ eventTypeId: eventType.id,
title: evt.title,
description: evt.description,
@@ -244,13 +247,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
endTime: evt.endTime,
attendees: {
- create: evt.attendees
- }
- }
+ create: evt.attendees,
+ },
+ },
});
} catch (e) {
console.error("Error when saving booking to db", e);
- res.status(500).json({message: "Booking already exists"});
+ res.status(500).json({ message: "Booking already exists" });
return;
}