Store video data in event location; fixed several types
parent
6b4cea2f30
commit
6dff2c9510
|
@ -2,16 +2,20 @@ import { CalendarEvent } from "./calendarClient";
|
|||
import { v5 as uuidv5 } from "uuid";
|
||||
import short from "short-uuid";
|
||||
import { stripHtml } from "./emails/helpers";
|
||||
import { VideoCallData } from "@lib/videoClient";
|
||||
import { getIntegrationName } from "@lib/integrations";
|
||||
|
||||
const translator = short();
|
||||
|
||||
export default class CalEventParser {
|
||||
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.maybeUid = maybeUid;
|
||||
this.optionalVideoCallData = optionalVideoCallData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,16 +65,46 @@ export default class CalEventParser {
|
|||
<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 />
|
||||
` +
|
||||
(this.calEvent.location
|
||||
? `<strong>Location:</strong><br />${this.calEvent.location}<br />
|
||||
(this.getLocation()
|
||||
? `<strong>Location:</strong><br />${this.getLocation()}<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()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
|
|
|
@ -5,9 +5,10 @@ import { Credential } from "@prisma/client";
|
|||
import CalEventParser from "./CalEventParser";
|
||||
import { EventResult } from "@lib/events/EventManager";
|
||||
import logger from "@lib/logger";
|
||||
import { CalDavCalendar } from "./integrations/CalDav/CalDavCalendarAdapter";
|
||||
import { VideoCallData } from "@lib/videoClient";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
|
||||
import { CalDavCalendar } from "./integrations/CalDav/CalDavCalendarAdapter";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { google } = require("googleapis");
|
||||
|
@ -543,9 +544,10 @@ const createEvent = async (
|
|||
credential: Credential,
|
||||
calEvent: CalendarEvent,
|
||||
noMail = false,
|
||||
maybeUid: string = null
|
||||
maybeUid?: string,
|
||||
optionalVideoCallData?: VideoCallData
|
||||
): Promise<EventResult> => {
|
||||
const parser: CalEventParser = new CalEventParser(calEvent, maybeUid);
|
||||
const parser: CalEventParser = new CalEventParser(calEvent, maybeUid, optionalVideoCallData);
|
||||
const uid: string = parser.getUid();
|
||||
/*
|
||||
* Matching the credential type is a workaround because the office calendar simply strips away newlines (\n and \r).
|
||||
|
|
|
@ -36,7 +36,7 @@ export default abstract class EventMail {
|
|||
* @param uid
|
||||
* @param additionInformation
|
||||
*/
|
||||
constructor(calEvent: CalendarEvent, uid: string, additionInformation: AdditionInformation = null) {
|
||||
constructor(calEvent: CalendarEvent, uid: string, additionInformation?: AdditionInformation) {
|
||||
this.calEvent = calEvent;
|
||||
this.uid = uid;
|
||||
this.parser = new CalEventParser(calEvent, uid);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { CalendarEvent, createEvent, updateEvent } from "@lib/calendarClient";
|
||||
import { Credential } from "@prisma/client";
|
||||
import async from "async";
|
||||
import { createMeeting, updateMeeting } from "@lib/videoClient";
|
||||
import { createMeeting, updateMeeting, VideoCallData } from "@lib/videoClient";
|
||||
import prisma from "@lib/prisma";
|
||||
import { LocationType } from "@lib/location";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
@ -16,6 +16,7 @@ export interface EventResult {
|
|||
createdEvent?: unknown;
|
||||
updatedEvent?: unknown;
|
||||
originalEvent: CalendarEvent;
|
||||
videoCalldata?: VideoCallData;
|
||||
}
|
||||
|
||||
export interface CreateUpdateResult {
|
||||
|
@ -61,19 +62,28 @@ export default class EventManager {
|
|||
* @param event
|
||||
* @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);
|
||||
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.
|
||||
const results: Array<EventResult> = await this.createAllCalendarEvents(event, isDedicated, maybeUid);
|
||||
// If and only if event type is a dedicated meeting, create a dedicated video meeting as well.
|
||||
const results: Array<EventResult> = [];
|
||||
let optionalVideoCallData: VideoCallData | undefined = undefined;
|
||||
|
||||
// If and only if event type is a dedicated meeting, create a dedicated video meeting.
|
||||
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 {
|
||||
await this.sendAttendeeMail("new", results, event, maybeUid);
|
||||
await EventManager.sendAttendeeMail("new", results, event, maybeUid);
|
||||
}
|
||||
|
||||
// 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.concat(await this.createAllCalendarEvents(event, isDedicated, maybeUid, optionalVideoCallData));
|
||||
|
||||
const referencesToCreate: Array<PartialReference> = results.map((result) => {
|
||||
return {
|
||||
type: result.type,
|
||||
|
@ -123,7 +133,7 @@ export default class EventManager {
|
|||
if (isDedicated) {
|
||||
results.push(await this.updateVideoEvent(event, booking));
|
||||
} else {
|
||||
await this.sendAttendeeMail("reschedule", results, event, rescheduleUid);
|
||||
await EventManager.sendAttendeeMail("reschedule", results, event, rescheduleUid);
|
||||
}
|
||||
|
||||
// Now we can delete the old booking and its references.
|
||||
|
@ -163,15 +173,17 @@ export default class EventManager {
|
|||
* @param event
|
||||
* @param noMail
|
||||
* @param maybeUid
|
||||
* @param optionalVideoCallData
|
||||
* @private
|
||||
*/
|
||||
private createAllCalendarEvents(
|
||||
event: CalendarEvent,
|
||||
noMail: boolean,
|
||||
maybeUid: string = null
|
||||
maybeUid?: string,
|
||||
optionalVideoCallData?: VideoCallData
|
||||
): Promise<Array<EventResult>> {
|
||||
return async.mapLimit(this.calendarCredentials, 5, async (credential: Credential) => {
|
||||
return createEvent(credential, event, noMail, maybeUid);
|
||||
return createEvent(credential, event, noMail, maybeUid, optionalVideoCallData);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -195,7 +207,7 @@ export default class EventManager {
|
|||
* @param maybeUid
|
||||
* @private
|
||||
*/
|
||||
private createVideoEvent(event: CalendarEvent, maybeUid: string = null): Promise<EventResult> {
|
||||
private createVideoEvent(event: CalendarEvent, maybeUid?: string): Promise<EventResult> {
|
||||
const credential = this.getVideoCredential(event);
|
||||
|
||||
if (credential) {
|
||||
|
@ -309,7 +321,21 @@ export default class EventManager {
|
|||
return event;
|
||||
}
|
||||
|
||||
private async sendAttendeeMail(type: "new" | "reschedule", results, event, maybeUid) {
|
||||
/**
|
||||
* 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 (
|
||||
!results.length ||
|
||||
!results.some((eRes) => (eRes.createdEvent || eRes.updatedEvent).disableConfirmationEmail)
|
||||
|
|
|
@ -266,6 +266,7 @@ const createMeeting = async (
|
|||
uid,
|
||||
createdEvent: creationResult,
|
||||
originalEvent: calEvent,
|
||||
videoCalldata: videoCallData,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -68,13 +68,16 @@
|
|||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/async": "^3.2.7",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/lodash.merge": "^4.6.6",
|
||||
"@types/node": "^16.6.1",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/react": "^17.0.18",
|
||||
"@types/react-dates": "^21.8.3",
|
||||
"@types/react-select": "^4.0.17",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
||||
"@typescript-eslint/parser": "^4.29.2",
|
||||
"autoprefixer": "^10.3.1",
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -1309,6 +1309,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
|
||||
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":
|
||||
version "7.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702"
|
||||
|
@ -1386,7 +1391,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
|
||||
|
||||
"@types/lodash@^4.14.165":
|
||||
"@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@*", "@types/lodash@^4.14.165":
|
||||
version "4.14.172"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
||||
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
|
||||
|
@ -1492,6 +1504,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||
|
||||
"@types/uuid@^8.3.1":
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
|
||||
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "20.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
|
||||
|
|
Loading…
Reference in New Issue