Bugfix/include zoom location (#715)
* Store video data in event location; fixed several types * fixed malformed id * Insert Zoom data when updating as well * Add columns to store (video) meetings * Store meeting data * fixed type * Use stored videoCallData * Store location in field as well * Use meta field for booking reference * Introduced meta field in code * Revert "Introduced meta field in code" This reverts commit 535baccee3d87e3e793e84c4b91f8cad0e09063f. * Revert "Use meta field for booking reference" This reverts commit 174c252f672bcc3e461c8b3b975ac7541066d6a8. * Linting fixes Co-authored-by: nicolas <privat@nicolasjessen.de> Co-authored-by: Peer_Rich <peeroke@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
This commit is contained in:
parent
4f964533cf
commit
3764a9d462
9 changed files with 190 additions and 40 deletions
|
@ -1,18 +1,21 @@
|
||||||
import short from "short-uuid";
|
import short from "short-uuid";
|
||||||
import { v5 as uuidv5 } from "uuid";
|
import { v5 as uuidv5 } from "uuid";
|
||||||
|
|
||||||
import { CalendarEvent } from "./calendarClient";
|
import { CalendarEvent } from "./calendarClient";
|
||||||
import { stripHtml } from "./emails/helpers";
|
import { stripHtml } from "./emails/helpers";
|
||||||
|
import { VideoCallData } from "@lib/videoClient";
|
||||||
|
import { getIntegrationName } from "@lib/integrations";
|
||||||
|
|
||||||
const translator = short();
|
const translator = short();
|
||||||
|
|
||||||
export default class CalEventParser {
|
export default class CalEventParser {
|
||||||
protected calEvent: CalendarEvent;
|
protected calEvent: CalendarEvent;
|
||||||
protected maybeUid: string;
|
protected maybeUid?: string;
|
||||||
|
protected optionalVideoCallData?: VideoCallData;
|
||||||
|
|
||||||
constructor(calEvent: CalendarEvent, maybeUid: string = null) {
|
constructor(calEvent: CalendarEvent, maybeUid?: string, optionalVideoCallData?: VideoCallData) {
|
||||||
this.calEvent = calEvent;
|
this.calEvent = calEvent;
|
||||||
this.maybeUid = maybeUid;
|
this.maybeUid = maybeUid;
|
||||||
|
this.optionalVideoCallData = optionalVideoCallData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,16 +65,46 @@ export default class CalEventParser {
|
||||||
<strong>Event Type:</strong><br />${this.calEvent.type}<br />
|
<strong>Event Type:</strong><br />${this.calEvent.type}<br />
|
||||||
<strong>Invitee Email:</strong><br /><a href="mailto:${this.calEvent.attendees[0].email}">${this.calEvent.attendees[0].email}</a><br />
|
<strong>Invitee Email:</strong><br /><a href="mailto:${this.calEvent.attendees[0].email}">${this.calEvent.attendees[0].email}</a><br />
|
||||||
` +
|
` +
|
||||||
(this.calEvent.location
|
(this.getLocation()
|
||||||
? `<strong>Location:</strong><br />${this.calEvent.location}<br />
|
? `<strong>Location:</strong><br />${this.getLocation()}<br />
|
||||||
`
|
`
|
||||||
: "") +
|
: "") +
|
||||||
`<strong>Invitee Time Zone:</strong><br />${this.calEvent.attendees[0].timeZone}<br />
|
`<strong>Invitee Time Zone:</strong><br />${this.calEvent.attendees[0].timeZone}<br />
|
||||||
<strong>Additional notes:</strong><br />${this.calEvent.description}<br />` +
|
<strong>Additional notes:</strong><br />${this.getDescriptionText()}<br />` +
|
||||||
this.getChangeEventFooterHtml()
|
this.getChangeEventFooterHtml()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditionally returns the event's location. When VideoCallData is set,
|
||||||
|
* it returns the meeting url. Otherwise, the regular location is returned.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected getLocation(): string | undefined {
|
||||||
|
if (this.optionalVideoCallData) {
|
||||||
|
return this.optionalVideoCallData.url;
|
||||||
|
}
|
||||||
|
return this.calEvent.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the event's description text. If VideoCallData is set, it prepends
|
||||||
|
* some video call information before the text as well.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected getDescriptionText(): string | undefined {
|
||||||
|
if (this.optionalVideoCallData) {
|
||||||
|
return `
|
||||||
|
${getIntegrationName(this.optionalVideoCallData.type)} meeting
|
||||||
|
ID: ${this.optionalVideoCallData.id}
|
||||||
|
Password: ${this.optionalVideoCallData.password}
|
||||||
|
${this.calEvent.description}`;
|
||||||
|
}
|
||||||
|
return this.calEvent.description;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an extended description with all important information (as plain text).
|
* Returns an extended description with all important information (as plain text).
|
||||||
*
|
*
|
||||||
|
@ -87,6 +120,7 @@ export default class CalEventParser {
|
||||||
public asRichEvent(): CalendarEvent {
|
public asRichEvent(): CalendarEvent {
|
||||||
const eventCopy: CalendarEvent = { ...this.calEvent };
|
const eventCopy: CalendarEvent = { ...this.calEvent };
|
||||||
eventCopy.description = this.getRichDescriptionHtml();
|
eventCopy.description = this.getRichDescriptionHtml();
|
||||||
|
eventCopy.location = this.getLocation();
|
||||||
return eventCopy;
|
return eventCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +130,7 @@ export default class CalEventParser {
|
||||||
public asRichEventPlain(): CalendarEvent {
|
public asRichEventPlain(): CalendarEvent {
|
||||||
const eventCopy: CalendarEvent = { ...this.calEvent };
|
const eventCopy: CalendarEvent = { ...this.calEvent };
|
||||||
eventCopy.description = this.getRichDescription();
|
eventCopy.description = this.getRichDescription();
|
||||||
|
eventCopy.location = this.getLocation();
|
||||||
return eventCopy;
|
return eventCopy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import { Prisma, Credential } from "@prisma/client";
|
import { Prisma, Credential } from "@prisma/client";
|
||||||
|
|
||||||
import { EventResult } from "@lib/events/EventManager";
|
import { EventResult } from "@lib/events/EventManager";
|
||||||
import logger from "@lib/logger";
|
import logger from "@lib/logger";
|
||||||
|
|
||||||
import CalEventParser from "./CalEventParser";
|
import CalEventParser from "./CalEventParser";
|
||||||
import EventOrganizerMail from "./emails/EventOrganizerMail";
|
import EventOrganizerMail from "./emails/EventOrganizerMail";
|
||||||
import EventOrganizerRescheduledMail from "./emails/EventOrganizerRescheduledMail";
|
import EventOrganizerRescheduledMail from "./emails/EventOrganizerRescheduledMail";
|
||||||
import { AppleCalendar } from "./integrations/Apple/AppleCalendarAdapter";
|
import { AppleCalendar } from "./integrations/Apple/AppleCalendarAdapter";
|
||||||
import { CalDavCalendar } from "./integrations/CalDav/CalDavCalendarAdapter";
|
import { CalDavCalendar } from "./integrations/CalDav/CalDavCalendarAdapter";
|
||||||
import prisma from "./prisma";
|
import prisma from "./prisma";
|
||||||
|
import { VideoCallData } from "@lib/videoClient";
|
||||||
|
|
||||||
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
|
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
|
||||||
|
|
||||||
|
@ -554,9 +553,10 @@ const createEvent = async (
|
||||||
credential: Credential,
|
credential: Credential,
|
||||||
calEvent: CalendarEvent,
|
calEvent: CalendarEvent,
|
||||||
noMail = false,
|
noMail = false,
|
||||||
maybeUid: string = null
|
maybeUid?: string,
|
||||||
|
optionalVideoCallData?: VideoCallData
|
||||||
): Promise<EventResult> => {
|
): Promise<EventResult> => {
|
||||||
const parser: CalEventParser = new CalEventParser(calEvent, maybeUid);
|
const parser: CalEventParser = new CalEventParser(calEvent, maybeUid, optionalVideoCallData);
|
||||||
const uid: string = parser.getUid();
|
const uid: string = parser.getUid();
|
||||||
/*
|
/*
|
||||||
* Matching the credential type is a workaround because the office calendar simply strips away newlines (\n and \r).
|
* Matching the credential type is a workaround because the office calendar simply strips away newlines (\n and \r).
|
||||||
|
@ -607,9 +607,10 @@ const updateEvent = async (
|
||||||
credential: Credential,
|
credential: Credential,
|
||||||
uidToUpdate: string,
|
uidToUpdate: string,
|
||||||
calEvent: CalendarEvent,
|
calEvent: CalendarEvent,
|
||||||
noMail = false
|
noMail = false,
|
||||||
|
optionalVideoCallData?: VideoCallData
|
||||||
): Promise<EventResult> => {
|
): Promise<EventResult> => {
|
||||||
const parser: CalEventParser = new CalEventParser(calEvent);
|
const parser: CalEventParser = new CalEventParser(calEvent, undefined, optionalVideoCallData);
|
||||||
const newUid: string = parser.getUid();
|
const newUid: string = parser.getUid();
|
||||||
const richEvent: CalendarEvent = parser.asRichEventPlain();
|
const richEvent: CalendarEvent = parser.asRichEventPlain();
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default abstract class EventMail {
|
||||||
* @param uid
|
* @param uid
|
||||||
* @param additionInformation
|
* @param additionInformation
|
||||||
*/
|
*/
|
||||||
constructor(calEvent: CalendarEvent, uid: string, additionInformation: AdditionInformation = null) {
|
constructor(calEvent: CalendarEvent, uid: string, additionInformation?: AdditionInformation) {
|
||||||
this.calEvent = calEvent;
|
this.calEvent = calEvent;
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
this.parser = new CalEventParser(calEvent, uid);
|
this.parser = new CalEventParser(calEvent, uid);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import EventAttendeeMail from "@lib/emails/EventAttendeeMail";
|
||||||
import EventAttendeeRescheduledMail from "@lib/emails/EventAttendeeRescheduledMail";
|
import EventAttendeeRescheduledMail from "@lib/emails/EventAttendeeRescheduledMail";
|
||||||
import { LocationType } from "@lib/location";
|
import { LocationType } from "@lib/location";
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import { createMeeting, updateMeeting } from "@lib/videoClient";
|
import { createMeeting, updateMeeting, VideoCallData } from "@lib/videoClient";
|
||||||
|
|
||||||
export interface EventResult {
|
export interface EventResult {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -17,6 +17,7 @@ export interface EventResult {
|
||||||
createdEvent?: unknown;
|
createdEvent?: unknown;
|
||||||
updatedEvent?: unknown;
|
updatedEvent?: unknown;
|
||||||
originalEvent: CalendarEvent;
|
originalEvent: CalendarEvent;
|
||||||
|
videoCallData?: VideoCallData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateUpdateResult {
|
export interface CreateUpdateResult {
|
||||||
|
@ -33,6 +34,9 @@ export interface PartialReference {
|
||||||
id?: number;
|
id?: number;
|
||||||
type: string;
|
type: string;
|
||||||
uid: string;
|
uid: string;
|
||||||
|
meetingId?: string;
|
||||||
|
meetingPassword?: string;
|
||||||
|
meetingUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetLocationRequestFromIntegrationRequest {
|
interface GetLocationRequestFromIntegrationRequest {
|
||||||
|
@ -62,23 +66,37 @@ export default class EventManager {
|
||||||
* @param event
|
* @param event
|
||||||
* @param maybeUid
|
* @param maybeUid
|
||||||
*/
|
*/
|
||||||
public async create(event: CalendarEvent, maybeUid: string = null): Promise<CreateUpdateResult> {
|
public async create(event: CalendarEvent, maybeUid?: string): Promise<CreateUpdateResult> {
|
||||||
event = EventManager.processLocation(event);
|
event = EventManager.processLocation(event);
|
||||||
const isDedicated = EventManager.isDedicatedIntegration(event.location);
|
const isDedicated = EventManager.isDedicatedIntegration(event.location);
|
||||||
|
|
||||||
// First, create all calendar events. If this is a dedicated integration event, don't send a mail right here.
|
let results: Array<EventResult> = [];
|
||||||
const results: Array<EventResult> = await this.createAllCalendarEvents(event, isDedicated, maybeUid);
|
let optionalVideoCallData: VideoCallData | undefined = undefined;
|
||||||
// If and only if event type is a dedicated meeting, create a dedicated video meeting as well.
|
|
||||||
|
// If and only if event type is a dedicated meeting, create a dedicated video meeting.
|
||||||
if (isDedicated) {
|
if (isDedicated) {
|
||||||
results.push(await this.createVideoEvent(event, maybeUid));
|
const result = await this.createVideoEvent(event, maybeUid);
|
||||||
|
if (result.videoCallData) {
|
||||||
|
optionalVideoCallData = result.videoCallData;
|
||||||
|
}
|
||||||
|
results.push(result);
|
||||||
} else {
|
} else {
|
||||||
await this.sendAttendeeMail("new", results, event, maybeUid);
|
await EventManager.sendAttendeeMail("new", results, event, maybeUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const referencesToCreate: Array<PartialReference> = results.map((result) => {
|
// Now create all calendar events. If this is a dedicated integration event,
|
||||||
|
// don't send a mail right here, because it has already been sent.
|
||||||
|
results = results.concat(
|
||||||
|
await this.createAllCalendarEvents(event, isDedicated, maybeUid, optionalVideoCallData)
|
||||||
|
);
|
||||||
|
|
||||||
|
const referencesToCreate: Array<PartialReference> = results.map((result: EventResult) => {
|
||||||
return {
|
return {
|
||||||
type: result.type,
|
type: result.type,
|
||||||
uid: result.createdEvent.id.toString(),
|
uid: result.createdEvent.id.toString(),
|
||||||
|
meetingId: result.videoCallData?.id.toString(),
|
||||||
|
meetingPassword: result.videoCallData?.password,
|
||||||
|
meetingUrl: result.videoCallData?.url,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -110,6 +128,9 @@ export default class EventManager {
|
||||||
id: true,
|
id: true,
|
||||||
type: true,
|
type: true,
|
||||||
uid: true,
|
uid: true,
|
||||||
|
meetingId: true,
|
||||||
|
meetingPassword: true,
|
||||||
|
meetingUrl: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -117,16 +138,26 @@ export default class EventManager {
|
||||||
|
|
||||||
const isDedicated = EventManager.isDedicatedIntegration(event.location);
|
const isDedicated = EventManager.isDedicatedIntegration(event.location);
|
||||||
|
|
||||||
// First, update all calendar events. If this is a dedicated event, don't send a mail right here.
|
let results: Array<EventResult> = [];
|
||||||
const results: Array<EventResult> = await this.updateAllCalendarEvents(event, booking, isDedicated);
|
let optionalVideoCallData: VideoCallData | undefined = undefined;
|
||||||
|
|
||||||
// If and only if event type is a dedicated meeting, update the dedicated video meeting as well.
|
// If and only if event type is a dedicated meeting, update the dedicated video meeting.
|
||||||
if (isDedicated) {
|
if (isDedicated) {
|
||||||
results.push(await this.updateVideoEvent(event, booking));
|
const result = await this.updateVideoEvent(event, booking);
|
||||||
|
if (result.videoCallData) {
|
||||||
|
optionalVideoCallData = result.videoCallData;
|
||||||
|
}
|
||||||
|
results.push(result);
|
||||||
} else {
|
} else {
|
||||||
await this.sendAttendeeMail("reschedule", results, event, rescheduleUid);
|
await EventManager.sendAttendeeMail("reschedule", results, event, rescheduleUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now update all calendar events. If this is a dedicated integration event,
|
||||||
|
// don't send a mail right here, because it has already been sent.
|
||||||
|
results = results.concat(
|
||||||
|
await this.updateAllCalendarEvents(event, booking, isDedicated, optionalVideoCallData)
|
||||||
|
);
|
||||||
|
|
||||||
// Now we can delete the old booking and its references.
|
// Now we can delete the old booking and its references.
|
||||||
const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
|
const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
|
@ -164,15 +195,17 @@ export default class EventManager {
|
||||||
* @param event
|
* @param event
|
||||||
* @param noMail
|
* @param noMail
|
||||||
* @param maybeUid
|
* @param maybeUid
|
||||||
|
* @param optionalVideoCallData
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private createAllCalendarEvents(
|
private createAllCalendarEvents(
|
||||||
event: CalendarEvent,
|
event: CalendarEvent,
|
||||||
noMail: boolean,
|
noMail: boolean,
|
||||||
maybeUid: string = null
|
maybeUid?: string,
|
||||||
|
optionalVideoCallData?: VideoCallData
|
||||||
): Promise<Array<EventResult>> {
|
): Promise<Array<EventResult>> {
|
||||||
return async.mapLimit(this.calendarCredentials, 5, async (credential: Credential) => {
|
return async.mapLimit(this.calendarCredentials, 5, async (credential: Credential) => {
|
||||||
return createEvent(credential, event, noMail, maybeUid);
|
return createEvent(credential, event, noMail, maybeUid, optionalVideoCallData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +229,7 @@ export default class EventManager {
|
||||||
* @param maybeUid
|
* @param maybeUid
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private createVideoEvent(event: CalendarEvent, maybeUid: string = null): Promise<EventResult> {
|
private createVideoEvent(event: CalendarEvent, maybeUid?: string): Promise<EventResult> {
|
||||||
const credential = this.getVideoCredential(event);
|
const credential = this.getVideoCredential(event);
|
||||||
|
|
||||||
if (credential) {
|
if (credential) {
|
||||||
|
@ -220,11 +253,12 @@ export default class EventManager {
|
||||||
private updateAllCalendarEvents(
|
private updateAllCalendarEvents(
|
||||||
event: CalendarEvent,
|
event: CalendarEvent,
|
||||||
booking: PartialBooking,
|
booking: PartialBooking,
|
||||||
noMail: boolean
|
noMail: boolean,
|
||||||
|
optionalVideoCallData?: VideoCallData
|
||||||
): Promise<Array<EventResult>> {
|
): Promise<Array<EventResult>> {
|
||||||
return async.mapLimit(this.calendarCredentials, 5, async (credential) => {
|
return async.mapLimit(this.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, event, noMail);
|
return updateEvent(credential, bookingRefUid, event, noMail, optionalVideoCallData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,8 +273,15 @@ export default class EventManager {
|
||||||
const credential = this.getVideoCredential(event);
|
const credential = this.getVideoCredential(event);
|
||||||
|
|
||||||
if (credential) {
|
if (credential) {
|
||||||
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
|
const bookingRef = booking.references.filter((ref) => ref.type === credential.type)[0];
|
||||||
return updateMeeting(credential, bookingRefUid, event);
|
|
||||||
|
return updateMeeting(credential, bookingRef.uid, event).then((returnVal: EventResult) => {
|
||||||
|
// Some video integrations, such as Zoom, don't return any data about the booking when updating it.
|
||||||
|
if (returnVal.videoCallData == undefined) {
|
||||||
|
returnVal.videoCallData = EventManager.bookingReferenceToVideoCallData(bookingRef);
|
||||||
|
}
|
||||||
|
return returnVal;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject("No suitable credentials given for the requested integration name.");
|
return Promise.reject("No suitable credentials given for the requested integration name.");
|
||||||
}
|
}
|
||||||
|
@ -310,7 +351,58 @@ export default class EventManager {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendAttendeeMail(type: "new" | "reschedule", results, event, maybeUid) {
|
/**
|
||||||
|
* Accepts a PartialReference object and, if all data is complete,
|
||||||
|
* returns a VideoCallData object containing the meeting information.
|
||||||
|
*
|
||||||
|
* @param reference
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private static bookingReferenceToVideoCallData(reference: PartialReference): VideoCallData | undefined {
|
||||||
|
let isComplete = true;
|
||||||
|
|
||||||
|
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 =
|
||||||
|
reference.meetingId != undefined &&
|
||||||
|
reference.meetingPassword != undefined &&
|
||||||
|
reference.meetingUrl != undefined;
|
||||||
|
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
|
||||||
|
* @param maybeUid
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private static async sendAttendeeMail(
|
||||||
|
type: "new" | "reschedule",
|
||||||
|
results: Array<EventResult>,
|
||||||
|
event: CalendarEvent,
|
||||||
|
maybeUid?: string
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
!results.length ||
|
!results.length ||
|
||||||
!results.some((eRes) => (eRes.createdEvent || eRes.updatedEvent).disableConfirmationEmail)
|
!results.some((eRes) => (eRes.createdEvent || eRes.updatedEvent).disableConfirmationEmail)
|
||||||
|
|
|
@ -219,7 +219,7 @@ const getBusyVideoTimes: (withCredentials: Credential[]) => Promise<unknown[]> =
|
||||||
const createMeeting = async (
|
const createMeeting = async (
|
||||||
credential: Credential,
|
credential: Credential,
|
||||||
calEvent: CalendarEvent,
|
calEvent: CalendarEvent,
|
||||||
maybeUid: string = null
|
maybeUid?: string
|
||||||
): Promise<EventResult> => {
|
): Promise<EventResult> => {
|
||||||
const parser: CalEventParser = new CalEventParser(calEvent, maybeUid);
|
const parser: CalEventParser = new CalEventParser(calEvent, maybeUid);
|
||||||
const uid: string = parser.getUid();
|
const uid: string = parser.getUid();
|
||||||
|
@ -279,6 +279,7 @@ const createMeeting = async (
|
||||||
uid,
|
uid,
|
||||||
createdEvent: creationResult,
|
createdEvent: creationResult,
|
||||||
originalEvent: calEvent,
|
originalEvent: calEvent,
|
||||||
|
videoCallData: videoCallData,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -77,9 +77,11 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@trivago/prettier-plugin-sort-imports": "2.0.4",
|
"@trivago/prettier-plugin-sort-imports": "2.0.4",
|
||||||
|
"@types/async": "^3.2.7",
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"@types/jest": "^27.0.1",
|
"@types/jest": "^27.0.1",
|
||||||
"@types/lodash.debounce": "^4.0.6",
|
"@types/lodash.debounce": "^4.0.6",
|
||||||
|
"@types/lodash.merge": "^4.6.6",
|
||||||
"@types/node": "^16.6.1",
|
"@types/node": "^16.6.1",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/qrcode": "^1.4.1",
|
"@types/qrcode": "^1.4.1",
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "BookingReference" ADD COLUMN "meetingId" TEXT,
|
||||||
|
ADD COLUMN "meetingPassword" TEXT,
|
||||||
|
ADD COLUMN "meetingUrl" TEXT;
|
|
@ -135,11 +135,14 @@ model VerificationRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
model BookingReference {
|
model BookingReference {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
type String
|
type String
|
||||||
uid String
|
uid String
|
||||||
booking Booking? @relation(fields: [bookingId], references: [id])
|
meetingId String?
|
||||||
bookingId Int?
|
meetingPassword String?
|
||||||
|
meetingUrl String?
|
||||||
|
booking Booking? @relation(fields: [bookingId], references: [id])
|
||||||
|
bookingId Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Attendee {
|
model Attendee {
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -1450,6 +1450,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
|
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
|
||||||
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
|
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
|
||||||
|
|
||||||
|
"@types/async@^3.2.7":
|
||||||
|
version "3.2.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.7.tgz#f784478440d313941e7b12c2e4db53b0ed55637b"
|
||||||
|
integrity sha512-a+MBBfOTs3ShFMlbH9qsRVFkjIUunEtxrBT0gxRx1cntjKRg2WApuGmNYzHkwKaIhMi3SMbKktaD/rLObQMwIw==
|
||||||
|
|
||||||
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
|
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
|
||||||
version "7.1.16"
|
version "7.1.16"
|
||||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702"
|
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702"
|
||||||
|
@ -1535,6 +1540,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||||
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
|
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
|
||||||
|
|
||||||
|
"@types/lodash.merge@^4.6.6":
|
||||||
|
version "4.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash.merge/-/lodash.merge-4.6.6.tgz#b84b403c1d31bc42d51772d1cd5557fa008cd3d6"
|
||||||
|
integrity sha512-IB90krzMf7YpfgP3u/EvZEdXVvm4e3gJbUvh5ieuI+o+XqiNEt6fCzqNRaiLlPVScLI59RxIGZMQ3+Ko/DJ8vQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "*"
|
||||||
|
|
||||||
"@types/lodash.debounce@^4.0.6":
|
"@types/lodash.debounce@^4.0.6":
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60"
|
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60"
|
||||||
|
|
Loading…
Reference in a new issue