parent
a9b45c1057
commit
5c4a9c32d1
4 changed files with 326 additions and 278 deletions
20
lib/logger.ts
Normal file
20
lib/logger.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { Logger } from "tslog";
|
||||||
|
|
||||||
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
const logger = new Logger({
|
||||||
|
dateTimePattern: "hour:minute:second.millisecond timeZoneName",
|
||||||
|
displayFunctionName: false,
|
||||||
|
displayFilePath: "hidden",
|
||||||
|
dateTimeTimezone: isProduction ? "utc" : Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
prettyInspectHighlightStyles: {
|
||||||
|
name: "yellow",
|
||||||
|
number: "blue",
|
||||||
|
bigint: "blue",
|
||||||
|
boolean: "blue",
|
||||||
|
},
|
||||||
|
maskValuesOfKeys: ["password", "passwordConfirmation", "credentials", "credential"],
|
||||||
|
exposeErrorCodeFrame: !isProduction,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default logger;
|
|
@ -36,6 +36,7 @@
|
||||||
"react-select": "^4.3.0",
|
"react-select": "^4.3.0",
|
||||||
"react-timezone-select": "^1.0.2",
|
"react-timezone-select": "^1.0.2",
|
||||||
"short-uuid": "^4.2.0",
|
"short-uuid": "^4.2.0",
|
||||||
|
"tslog": "^3.2.0",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -10,8 +10,10 @@ import { getEventName } from "../../../lib/event";
|
||||||
import { LocationType } from "../../../lib/location";
|
import { LocationType } from "../../../lib/location";
|
||||||
import merge from "lodash.merge";
|
import merge from "lodash.merge";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import logger from "../../../lib/logger";
|
||||||
|
|
||||||
const translator = short();
|
const translator = short();
|
||||||
|
const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
|
||||||
|
|
||||||
function isAvailable(busyTimes, time, length) {
|
function isAvailable(busyTimes, time, length) {
|
||||||
// Check for conflicts
|
// Check for conflicts
|
||||||
|
@ -69,304 +71,322 @@ const getLocationRequestFromIntegration = ({ location }: GetLocationRequestFromI
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
||||||
const { user } = req.query;
|
const { user } = req.query;
|
||||||
|
log.debug(`Booking ${user} started`);
|
||||||
const isTimeInPast = (time) => {
|
|
||||||
return dayjs(time).isBefore(new Date(), "day");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isTimeInPast(req.body.start)) {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.json({ errorCode: "BookingDateInPast", message: "Attempting to create a meeting in the past." });
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentUser = await prisma.user.findFirst({
|
|
||||||
where: {
|
|
||||||
username: user,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
credentials: true,
|
|
||||||
timeZone: true,
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedCalendars = await prisma.selectedCalendar.findMany({
|
|
||||||
where: {
|
|
||||||
userId: currentUser.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Split credentials up into calendar credentials and video credentials
|
|
||||||
let calendarCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar"));
|
|
||||||
let videoCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_video"));
|
|
||||||
|
|
||||||
const hasCalendarIntegrations =
|
|
||||||
currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar")).length > 0;
|
|
||||||
const hasVideoIntegrations =
|
|
||||||
currentUser.credentials.filter((cred) => cred.type.endsWith("_video")).length > 0;
|
|
||||||
|
|
||||||
const calendarAvailability = await getBusyCalendarTimes(
|
|
||||||
currentUser.credentials,
|
|
||||||
dayjs(req.body.start).startOf("day").utc().format(),
|
|
||||||
dayjs(req.body.end).endOf("day").utc().format(),
|
|
||||||
selectedCalendars
|
|
||||||
);
|
|
||||||
const videoAvailability = await getBusyVideoTimes(
|
|
||||||
currentUser.credentials,
|
|
||||||
dayjs(req.body.start).startOf("day").utc().format(),
|
|
||||||
dayjs(req.body.end).endOf("day").utc().format()
|
|
||||||
);
|
|
||||||
let commonAvailability = [];
|
|
||||||
|
|
||||||
if (hasCalendarIntegrations && hasVideoIntegrations) {
|
|
||||||
commonAvailability = calendarAvailability.filter((availability) =>
|
|
||||||
videoAvailability.includes(availability)
|
|
||||||
);
|
|
||||||
} else if (hasVideoIntegrations) {
|
|
||||||
commonAvailability = videoAvailability;
|
|
||||||
} else if (hasCalendarIntegrations) {
|
|
||||||
commonAvailability = calendarAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, get the newly stored credentials (new refresh token for example).
|
|
||||||
currentUser = await prisma.user.findFirst({
|
|
||||||
where: {
|
|
||||||
username: user,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
credentials: true,
|
|
||||||
timeZone: true,
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
calendarCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar"));
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
eventName: true,
|
|
||||||
title: true,
|
|
||||||
length: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const rawLocation = req.body.location;
|
|
||||||
|
|
||||||
let evt: CalendarEvent = {
|
|
||||||
type: selectedEventType.title,
|
|
||||||
title: getEventName(req.body.name, selectedEventType.title, selectedEventType.eventName),
|
|
||||||
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 }],
|
|
||||||
};
|
|
||||||
|
|
||||||
// If phone or inPerson use raw location
|
|
||||||
// set evt.location to req.body.location
|
|
||||||
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")) {
|
|
||||||
const maybeLocationRequestObject = getLocationRequestFromIntegration({
|
|
||||||
location: rawLocation,
|
|
||||||
});
|
|
||||||
|
|
||||||
evt = merge(evt, maybeLocationRequestObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventType = await prisma.eventType.findFirst({
|
|
||||||
where: {
|
|
||||||
userId: currentUser.id,
|
|
||||||
title: evt.type,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let isAvailableToBeBooked = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isAvailableToBeBooked = isAvailable(commonAvailability, req.body.start, selectedEventType.length);
|
const isTimeInPast = (time) => {
|
||||||
} catch {
|
return dayjs(time).isBefore(new Date(), "day");
|
||||||
console.debug({
|
};
|
||||||
message: "Unable set isAvailableToBeBooked. Using true. ",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAvailableToBeBooked) {
|
if (isTimeInPast(req.body.start)) {
|
||||||
return res.status(400).json({ message: `${currentUser.name} is unavailable at this time.` });
|
const error = {
|
||||||
}
|
errorCode: "BookingDateInPast",
|
||||||
|
message: "Attempting to create a meeting in the past.",
|
||||||
|
};
|
||||||
|
|
||||||
let results = [];
|
log.error(`Booking ${user} failed`, error);
|
||||||
let referencesToCreate = [];
|
return res.status(400).json(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (rescheduleUid) {
|
let currentUser = await prisma.user.findFirst({
|
||||||
// Reschedule event
|
|
||||||
const booking = await prisma.booking.findFirst({
|
|
||||||
where: {
|
where: {
|
||||||
uid: rescheduleUid,
|
username: user,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
references: {
|
credentials: true,
|
||||||
select: {
|
timeZone: true,
|
||||||
id: true,
|
email: true,
|
||||||
type: true,
|
name: true,
|
||||||
uid: true,
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCalendars = await prisma.selectedCalendar.findMany({
|
||||||
|
where: {
|
||||||
|
userId: currentUser.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Split credentials up into calendar credentials and video credentials
|
||||||
|
let calendarCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar"));
|
||||||
|
let videoCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_video"));
|
||||||
|
|
||||||
|
const hasCalendarIntegrations =
|
||||||
|
currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar")).length > 0;
|
||||||
|
const hasVideoIntegrations =
|
||||||
|
currentUser.credentials.filter((cred) => cred.type.endsWith("_video")).length > 0;
|
||||||
|
|
||||||
|
const calendarAvailability = await getBusyCalendarTimes(
|
||||||
|
currentUser.credentials,
|
||||||
|
dayjs(req.body.start).startOf("day").utc().format(),
|
||||||
|
dayjs(req.body.end).endOf("day").utc().format(),
|
||||||
|
selectedCalendars
|
||||||
|
);
|
||||||
|
const videoAvailability = await getBusyVideoTimes(
|
||||||
|
currentUser.credentials,
|
||||||
|
dayjs(req.body.start).startOf("day").utc().format(),
|
||||||
|
dayjs(req.body.end).endOf("day").utc().format()
|
||||||
|
);
|
||||||
|
let commonAvailability = [];
|
||||||
|
|
||||||
|
if (hasCalendarIntegrations && hasVideoIntegrations) {
|
||||||
|
commonAvailability = calendarAvailability.filter((availability) =>
|
||||||
|
videoAvailability.includes(availability)
|
||||||
|
);
|
||||||
|
} else if (hasVideoIntegrations) {
|
||||||
|
commonAvailability = videoAvailability;
|
||||||
|
} else if (hasCalendarIntegrations) {
|
||||||
|
commonAvailability = calendarAvailability;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, get the newly stored credentials (new refresh token for example).
|
||||||
|
currentUser = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
username: user,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
credentials: true,
|
||||||
|
timeZone: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
calendarCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar"));
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
eventName: true,
|
||||||
|
title: true,
|
||||||
|
length: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawLocation = req.body.location;
|
||||||
|
|
||||||
|
let evt: CalendarEvent = {
|
||||||
|
type: selectedEventType.title,
|
||||||
|
title: getEventName(req.body.name, selectedEventType.title, selectedEventType.eventName),
|
||||||
|
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 }],
|
||||||
|
};
|
||||||
|
|
||||||
|
// If phone or inPerson use raw location
|
||||||
|
// set evt.location to req.body.location
|
||||||
|
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")) {
|
||||||
|
const maybeLocationRequestObject = getLocationRequestFromIntegration({
|
||||||
|
location: rawLocation,
|
||||||
|
});
|
||||||
|
|
||||||
|
evt = merge(evt, maybeLocationRequestObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventType = await prisma.eventType.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: currentUser.id,
|
||||||
|
title: evt.type,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAvailableToBeBooked = true;
|
||||||
|
|
||||||
|
if (!isAvailableToBeBooked) {
|
||||||
|
const error = {
|
||||||
|
errorCode: "BookingUserUnAvailable",
|
||||||
|
message: `${currentUser.name} is unavailable at this time.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
log.debug(`Booking ${user} failed`, error);
|
||||||
|
return res.status(400).json(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = [];
|
||||||
|
let referencesToCreate = [];
|
||||||
|
|
||||||
|
if (rescheduleUid) {
|
||||||
|
// Reschedule event
|
||||||
|
const booking = await prisma.booking.findFirst({
|
||||||
|
where: {
|
||||||
|
uid: rescheduleUid,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
references: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
type: true,
|
||||||
|
uid: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Use all integrations
|
// Use all integrations
|
||||||
results = results.concat(
|
results = results.concat(
|
||||||
await async.mapLimit(calendarCredentials, 5, async (credential) => {
|
await async.mapLimit(calendarCredentials, 5, async (credential) => {
|
||||||
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
|
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
|
||||||
return updateEvent(credential, bookingRefUid, evt)
|
return updateEvent(credential, bookingRefUid, evt)
|
||||||
.then((response) => ({ type: credential.type, success: true, response }))
|
.then((response) => ({ type: credential.type, success: true, response }))
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error("updateEvent failed", e);
|
log.error("updateEvent failed", e, evt);
|
||||||
return { type: credential.type, success: false };
|
return { type: credential.type, success: false };
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
results = results.concat(
|
results = results.concat(
|
||||||
await async.mapLimit(videoCredentials, 5, async (credential) => {
|
await async.mapLimit(videoCredentials, 5, async (credential) => {
|
||||||
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
|
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
|
||||||
return updateMeeting(credential, bookingRefUid, evt)
|
return updateMeeting(credential, bookingRefUid, evt)
|
||||||
.then((response) => ({ type: credential.type, success: true, response }))
|
.then((response) => ({ type: credential.type, success: true, response }))
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error("updateMeeting failed", e);
|
log.error("updateMeeting failed", e, evt);
|
||||||
return { type: credential.type, success: false };
|
return { type: credential.type, success: false };
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (results.length > 0 && results.every((res) => !res.success)) {
|
if (results.length > 0 && results.every((res) => !res.success)) {
|
||||||
res.status(500).json({ message: "Rescheduling failed" });
|
const error = {
|
||||||
return;
|
errorCode: "BookingReschedulingMeetingFailed",
|
||||||
|
message: "Booking Rescheduling failed",
|
||||||
|
};
|
||||||
|
|
||||||
|
log.error(`Booking ${user} failed`, error, results);
|
||||||
|
return res.status(500).json(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone elements
|
||||||
|
referencesToCreate = [...booking.references];
|
||||||
|
|
||||||
|
// Now we can delete the old booking and its references.
|
||||||
|
const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
|
||||||
|
where: {
|
||||||
|
bookingId: booking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attendeeDeletes = prisma.attendee.deleteMany({
|
||||||
|
where: {
|
||||||
|
bookingId: booking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const bookingDeletes = prisma.booking.delete({
|
||||||
|
where: {
|
||||||
|
uid: rescheduleUid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
log.error("createEvent failed", e, evt);
|
||||||
|
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) => {
|
||||||
|
log.error("createMeeting failed", e, evt);
|
||||||
|
return { type: credential.type, success: false };
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (results.length > 0 && results.every((res) => !res.success)) {
|
||||||
|
const error = {
|
||||||
|
errorCode: "BookingCreatingMeetingFailed",
|
||||||
|
message: "Booking failed",
|
||||||
|
};
|
||||||
|
|
||||||
|
log.error(`Booking ${user} failed`, error, results);
|
||||||
|
return res.status(500).json(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
referencesToCreate = results.map((result) => {
|
||||||
|
return {
|
||||||
|
type: result.type,
|
||||||
|
uid: result.response.createdEvent.id.toString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone elements
|
const hashUID =
|
||||||
referencesToCreate = [...booking.references];
|
results.length > 0
|
||||||
|
? results[0].response.uid
|
||||||
// Now we can delete the old booking and its references.
|
: translator.fromUUID(uuidv5(JSON.stringify(evt), uuidv5.URL));
|
||||||
const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
|
// TODO Should just be set to the true case as soon as we have a "bare email" integration class.
|
||||||
where: {
|
// UID generation should happen in the integration itself, not here.
|
||||||
bookingId: booking.id,
|
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.
|
||||||
const attendeeDeletes = prisma.attendee.deleteMany({
|
try {
|
||||||
where: {
|
const mail = new EventAttendeeMail(evt, hashUID);
|
||||||
bookingId: booking.id,
|
await mail.sendEmail();
|
||||||
},
|
} catch (e) {
|
||||||
});
|
log.error("Sending legacy event mail failed", e);
|
||||||
const bookingDeletes = prisma.booking.delete({
|
log.error(`Booking ${user} failed`);
|
||||||
where: {
|
res.status(500).json({ message: "Booking failed" });
|
||||||
uid: rescheduleUid,
|
return;
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
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(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" });
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
referencesToCreate = results.map((result) => {
|
|
||||||
return {
|
|
||||||
type: result.type,
|
|
||||||
uid: result.response.createdEvent.id.toString(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// 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 {
|
try {
|
||||||
const mail = new EventAttendeeMail(evt, hashUID);
|
await prisma.booking.create({
|
||||||
await mail.sendEmail();
|
data: {
|
||||||
|
uid: hashUID,
|
||||||
|
userId: currentUser.id,
|
||||||
|
references: {
|
||||||
|
create: referencesToCreate,
|
||||||
|
},
|
||||||
|
eventTypeId: eventType.id,
|
||||||
|
title: evt.title,
|
||||||
|
description: evt.description,
|
||||||
|
startTime: evt.startTime,
|
||||||
|
endTime: evt.endTime,
|
||||||
|
attendees: {
|
||||||
|
create: evt.attendees,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Sending legacy event mail failed", e);
|
log.error(`Booking ${user} failed`, "Error when saving booking to db", e);
|
||||||
res.status(500).json({ message: "Booking failed" });
|
res.status(500).json({ message: "Booking already exists" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug(`Booking ${user} completed`);
|
||||||
|
return res.status(204).json({ message: "Booking completed" });
|
||||||
|
} catch (reason) {
|
||||||
|
log.error(`Booking ${user} failed`, reason);
|
||||||
|
return res.status(500).json({ message: "Booking failed for some unknown reason" });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await prisma.booking.create({
|
|
||||||
data: {
|
|
||||||
uid: hashUID,
|
|
||||||
userId: currentUser.id,
|
|
||||||
references: {
|
|
||||||
create: referencesToCreate,
|
|
||||||
},
|
|
||||||
eventTypeId: eventType.id,
|
|
||||||
|
|
||||||
title: evt.title,
|
|
||||||
description: evt.description,
|
|
||||||
startTime: evt.startTime,
|
|
||||||
endTime: evt.endTime,
|
|
||||||
|
|
||||||
attendees: {
|
|
||||||
create: evt.attendees,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error when saving booking to db", e);
|
|
||||||
res.status(500).json({ message: "Booking already exists" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(204).json({});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5402,7 +5402,7 @@ source-map-js@^0.6.2:
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
|
||||||
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
||||||
|
|
||||||
source-map-support@^0.5.6:
|
source-map-support@^0.5.19, source-map-support@^0.5.6:
|
||||||
version "0.5.19"
|
version "0.5.19"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||||
|
@ -5873,6 +5873,13 @@ tslib@^2.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
||||||
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
||||||
|
|
||||||
|
tslog@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslog/-/tslog-3.2.0.tgz#4982c289a8948670d6a1c49c29977ae9f861adfa"
|
||||||
|
integrity sha512-xOCghepl5w+wcI4qXI7vJy6c53loF8OoC/EuKz1ktAPMtltEDz00yo1poKuyBYIQaq4ZDYKYFPD9PfqVrFXh0A==
|
||||||
|
dependencies:
|
||||||
|
source-map-support "^0.5.19"
|
||||||
|
|
||||||
tsutils@^3.21.0:
|
tsutils@^3.21.0:
|
||||||
version "3.21.0"
|
version "3.21.0"
|
||||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||||
|
|
Loading…
Reference in a new issue