Created EventManager in order to unify event CRUD logic

pull/375/head
nicolas 2021-07-15 03:19:30 +02:00
parent 36b258f4b7
commit daecc1e0e4
4 changed files with 347 additions and 180 deletions

View File

@ -5,6 +5,10 @@ import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail"
import prisma from "./prisma";
import { Credential } from "@prisma/client";
import CalEventParser from "./CalEventParser";
import { EventResult } from "@lib/events/EventManager";
import logger from "@lib/logger";
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { google } = require("googleapis");
@ -494,9 +498,7 @@ const calendars = (withCredentials): CalendarApiAdapter[] =>
.filter(Boolean);
const getBusyCalendarTimes = (withCredentials, dateFrom, dateTo, selectedCalendars) =>
Promise.all(
calendars(withCredentials).map((c) => c.getAvailability(dateFrom, dateTo, selectedCalendars))
).then((results) => {
Promise.all(calendars(withCredentials).map((c) => c.getAvailability(selectedCalendars))).then((results) => {
return results.reduce((acc, availability) => acc.concat(availability), []);
});
@ -505,12 +507,21 @@ const listCalendars = (withCredentials) =>
results.reduce((acc, calendars) => acc.concat(calendars), [])
);
const createEvent = async (credential: Credential, calEvent: CalendarEvent): Promise<unknown> => {
const createEvent = async (credential: Credential, calEvent: CalendarEvent): Promise<EventResult> => {
const parser: CalEventParser = new CalEventParser(calEvent);
const uid: string = parser.getUid();
const richEvent: CalendarEvent = parser.asRichEvent();
const creationResult = credential ? await calendars([credential])[0].createEvent(richEvent) : null;
let success = true;
const creationResult = credential
? await calendars([credential])[0]
.createEvent(richEvent)
.catch((e) => {
log.error("createEvent failed", e, calEvent);
success = false;
})
: null;
const maybeHangoutLink = creationResult?.hangoutLink;
const maybeEntryPoints = creationResult?.entryPoints;
@ -543,8 +554,11 @@ const createEvent = async (credential: Credential, calEvent: CalendarEvent): Pro
}
return {
type: credential.type,
success,
uid,
createdEvent: creationResult,
originalEvent: calEvent,
};
};
@ -552,13 +566,20 @@ const updateEvent = async (
credential: Credential,
uidToUpdate: string,
calEvent: CalendarEvent
): Promise<unknown> => {
): Promise<EventResult> => {
const parser: CalEventParser = new CalEventParser(calEvent);
const newUid: string = parser.getUid();
const richEvent: CalendarEvent = parser.asRichEvent();
let success = true;
const updateResult = credential
? await calendars([credential])[0].updateEvent(uidToUpdate, richEvent)
? await calendars([credential])[0]
.updateEvent(uidToUpdate, richEvent)
.catch((e) => {
log.error("updateEvent failed", e, calEvent);
success = false;
})
: null;
const organizerMail = new EventOrganizerRescheduledMail(calEvent, newUid);
@ -578,8 +599,11 @@ const updateEvent = async (
}
return {
type: credential.type,
success,
uid: newUid,
updatedEvent: updateResult,
originalEvent: calEvent,
};
};

124
lib/events/EventManager.ts Normal file
View File

@ -0,0 +1,124 @@
import { CalendarEvent, createEvent, updateEvent } from "@lib/calendarClient";
import { Credential } from "@prisma/client";
import async from "async";
import { createMeeting, updateMeeting } from "@lib/videoClient";
export interface EventResult {
type: string;
success: boolean;
uid: string;
createdEvent?: unknown;
updatedEvent?: unknown;
originalEvent: CalendarEvent;
}
export interface PartialBooking {
id: number;
references: Array<PartialReference>;
}
export interface PartialReference {
id: number;
type: string;
uid: string;
}
export default class EventManager {
calendarCredentials: Array<Credential>;
videoCredentials: Array<Credential>;
constructor(credentials: Array<Credential>) {
this.calendarCredentials = credentials.filter((cred) => cred.type.endsWith("_calendar"));
this.videoCredentials = credentials.filter((cred) => cred.type.endsWith("_video"));
}
public async create(event: CalendarEvent): Promise<Array<EventResult>> {
const results: Array<EventResult> = [];
// First, create all calendar events.
results.concat(await this.createAllCalendarEvents(event));
// If and only if event type is a video meeting, create a video meeting as well.
if (EventManager.isIntegration(event.location)) {
results.push(await this.createVideoEvent(event));
}
return results;
}
public async update(event: CalendarEvent, booking: PartialBooking): Promise<Array<EventResult>> {
const results: Array<EventResult> = [];
// First, update all calendar events.
results.concat(await this.updateAllCalendarEvents(event, booking));
// If and only if event type is a video meeting, update the video meeting as well.
if (EventManager.isIntegration(event.location)) {
results.push(await this.updateVideoEvent(event, booking));
}
return results;
}
/**
* Creates event entries for all calendar integrations given in the credentials.
*
* @param event
* @private
*/
private createAllCalendarEvents(event: CalendarEvent): Promise<Array<EventResult>> {
return async.mapLimit(this.calendarCredentials, 5, async (credential: Credential) => {
return createEvent(credential, event);
});
}
private getVideoCredential(event: CalendarEvent): Credential | undefined {
const integrationName = event.location.replace("integrations:", "");
return this.videoCredentials.find((credential: Credential) => credential.type.includes(integrationName));
}
/**
* Creates a video event entry for the selected integration location.
*
* @param event
* @private
*/
private createVideoEvent(event: CalendarEvent): Promise<EventResult> {
const credential = this.getVideoCredential(event);
if (credential) {
return createMeeting(credential, event);
} else {
return Promise.reject("No suitable credentials given for the requested integration name.");
}
}
private updateAllCalendarEvents(
event: CalendarEvent,
booking: PartialBooking
): Promise<Array<EventResult>> {
return async.mapLimit(this.calendarCredentials, 5, async (credential) => {
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
return updateEvent(credential, bookingRefUid, event);
});
}
private updateVideoEvent(event: CalendarEvent, booking: PartialBooking) {
const credential = this.getVideoCredential(event);
if (credential) {
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
return updateMeeting(credential, bookingRefUid, event);
} else {
return Promise.reject("No suitable credentials given for the requested integration name.");
}
}
/**
* Returns true if the given location describes an integration that delivers meeting credentials.
*
* @param location
* @private
*/
private static isIntegration(location: string): boolean {
return location.includes("integrations:");
}
}

View File

@ -1,11 +1,15 @@
import prisma from "./prisma";
import {CalendarEvent} from "./calendarClient";
import { CalendarEvent } from "./calendarClient";
import VideoEventOrganizerMail from "./emails/VideoEventOrganizerMail";
import VideoEventAttendeeMail from "./emails/VideoEventAttendeeMail";
import {v5 as uuidv5} from 'uuid';
import short from 'short-uuid';
import { v5 as uuidv5 } from "uuid";
import short from "short-uuid";
import EventAttendeeRescheduledMail from "./emails/EventAttendeeRescheduledMail";
import EventOrganizerRescheduledMail from "./emails/EventOrganizerRescheduledMail";
import { EventResult } from "@lib/events/EventManager";
import logger from "@lib/logger";
const log = logger.getChildLogger({ prefix: ["[lib] videoClient"] });
const translator = short();
@ -33,63 +37,67 @@ function handleErrorsRaw(response) {
}
const zoomAuth = (credential) => {
const isExpired = (expiryDate) => expiryDate < +new Date();
const authHeader =
"Basic " +
Buffer.from(process.env.ZOOM_CLIENT_ID + ":" + process.env.ZOOM_CLIENT_SECRET).toString("base64");
const isExpired = (expiryDate) => expiryDate < +(new Date());
const authHeader = 'Basic ' + Buffer.from(process.env.ZOOM_CLIENT_ID + ':' + process.env.ZOOM_CLIENT_SECRET).toString('base64');
const refreshAccessToken = (refreshToken) => fetch('https://zoom.us/oauth/token', {
method: 'POST',
headers: {
'Authorization': authHeader,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'refresh_token': refreshToken,
'grant_type': 'refresh_token',
const refreshAccessToken = (refreshToken) =>
fetch("https://zoom.us/oauth/token", {
method: "POST",
headers: {
Authorization: authHeader,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
refresh_token: refreshToken,
grant_type: "refresh_token",
}),
})
})
.then(handleErrorsJson)
.then(async (responseBody) => {
// Store new tokens in database.
await prisma.credential.update({
where: {
id: credential.id
},
data: {
key: responseBody
}
.then(handleErrorsJson)
.then(async (responseBody) => {
// Store new tokens in database.
await prisma.credential.update({
where: {
id: credential.id,
},
data: {
key: responseBody,
},
});
credential.key.access_token = responseBody.access_token;
credential.key.expires_in = Math.round(+new Date() / 1000 + responseBody.expires_in);
return credential.key.access_token;
});
credential.key.access_token = responseBody.access_token;
credential.key.expires_in = Math.round((+(new Date()) / 1000) + responseBody.expires_in);
return credential.key.access_token;
})
return {
getToken: () => !isExpired(credential.key.expires_in) ? Promise.resolve(credential.key.access_token) : refreshAccessToken(credential.key.refresh_token)
getToken: () =>
!isExpired(credential.key.expires_in)
? Promise.resolve(credential.key.access_token)
: refreshAccessToken(credential.key.refresh_token),
};
};
interface VideoApiAdapter {
createMeeting(event: CalendarEvent): Promise<any>;
updateMeeting(uid: String, event: CalendarEvent);
updateMeeting(uid: string, event: CalendarEvent);
deleteMeeting(uid: String);
deleteMeeting(uid: string);
getAvailability(dateFrom, dateTo): Promise<any>;
}
const ZoomVideo = (credential): VideoApiAdapter => {
const auth = zoomAuth(credential);
const translateEvent = (event: CalendarEvent) => {
// Documentation at: https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
return {
topic: event.title,
type: 2, // Means that this is a scheduled meeting
type: 2, // Means that this is a scheduled meeting
start_time: event.startTime,
duration: ((new Date(event.endTime)).getTime() - (new Date(event.startTime)).getTime()) / 60000,
duration: (new Date(event.endTime).getTime() - new Date(event.startTime).getTime()) / 60000,
//schedule_for: "string", TODO: Used when scheduling the meeting for someone else (needed?)
timezone: event.attendees[0].timeZone,
//password: "string", TODO: Should we use a password? Maybe generate a random one?
@ -97,8 +105,8 @@ const ZoomVideo = (credential): VideoApiAdapter => {
settings: {
host_video: true,
participant_video: true,
cn_meeting: false, // TODO: true if host meeting in China
in_meeting: false, // TODO: true if host meeting in India
cn_meeting: false, // TODO: true if host meeting in China
in_meeting: false, // TODO: true if host meeting in India
join_before_host: true,
mute_upon_entry: false,
watermark: false,
@ -107,82 +115,107 @@ const ZoomVideo = (credential): VideoApiAdapter => {
audio: "both",
auto_recording: "none",
enforce_login: false,
registrants_email_notification: true
}
registrants_email_notification: true,
},
};
};
return {
getAvailability: (dateFrom, dateTo) => {
return auth.getToken().then(
// TODO Possibly implement pagination for cases when there are more than 300 meetings already scheduled.
(accessToken) => fetch('https://api.zoom.us/v2/users/me/meetings?type=scheduled&page_size=300', {
method: 'get',
headers: {
'Authorization': 'Bearer ' + accessToken
}
})
.then(handleErrorsJson)
.then(responseBody => {
return responseBody.meetings.map((meeting) => ({
start: meeting.start_time,
end: (new Date((new Date(meeting.start_time)).getTime() + meeting.duration * 60000)).toISOString()
}))
})
).catch((err) => {
console.log(err);
});
getAvailability: () => {
return auth
.getToken()
.then(
// TODO Possibly implement pagination for cases when there are more than 300 meetings already scheduled.
(accessToken) =>
fetch("https://api.zoom.us/v2/users/me/meetings?type=scheduled&page_size=300", {
method: "get",
headers: {
Authorization: "Bearer " + accessToken,
},
})
.then(handleErrorsJson)
.then((responseBody) => {
return responseBody.meetings.map((meeting) => ({
start: meeting.start_time,
end: new Date(
new Date(meeting.start_time).getTime() + meeting.duration * 60000
).toISOString(),
}));
})
)
.catch((err) => {
console.log(err);
});
},
createMeeting: (event: CalendarEvent) => auth.getToken().then(accessToken => fetch('https://api.zoom.us/v2/users/me/meetings', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(translateEvent(event))
}).then(handleErrorsJson)),
deleteMeeting: (uid: String) => auth.getToken().then(accessToken => fetch('https://api.zoom.us/v2/meetings/' + uid, {
method: 'DELETE',
headers: {
'Authorization': 'Bearer ' + accessToken
}
}).then(handleErrorsRaw)),
updateMeeting: (uid: String, event: CalendarEvent) => auth.getToken().then(accessToken => fetch('https://api.zoom.us/v2/meetings/' + uid, {
method: 'PATCH',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify(translateEvent(event))
}).then(handleErrorsRaw)),
}
createMeeting: (event: CalendarEvent) =>
auth.getToken().then((accessToken) =>
fetch("https://api.zoom.us/v2/users/me/meetings", {
method: "POST",
headers: {
Authorization: "Bearer " + accessToken,
"Content-Type": "application/json",
},
body: JSON.stringify(translateEvent(event)),
}).then(handleErrorsJson)
),
deleteMeeting: (uid: string) =>
auth.getToken().then((accessToken) =>
fetch("https://api.zoom.us/v2/meetings/" + uid, {
method: "DELETE",
headers: {
Authorization: "Bearer " + accessToken,
},
}).then(handleErrorsRaw)
),
updateMeeting: (uid: string, event: CalendarEvent) =>
auth.getToken().then((accessToken) =>
fetch("https://api.zoom.us/v2/meetings/" + uid, {
method: "PATCH",
headers: {
Authorization: "Bearer " + accessToken,
"Content-Type": "application/json",
},
body: JSON.stringify(translateEvent(event)),
}).then(handleErrorsRaw)
),
};
};
// factory
const videoIntegrations = (withCredentials): VideoApiAdapter[] => withCredentials.map((cred) => {
switch (cred.type) {
case 'zoom_video':
return ZoomVideo(cred);
default:
return; // unknown credential, could be legacy? In any case, ignore
}
}).filter(Boolean);
const videoIntegrations = (withCredentials): VideoApiAdapter[] =>
withCredentials
.map((cred) => {
switch (cred.type) {
case "zoom_video":
return ZoomVideo(cred);
default:
return; // unknown credential, could be legacy? In any case, ignore
}
})
.filter(Boolean);
const getBusyVideoTimes = (withCredentials) =>
Promise.all(videoIntegrations(withCredentials).map((c) => c.getAvailability())).then((results) =>
results.reduce((acc, availability) => acc.concat(availability), [])
);
const getBusyVideoTimes = (withCredentials, dateFrom, dateTo) => Promise.all(
videoIntegrations(withCredentials).map(c => c.getAvailability(dateFrom, dateTo))
).then(
(results) => results.reduce((acc, availability) => acc.concat(availability), [])
);
const createMeeting = async (credential, calEvent: CalendarEvent): Promise<any> => {
const createMeeting = async (credential, calEvent: CalendarEvent): Promise<EventResult> => {
const uid: 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.");
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."
);
}
const creationResult = await videoIntegrations([credential])[0].createMeeting(calEvent);
let success = true;
const creationResult = await videoIntegrations([credential])[0]
.createMeeting(calEvent)
.catch((e) => {
log.error("createMeeting failed", e, calEvent);
success = false;
});
const videoCallData: VideoCallData = {
type: credential.type,
@ -196,55 +229,76 @@ const createMeeting = async (credential, calEvent: CalendarEvent): Promise<any>
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);
}
}
return {
type: credential.type,
success,
uid,
createdEvent: creationResult
createdEvent: creationResult,
originalEvent: calEvent,
};
};
const updateMeeting = async (credential, uidToUpdate: String, calEvent: CalendarEvent): Promise<any> => {
const updateMeeting = async (
credential,
uidToUpdate: string,
calEvent: CalendarEvent
): Promise<EventResult> => {
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.");
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."
);
}
const updateResult = credential ? await videoIntegrations([credential])[0].updateMeeting(uidToUpdate, calEvent) : null;
let success = true;
const updateResult = credential
? await videoIntegrations([credential])[0]
.updateMeeting(uidToUpdate, calEvent)
.catch((e) => {
log.error("updateMeeting failed", e, calEvent);
success = false;
})
: null;
const organizerMail = new EventOrganizerRescheduledMail(calEvent, newUid);
const attendeeMail = new EventAttendeeRescheduledMail(calEvent, newUid);
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);
}
}
return {
type: credential.type,
success,
uid: newUid,
updatedEvent: updateResult
updatedEvent: updateResult,
originalEvent: calEvent,
};
};
const deleteMeeting = (credential, uid: String): Promise<any> => {
const deleteMeeting = (credential, uid: string): Promise<any> => {
if (credential) {
return videoIntegrations([credential])[0].deleteMeeting(uid);
}
@ -252,4 +306,4 @@ const deleteMeeting = (credential, uid: String): Promise<any> => {
return Promise.resolve({});
};
export {getBusyVideoTimes, createMeeting, updateMeeting, deleteMeeting};
export { getBusyVideoTimes, createMeeting, updateMeeting, deleteMeeting };

View File

@ -1,16 +1,17 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";
import { CalendarEvent, createEvent, getBusyCalendarTimes, updateEvent } from "../../../lib/calendarClient";
import async from "async";
import { CalendarEvent, getBusyCalendarTimes } from "@lib/calendarClient";
import { v5 as uuidv5 } from "uuid";
import short from "short-uuid";
import { createMeeting, getBusyVideoTimes, updateMeeting } from "../../../lib/videoClient";
import { getBusyVideoTimes } from "@lib/videoClient";
import EventAttendeeMail from "../../../lib/emails/EventAttendeeMail";
import { getEventName } from "../../../lib/event";
import { LocationType } from "../../../lib/location";
import { getEventName } from "@lib/event";
import { LocationType } from "@lib/location";
import merge from "lodash.merge";
import dayjs from "dayjs";
import logger from "../../../lib/logger";
import EventManager, { EventResult } from "@lib/events/EventManager";
import { User } from "@prisma/client";
const translator = short();
const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
@ -63,6 +64,18 @@ const getLocationRequestFromIntegration = ({ location }: GetLocationRequestFromI
requestId: requestId,
},
},
location,
};
} else if (location === LocationType.Zoom.valueOf()) {
const requestId = uuidv5(location, uuidv5.URL);
return {
conferenceData: {
createRequest: {
requestId: requestId,
},
},
location,
};
}
@ -88,7 +101,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json(error);
}
let currentUser = await prisma.user.findFirst({
let currentUser: User = await prisma.user.findFirst({
where: {
username: user,
},
@ -107,10 +120,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
// 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 =
@ -152,9 +161,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
name: true,
},
});
calendarCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar"));
videoCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_video"));
// Initialize EventManager with credentials
const eventManager = new EventManager(currentUser.credentials);
const rescheduleUid = req.body.rescheduleUid;
const selectedEventType = await prisma.eventType.findFirst({
@ -228,7 +237,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json(error);
}
let results = [];
let results: Array<EventResult> = [];
let referencesToCreate = [];
if (rescheduleUid) {
@ -249,30 +258,8 @@ 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 updateEvent(credential, bookingRefUid, evt)
.then((response) => ({ type: credential.type, success: true, response }))
.catch((e) => {
log.error("updateEvent failed", e, evt);
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) => {
log.error("updateMeeting failed", e, evt);
return { type: credential.type, success: false };
});
})
);
// Use EventManager to conditionally use all needed integrations.
results = await eventManager.update(evt, booking);
if (results.length > 0 && results.every((res) => !res.success)) {
const error = {
@ -306,28 +293,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
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 };
});
})
);
// Use EventManager to conditionally use all needed integrations.
const results: Array<EventResult> = await eventManager.create(evt);
if (results.length > 0 && results.every((res) => !res.success)) {
const error = {
@ -342,15 +309,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
referencesToCreate = results.map((result) => {
return {
type: result.type,
uid: result.response.createdEvent.id.toString(),
uid: result.createdEvent.id.toString(),
};
});
}
const hashUID =
results.length > 0
? results[0].response.uid
: translator.fromUUID(uuidv5(JSON.stringify(evt), uuidv5.URL));
results.length > 0 ? results[0].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) {