2021-07-15 01:19:30 +00:00
|
|
|
import { Credential } from "@prisma/client";
|
|
|
|
import async from "async";
|
2021-10-13 11:35:25 +00:00
|
|
|
import merge from "lodash/merge";
|
2021-09-22 19:52:38 +00:00
|
|
|
import { v5 as uuidv5 } from "uuid";
|
|
|
|
|
2021-10-26 16:17:24 +00:00
|
|
|
import { AdditionInformation, CalendarEvent, createEvent, updateEvent } from "@lib/calendarClient";
|
2021-09-06 09:06:33 +00:00
|
|
|
import EventAttendeeMail from "@lib/emails/EventAttendeeMail";
|
2021-09-08 11:21:19 +00:00
|
|
|
import EventAttendeeRescheduledMail from "@lib/emails/EventAttendeeRescheduledMail";
|
2021-10-26 16:17:24 +00:00
|
|
|
import { DailyEventResult, FAKE_DAILY_CREDENTIAL } from "@lib/integrations/Daily/DailyVideoApiAdapter";
|
|
|
|
import { ZoomEventResult } from "@lib/integrations/Zoom/ZoomVideoApiAdapter";
|
2021-09-22 19:52:38 +00:00
|
|
|
import { LocationType } from "@lib/location";
|
|
|
|
import prisma from "@lib/prisma";
|
2021-10-26 16:17:24 +00:00
|
|
|
import { Ensure } from "@lib/types/utils";
|
2021-09-22 22:43:10 +00:00
|
|
|
import { createMeeting, updateMeeting, VideoCallData } from "@lib/videoClient";
|
2021-07-15 01:19:30 +00:00
|
|
|
|
2021-10-26 16:17:24 +00:00
|
|
|
export type Event = AdditionInformation & { name: string; id: string; disableConfirmationEmail?: boolean } & (
|
|
|
|
| ZoomEventResult
|
|
|
|
| DailyEventResult
|
|
|
|
);
|
|
|
|
|
2021-07-15 01:19:30 +00:00
|
|
|
export interface EventResult {
|
|
|
|
type: string;
|
|
|
|
success: boolean;
|
|
|
|
uid: string;
|
2021-10-26 16:17:24 +00:00
|
|
|
createdEvent?: Event;
|
|
|
|
updatedEvent?: Event;
|
2021-07-15 01:19:30 +00:00
|
|
|
originalEvent: CalendarEvent;
|
2021-09-22 22:43:10 +00:00
|
|
|
videoCallData?: VideoCallData;
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 20:24:00 +00:00
|
|
|
export interface CreateUpdateResult {
|
|
|
|
results: Array<EventResult>;
|
|
|
|
referencesToCreate: Array<PartialReference>;
|
|
|
|
}
|
|
|
|
|
2021-07-15 01:19:30 +00:00
|
|
|
export interface PartialBooking {
|
|
|
|
id: number;
|
|
|
|
references: Array<PartialReference>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface PartialReference {
|
2021-07-24 20:24:00 +00:00
|
|
|
id?: number;
|
2021-07-15 01:19:30 +00:00
|
|
|
type: string;
|
|
|
|
uid: string;
|
2021-10-25 13:05:21 +00:00
|
|
|
meetingId?: string | null;
|
|
|
|
meetingPassword?: string | null;
|
|
|
|
meetingUrl?: string | null;
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
|
|
|
|
2021-07-25 12:19:49 +00:00
|
|
|
interface GetLocationRequestFromIntegrationRequest {
|
|
|
|
location: string;
|
|
|
|
}
|
|
|
|
|
2021-07-15 01:19:30 +00:00
|
|
|
export default class EventManager {
|
|
|
|
calendarCredentials: Array<Credential>;
|
|
|
|
videoCredentials: Array<Credential>;
|
|
|
|
|
2021-07-24 20:30:14 +00:00
|
|
|
/**
|
|
|
|
* Takes an array of credentials and initializes a new instance of the EventManager.
|
|
|
|
*
|
|
|
|
* @param credentials
|
|
|
|
*/
|
2021-07-15 01:19:30 +00:00
|
|
|
constructor(credentials: Array<Credential>) {
|
|
|
|
this.calendarCredentials = credentials.filter((cred) => cred.type.endsWith("_calendar"));
|
|
|
|
this.videoCredentials = credentials.filter((cred) => cred.type.endsWith("_video"));
|
2021-10-07 16:12:39 +00:00
|
|
|
|
|
|
|
//for Daily.co video, temporarily pushes a credential for the daily-video-client
|
|
|
|
const hasDailyIntegration = process.env.DAILY_API_KEY;
|
|
|
|
if (hasDailyIntegration) {
|
2021-10-26 16:17:24 +00:00
|
|
|
this.videoCredentials.push(FAKE_DAILY_CREDENTIAL);
|
2021-10-07 16:12:39 +00:00
|
|
|
}
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 20:30:14 +00:00
|
|
|
/**
|
|
|
|
* Takes a CalendarEvent and creates all necessary integration entries for it.
|
|
|
|
* When a video integration is chosen as the event's location, a video integration
|
|
|
|
* event will be scheduled for it as well.
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
*/
|
2021-10-26 16:17:24 +00:00
|
|
|
public async create(event: Ensure<CalendarEvent, "language">): Promise<CreateUpdateResult> {
|
2021-11-09 16:27:33 +00:00
|
|
|
const evt = EventManager.processLocation(event);
|
2021-10-25 13:05:21 +00:00
|
|
|
const isDedicated = evt.location ? EventManager.isDedicatedIntegration(evt.location) : null;
|
2021-07-20 18:07:59 +00:00
|
|
|
|
2021-10-29 08:21:32 +00:00
|
|
|
// First, create all calendar events. If this is a dedicated integration event, don't send a mail right here.
|
|
|
|
const results: Array<EventResult> = await this.createAllCalendarEvents(evt, isDedicated);
|
2021-09-22 22:43:10 +00:00
|
|
|
// If and only if event type is a dedicated meeting, create a dedicated video meeting.
|
2021-08-01 21:29:15 +00:00
|
|
|
if (isDedicated) {
|
2021-10-25 13:05:21 +00:00
|
|
|
const result = await this.createVideoEvent(evt);
|
2021-09-22 22:43:10 +00:00
|
|
|
if (result.videoCallData) {
|
2021-11-09 16:27:33 +00:00
|
|
|
evt.videoCallData = result.videoCallData;
|
2021-09-22 22:43:10 +00:00
|
|
|
}
|
|
|
|
results.push(result);
|
2021-09-06 09:06:33 +00:00
|
|
|
} else {
|
2021-10-25 13:05:21 +00:00
|
|
|
await EventManager.sendAttendeeMail("new", results, evt);
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
|
|
|
|
2021-09-22 22:43:10 +00:00
|
|
|
const referencesToCreate: Array<PartialReference> = results.map((result: EventResult) => {
|
2021-10-25 13:05:21 +00:00
|
|
|
let uid = "";
|
2021-10-26 16:17:24 +00:00
|
|
|
if (result.createdEvent) {
|
|
|
|
const isDailyResult = result.type === "daily_video";
|
|
|
|
if (isDailyResult) {
|
|
|
|
uid = (result.createdEvent as DailyEventResult).name.toString();
|
|
|
|
} else {
|
|
|
|
uid = (result.createdEvent as ZoomEventResult).id.toString();
|
|
|
|
}
|
2021-10-07 16:12:39 +00:00
|
|
|
}
|
2021-10-25 13:05:21 +00:00
|
|
|
return {
|
|
|
|
type: result.type,
|
|
|
|
uid,
|
|
|
|
meetingId: result.videoCallData?.id.toString(),
|
|
|
|
meetingPassword: result.videoCallData?.password,
|
|
|
|
meetingUrl: result.videoCallData?.url,
|
|
|
|
};
|
2021-07-24 20:24:00 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
results,
|
|
|
|
referencesToCreate,
|
|
|
|
};
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 20:30:14 +00:00
|
|
|
/**
|
|
|
|
* Takes a calendarEvent and a rescheduleUid and updates the event that has the
|
|
|
|
* given uid using the data delivered in the given CalendarEvent.
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
*/
|
2021-11-09 16:27:33 +00:00
|
|
|
public async update(
|
|
|
|
event: Ensure<CalendarEvent, "language">,
|
|
|
|
rescheduleUid: string
|
|
|
|
): Promise<CreateUpdateResult> {
|
|
|
|
const evt = EventManager.processLocation(event);
|
|
|
|
|
|
|
|
if (!rescheduleUid) {
|
|
|
|
throw new Error("You called eventManager.update without an `rescheduleUid`. This should never happen.");
|
2021-10-25 13:05:21 +00:00
|
|
|
}
|
2021-07-25 12:19:49 +00:00
|
|
|
|
2021-07-24 20:24:00 +00:00
|
|
|
// Get details of existing booking.
|
|
|
|
const booking = await prisma.booking.findFirst({
|
|
|
|
where: {
|
2021-11-09 16:27:33 +00:00
|
|
|
uid: rescheduleUid,
|
2021-07-24 20:24:00 +00:00
|
|
|
},
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
references: {
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
type: true,
|
|
|
|
uid: true,
|
2021-09-22 22:43:10 +00:00
|
|
|
meetingId: true,
|
|
|
|
meetingPassword: true,
|
|
|
|
meetingUrl: true,
|
2021-07-24 20:24:00 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2021-10-25 13:05:21 +00:00
|
|
|
if (!booking) {
|
|
|
|
throw new Error("booking not found");
|
|
|
|
}
|
|
|
|
|
2021-10-26 16:17:24 +00:00
|
|
|
const isDedicated = evt.location ? EventManager.isDedicatedIntegration(evt.location) : null;
|
2021-10-29 08:21:32 +00:00
|
|
|
// First, create all calendar events. If this is a dedicated integration event, don't send a mail right here.
|
|
|
|
const results: Array<EventResult> = await this.updateAllCalendarEvents(evt, booking, isDedicated);
|
2021-09-22 22:43:10 +00:00
|
|
|
// If and only if event type is a dedicated meeting, update the dedicated video meeting.
|
2021-08-01 21:38:38 +00:00
|
|
|
if (isDedicated) {
|
2021-10-25 13:05:21 +00:00
|
|
|
const result = await this.updateVideoEvent(evt, booking);
|
2021-09-22 22:43:10 +00:00
|
|
|
if (result.videoCallData) {
|
2021-11-09 16:27:33 +00:00
|
|
|
evt.videoCallData = result.videoCallData;
|
2021-09-22 22:43:10 +00:00
|
|
|
}
|
|
|
|
results.push(result);
|
2021-09-08 11:21:19 +00:00
|
|
|
} else {
|
2021-10-25 13:05:21 +00:00
|
|
|
await EventManager.sendAttendeeMail("reschedule", results, evt);
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
2021-07-24 20:24:00 +00:00
|
|
|
// 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,
|
|
|
|
},
|
|
|
|
});
|
2021-10-25 13:05:21 +00:00
|
|
|
|
2021-11-09 16:27:33 +00:00
|
|
|
const bookingDeletes = prisma.booking.delete({
|
|
|
|
where: {
|
|
|
|
id: booking.id,
|
|
|
|
},
|
|
|
|
});
|
2021-07-24 20:24:00 +00:00
|
|
|
|
|
|
|
// Wait for all deletions to be applied.
|
|
|
|
await Promise.all([bookingReferenceDeletes, attendeeDeletes, bookingDeletes]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
results,
|
|
|
|
referencesToCreate: [...booking.references],
|
|
|
|
};
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates event entries for all calendar integrations given in the credentials.
|
2021-07-20 18:07:59 +00:00
|
|
|
* When noMail is true, no mails will be sent. This is used when the event is
|
|
|
|
* a video meeting because then the mail containing the video credentials will be
|
|
|
|
* more important than the mails created for these bare calendar events.
|
2021-07-15 01:19:30 +00:00
|
|
|
*
|
2021-07-25 15:05:18 +00:00
|
|
|
* When the optional uid is set, it will be used instead of the auto generated uid.
|
|
|
|
*
|
2021-07-15 01:19:30 +00:00
|
|
|
* @param event
|
2021-07-20 18:07:59 +00:00
|
|
|
* @param noMail
|
2021-07-15 01:19:30 +00:00
|
|
|
* @private
|
|
|
|
*/
|
2021-10-07 16:12:39 +00:00
|
|
|
|
2021-11-11 05:50:56 +00:00
|
|
|
private async createAllCalendarEvents(
|
|
|
|
event: CalendarEvent,
|
|
|
|
noMail: boolean | null
|
|
|
|
): Promise<Array<EventResult>> {
|
|
|
|
const [firstCalendar] = this.calendarCredentials;
|
|
|
|
if (!firstCalendar) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return [await createEvent(firstCalendar, event, noMail)];
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 20:30:14 +00:00
|
|
|
/**
|
|
|
|
* Checks which video integration is needed for the event's location and returns
|
|
|
|
* credentials for that - if existing.
|
|
|
|
* @param event
|
|
|
|
* @private
|
|
|
|
*/
|
2021-10-07 16:12:39 +00:00
|
|
|
|
2021-07-15 01:19:30 +00:00
|
|
|
private getVideoCredential(event: CalendarEvent): Credential | undefined {
|
2021-10-25 13:05:21 +00:00
|
|
|
if (!event.location) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-07-15 01:19:30 +00:00
|
|
|
const integrationName = event.location.replace("integrations:", "");
|
2021-10-07 16:12:39 +00:00
|
|
|
|
2021-07-15 01:19:30 +00:00
|
|
|
return this.videoCredentials.find((credential: Credential) => credential.type.includes(integrationName));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a video event entry for the selected integration location.
|
|
|
|
*
|
2021-07-25 15:05:18 +00:00
|
|
|
* When optional uid is set, it will be used instead of the auto generated uid.
|
|
|
|
*
|
2021-07-15 01:19:30 +00:00
|
|
|
* @param event
|
|
|
|
* @private
|
|
|
|
*/
|
2021-10-26 16:17:24 +00:00
|
|
|
private createVideoEvent(event: Ensure<CalendarEvent, "language">): Promise<EventResult> {
|
2021-07-15 01:19:30 +00:00
|
|
|
const credential = this.getVideoCredential(event);
|
|
|
|
|
2021-10-26 16:17:24 +00:00
|
|
|
if (credential) {
|
2021-10-25 13:05:21 +00:00
|
|
|
return createMeeting(credential, event);
|
2021-07-15 01:19:30 +00:00
|
|
|
} else {
|
|
|
|
return Promise.reject("No suitable credentials given for the requested integration name.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-20 18:07:59 +00:00
|
|
|
/**
|
|
|
|
* Updates the event entries for all calendar integrations given in the credentials.
|
|
|
|
* When noMail is true, no mails will be sent. This is used when the event is
|
|
|
|
* a video meeting because then the mail containing the video credentials will be
|
|
|
|
* more important than the mails created for these bare calendar events.
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
* @param booking
|
|
|
|
* @param noMail
|
|
|
|
* @private
|
|
|
|
*/
|
2021-07-15 01:19:30 +00:00
|
|
|
private updateAllCalendarEvents(
|
|
|
|
event: CalendarEvent,
|
2021-10-25 13:05:21 +00:00
|
|
|
booking: PartialBooking | null,
|
|
|
|
noMail: boolean | null
|
2021-07-15 01:19:30 +00:00
|
|
|
): Promise<Array<EventResult>> {
|
|
|
|
return async.mapLimit(this.calendarCredentials, 5, async (credential) => {
|
2021-10-25 13:05:21 +00:00
|
|
|
const bookingRefUid = booking
|
|
|
|
? booking.references.filter((ref) => ref.type === credential.type)[0]?.uid
|
|
|
|
: null;
|
2021-11-09 16:27:33 +00:00
|
|
|
return updateEvent(credential, event, noMail, bookingRefUid);
|
2021-07-15 01:19:30 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-20 18:07:59 +00:00
|
|
|
/**
|
|
|
|
* Updates a single video event.
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
* @param booking
|
|
|
|
* @private
|
|
|
|
*/
|
2021-07-15 01:19:30 +00:00
|
|
|
private updateVideoEvent(event: CalendarEvent, booking: PartialBooking) {
|
|
|
|
const credential = this.getVideoCredential(event);
|
|
|
|
|
2021-10-26 16:17:24 +00:00
|
|
|
if (credential) {
|
2021-10-25 13:05:21 +00:00
|
|
|
const bookingRef = booking ? booking.references.filter((ref) => ref.type === credential.type)[0] : null;
|
2021-11-09 16:27:33 +00:00
|
|
|
const bookingRefUid = bookingRef ? bookingRef.uid : null;
|
|
|
|
return updateMeeting(credential, event, bookingRefUid).then((returnVal: EventResult) => {
|
2021-09-22 22:43:10 +00:00
|
|
|
// Some video integrations, such as Zoom, don't return any data about the booking when updating it.
|
2021-11-09 16:27:33 +00:00
|
|
|
if (returnVal.videoCallData === undefined) {
|
2021-09-22 22:43:10 +00:00
|
|
|
returnVal.videoCallData = EventManager.bookingReferenceToVideoCallData(bookingRef);
|
|
|
|
}
|
|
|
|
return returnVal;
|
|
|
|
});
|
2021-07-15 01:19:30 +00:00
|
|
|
} else {
|
|
|
|
return Promise.reject("No suitable credentials given for the requested integration name.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-08-01 21:29:15 +00:00
|
|
|
* Returns true if the given location describes a dedicated integration that
|
|
|
|
* delivers meeting credentials. Zoom, for example, is dedicated, because it
|
|
|
|
* needs to be called independently from any calendar APIs to receive meeting
|
|
|
|
* credentials. Google Meetings, in contrast, are not dedicated, because they
|
|
|
|
* are created while scheduling a regular calendar event by simply adding some
|
|
|
|
* attributes to the payload JSON.
|
2021-07-15 01:19:30 +00:00
|
|
|
*
|
|
|
|
* @param location
|
|
|
|
* @private
|
|
|
|
*/
|
2021-08-01 21:29:15 +00:00
|
|
|
private static isDedicatedIntegration(location: string): boolean {
|
|
|
|
// Hard-coded for now, because Zoom and Google Meet are both integrations, but one is dedicated, the other one isn't.
|
2021-10-07 16:12:39 +00:00
|
|
|
|
2021-10-26 16:17:24 +00:00
|
|
|
return location === "integrations:zoom" || location === "integrations:daily";
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|
2021-07-25 12:19:49 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function for processLocation: Returns the conferenceData object to be merged
|
|
|
|
* with the CalendarEvent.
|
|
|
|
*
|
|
|
|
* @param locationObj
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private static getLocationRequestFromIntegration(locationObj: GetLocationRequestFromIntegrationRequest) {
|
|
|
|
const location = locationObj.location;
|
|
|
|
|
2021-10-07 16:12:39 +00:00
|
|
|
if (
|
|
|
|
location === LocationType.GoogleMeet.valueOf() ||
|
|
|
|
location === LocationType.Zoom.valueOf() ||
|
|
|
|
location === LocationType.Daily.valueOf()
|
|
|
|
) {
|
2021-07-25 12:19:49 +00:00
|
|
|
const requestId = uuidv5(location, uuidv5.URL);
|
|
|
|
|
|
|
|
return {
|
|
|
|
conferenceData: {
|
|
|
|
createRequest: {
|
|
|
|
requestId: requestId,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
location,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a CalendarEvent and adds a ConferenceData object to the event
|
|
|
|
* if the event has an integration-related location.
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
* @private
|
|
|
|
*/
|
2021-10-26 16:17:24 +00:00
|
|
|
private static processLocation<T extends CalendarEvent>(event: T): T {
|
2021-07-25 12:19:49 +00:00
|
|
|
// If location is set to an integration location
|
|
|
|
// Build proper transforms for evt object
|
|
|
|
// Extend evt object with those transformations
|
|
|
|
if (event.location?.includes("integration")) {
|
|
|
|
const maybeLocationRequestObject = EventManager.getLocationRequestFromIntegration({
|
|
|
|
location: event.location,
|
|
|
|
});
|
|
|
|
|
|
|
|
event = merge(event, maybeLocationRequestObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
return event;
|
|
|
|
}
|
2021-09-08 11:21:19 +00:00
|
|
|
|
2021-09-22 22:43:10 +00:00
|
|
|
/**
|
|
|
|
* Accepts a PartialReference object and, if all data is complete,
|
|
|
|
* returns a VideoCallData object containing the meeting information.
|
|
|
|
*
|
|
|
|
* @param reference
|
|
|
|
* @private
|
|
|
|
*/
|
2021-10-25 13:05:21 +00:00
|
|
|
private static bookingReferenceToVideoCallData(
|
|
|
|
reference: PartialReference | null
|
|
|
|
): VideoCallData | undefined {
|
2021-09-22 22:43:10 +00:00
|
|
|
let isComplete = true;
|
|
|
|
|
2021-10-25 13:05:21 +00:00
|
|
|
if (!reference) {
|
|
|
|
throw new Error("missing reference");
|
|
|
|
}
|
|
|
|
|
2021-09-22 22:43:10 +00:00
|
|
|
switch (reference.type) {
|
|
|
|
case "zoom_video":
|
|
|
|
// Zoom meetings in our system should always have an ID, a password and a join URL. In the
|
|
|
|
// future, it might happen that we consider making passwords for Zoom meetings optional.
|
|
|
|
// Then, this part below (where the password existence is checked) needs to be adapted.
|
|
|
|
isComplete =
|
2021-11-18 11:13:38 +00:00
|
|
|
reference.meetingId !== undefined &&
|
|
|
|
reference.meetingPassword !== undefined &&
|
|
|
|
reference.meetingUrl !== undefined;
|
2021-09-22 22:43:10 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
isComplete = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isComplete) {
|
|
|
|
return {
|
|
|
|
type: reference.type,
|
|
|
|
// The null coalescing operator should actually never be used here, because we checked if it's defined beforehand.
|
|
|
|
id: reference.meetingId ?? "",
|
|
|
|
password: reference.meetingPassword ?? "",
|
|
|
|
url: reference.meetingUrl ?? "",
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Conditionally sends an email to the attendee.
|
|
|
|
*
|
|
|
|
* @param type
|
|
|
|
* @param results
|
|
|
|
* @param event
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private static async sendAttendeeMail(
|
|
|
|
type: "new" | "reschedule",
|
|
|
|
results: Array<EventResult>,
|
2021-10-25 13:05:21 +00:00
|
|
|
event: CalendarEvent
|
2021-09-22 22:43:10 +00:00
|
|
|
) {
|
2021-09-08 11:21:19 +00:00
|
|
|
if (
|
|
|
|
!results.length ||
|
2021-10-25 13:05:21 +00:00
|
|
|
!results.some((eRes) => (eRes.createdEvent || eRes.updatedEvent)?.disableConfirmationEmail)
|
2021-09-08 11:21:19 +00:00
|
|
|
) {
|
2021-10-25 13:05:21 +00:00
|
|
|
const metadata: AdditionInformation = {};
|
2021-09-08 11:21:19 +00:00
|
|
|
if (results.length) {
|
|
|
|
// TODO: Handle created event metadata more elegantly
|
|
|
|
metadata.hangoutLink = results[0].createdEvent?.hangoutLink;
|
|
|
|
metadata.conferenceData = results[0].createdEvent?.conferenceData;
|
|
|
|
metadata.entryPoints = results[0].createdEvent?.entryPoints;
|
|
|
|
}
|
2021-11-09 16:27:33 +00:00
|
|
|
|
|
|
|
event.additionInformation = metadata;
|
2021-10-25 13:05:21 +00:00
|
|
|
|
2021-09-08 11:21:19 +00:00
|
|
|
let attendeeMail;
|
|
|
|
switch (type) {
|
|
|
|
case "reschedule":
|
2021-11-09 16:27:33 +00:00
|
|
|
attendeeMail = new EventAttendeeRescheduledMail(event);
|
2021-09-08 11:21:19 +00:00
|
|
|
break;
|
|
|
|
case "new":
|
2021-11-09 16:27:33 +00:00
|
|
|
attendeeMail = new EventAttendeeMail(event);
|
2021-09-08 11:21:19 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
await attendeeMail.sendEmail();
|
|
|
|
} catch (e) {
|
|
|
|
console.error("attendeeMail.sendEmail failed", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-15 01:19:30 +00:00
|
|
|
}
|