-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: EventManager
add cancelEvent
method
#14261
Changes from all commits
bad017a
e4f9679
2dc2272
12f228f
df6672e
5f274fc
4cd9236
dc45e8c
046a3a0
2f19c18
58eb841
4ce8a85
288915e
1777a3a
a9bb57f
698d41e
d40835e
bbc068f
747c71a
be2f2b1
11b6c7f
7f5fdb4
9cff361
1db94c8
9986bb4
db8f38e
f568c3e
b07473d
f068be4
bd65dad
8e12836
8f19358
4ba899a
666dcb3
db36cbf
8b1b206
9c2cb92
de1c55d
d9368a6
db7ef81
1870fcf
9d610ac
f0139fc
541eb8b
375c6a4
022df0f
1cd2955
95d333f
b35869e
30cac3f
a9c310f
cf7c15f
bbf4648
43cfd58
09a1c05
9ef46d7
b06d24d
32b119b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import type { DestinationCalendar } from "@prisma/client"; | ||
import type { DestinationCalendar, BookingReference } from "@prisma/client"; | ||
// eslint-disable-next-line no-restricted-imports | ||
import { cloneDeep, merge } from "lodash"; | ||
import { v5 as uuidv5 } from "uuid"; | ||
|
@@ -246,25 +246,36 @@ export default class EventManager { | |
} | ||
|
||
private async deleteCalendarEventForBookingReference({ | ||
bookingCalendarReference, | ||
reference, | ||
event, | ||
isBookingInRecurringSeries, | ||
}: { | ||
bookingCalendarReference: PartialReference; | ||
reference: PartialReference; | ||
event: CalendarEvent; | ||
isBookingInRecurringSeries?: boolean; | ||
}) { | ||
log.debug( | ||
"deleteCalendarEventForBookingReference", | ||
safeStringify({ bookingCalendarReference, event: getPiiFreeCalendarEvent(event) }) | ||
safeStringify({ bookingCalendarReference: reference, event: getPiiFreeCalendarEvent(event) }) | ||
); | ||
|
||
const { | ||
uid: bookingRefUid, | ||
// uid: bookingRefUid, | ||
externalCalendarId: bookingExternalCalendarId, | ||
credentialId, | ||
type: credentialType, | ||
} = bookingCalendarReference; | ||
} = reference; | ||
|
||
const calendarCredential = await this.getCredentialAndWarnIfNotFound(credentialId, credentialType); | ||
const bookingRefUid = | ||
isBookingInRecurringSeries && reference?.thirdPartyRecurringEventId | ||
? reference.thirdPartyRecurringEventId | ||
: reference.uid; | ||
|
||
const calendarCredential = await this.getCredentialAndWarnIfNotFound( | ||
credentialId, | ||
this.calendarCredentials, | ||
credentialType | ||
); | ||
if (calendarCredential) { | ||
await deleteEvent({ | ||
credential: calendarCredential, | ||
|
@@ -275,50 +286,56 @@ export default class EventManager { | |
} | ||
} | ||
|
||
private async deleteVideoEventForBookingReference({ | ||
bookingVideoReference, | ||
}: { | ||
bookingVideoReference: PartialReference; | ||
}) { | ||
log.debug("deleteVideoEventForBookingReference", safeStringify({ bookingVideoReference })); | ||
const { uid: bookingRefUid, credentialId } = bookingVideoReference; | ||
private async deleteVideoEventForBookingReference({ reference }: { reference: PartialReference }) { | ||
log.debug("deleteVideoEventForBookingReference", safeStringify({ bookingVideoReference: reference })); | ||
const { uid: bookingRefUid, credentialId } = reference; | ||
|
||
const videoCredential = await this.getCredentialAndWarnIfNotFound( | ||
credentialId, | ||
bookingVideoReference.type | ||
this.videoCredentials, | ||
reference.type | ||
); | ||
|
||
if (videoCredential) { | ||
await deleteMeeting(videoCredential, bookingRefUid); | ||
} | ||
} | ||
|
||
private async getCredentialAndWarnIfNotFound(credentialId: number | null | undefined, type: string) { | ||
const credential = | ||
typeof credentialId === "number" && credentialId > 0 | ||
? await prisma.credential.findUnique({ | ||
where: { | ||
id: credentialId, | ||
}, | ||
select: credentialForCalendarServiceSelect, | ||
private async getCredentialAndWarnIfNotFound( | ||
credentialId: number | null | undefined, | ||
credentials: CredentialPayload[], | ||
type: string | ||
) { | ||
const credential = credentials.find((cred) => cred.id === credentialId); | ||
if (credential) { | ||
return credential; | ||
} else { | ||
const credential = | ||
typeof credentialId === "number" && credentialId > 0 | ||
? await prisma.credential.findUnique({ | ||
where: { | ||
id: credentialId, | ||
}, | ||
select: credentialForCalendarServiceSelect, | ||
}) | ||
: // Fallback for zero or nullish credentialId which could be the case of Global App e.g. dailyVideo | ||
Comment on lines
+309
to
+320
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before this function would query the database for the credential. When we create a new instance of the |
||
this.videoCredentials.find((cred) => cred.type === type) || | ||
this.calendarCredentials.find((cred) => cred.type === type) || | ||
null; | ||
|
||
if (!credential) { | ||
log.error( | ||
"getCredentialAndWarnIfNotFound: Could not find credential", | ||
safeStringify({ | ||
credentialId, | ||
type, | ||
videoCredentials: this.videoCredentials, | ||
}) | ||
: // Fallback for zero or nullish credentialId which could be the case of Global App e.g. dailyVideo | ||
this.videoCredentials.find((cred) => cred.type === type) || | ||
this.calendarCredentials.find((cred) => cred.type === type) || | ||
null; | ||
|
||
if (!credential) { | ||
log.error( | ||
"getCredentialAndWarnIfNotFound: Could not find credential", | ||
safeStringify({ | ||
credentialId, | ||
type, | ||
videoCredentials: this.videoCredentials, | ||
}) | ||
); | ||
} | ||
); | ||
} | ||
|
||
return credential; | ||
return credential; | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -385,15 +402,15 @@ export default class EventManager { | |
log.debug("RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking"); | ||
// As the reschedule requires confirmation, we can't update the events and meetings to new time yet. So, just delete them and let it be handled when organizer confirms the booking. | ||
await this.deleteEventsAndMeetings({ | ||
booking, | ||
event: { ...event, destinationCalendar: previousHostDestinationCalendar }, | ||
bookingReferences: booking.references, | ||
}); | ||
} else { | ||
if (changedOrganizer) { | ||
log.debug("RescheduleOrganizerChanged: Deleting Event and Meeting for previous booking"); | ||
await this.deleteEventsAndMeetings({ | ||
booking, | ||
event: { ...event, destinationCalendar: previousHostDestinationCalendar }, | ||
bookingReferences: booking.references, | ||
}); | ||
|
||
log.debug("RescheduleOrganizerChanged: Creating Event and Meeting for for new booking"); | ||
|
@@ -451,30 +468,57 @@ export default class EventManager { | |
}; | ||
} | ||
|
||
public async cancelEvent( | ||
event: CalendarEvent, | ||
bookingReferences: Pick< | ||
BookingReference, | ||
"uid" | "type" | "externalCalendarId" | "credentialId" | "thirdPartyRecurringEventId" | ||
>[], | ||
isBookingInRecurringSeries?: boolean | ||
) { | ||
await this.deleteEventsAndMeetings({ | ||
event, | ||
bookingReferences, | ||
isBookingInRecurringSeries, | ||
}); | ||
} | ||
|
||
private async deleteEventsAndMeetings({ | ||
event, | ||
booking, | ||
bookingReferences, | ||
isBookingInRecurringSeries, | ||
}: { | ||
event: CalendarEvent; | ||
booking: PartialBooking; | ||
bookingReferences: PartialReference[]; | ||
isBookingInRecurringSeries?: boolean; | ||
}) { | ||
const calendarReferences = booking.references.filter((reference) => reference.type.includes("_calendar")); | ||
const videoReferences = booking.references.filter((reference) => reference.type.includes("_video")); | ||
log.debug("deleteEventsAndMeetings", safeStringify({ calendarReferences, videoReferences })); | ||
const calendarPromises = calendarReferences.map(async (bookingCalendarReference) => { | ||
return this.deleteCalendarEventForBookingReference({ | ||
bookingCalendarReference, | ||
event, | ||
}); | ||
}); | ||
const calendarReferences = [], | ||
Comment on lines
-461
to
-469
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're looping through the booking references two times. |
||
videoReferences = [], | ||
allPromises = []; | ||
|
||
for (const reference of bookingReferences) { | ||
if (reference.type.includes("_calendar")) { | ||
calendarReferences.push(reference); | ||
allPromises.push( | ||
this.deleteCalendarEventForBookingReference({ | ||
reference, | ||
Comment on lines
+499
to
+503
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's loop through the references once and call the appropriate method. |
||
event, | ||
isBookingInRecurringSeries, | ||
}) | ||
); | ||
} | ||
|
||
const videoPromises = videoReferences.map(async (bookingVideoReference) => { | ||
return this.deleteVideoEventForBookingReference({ | ||
bookingVideoReference, | ||
}); | ||
}); | ||
if (reference.type.includes("_video")) { | ||
videoReferences.push(reference); | ||
allPromises.push( | ||
this.deleteVideoEventForBookingReference({ | ||
reference, | ||
}) | ||
); | ||
} | ||
} | ||
|
||
const allPromises = [...calendarPromises, ...videoPromises]; | ||
log.debug("deleteEventsAndMeetings", safeStringify({ calendarReferences, videoReferences })); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still kept this for logging though. |
||
// Using allSettled to ensure that if one of the promises rejects, the others will still be executed. | ||
// Because we are just cleaning up the events and meetings, we don't want to throw an error if one of them fails. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
import type { Prisma } from "@prisma/client"; | ||
|
||
import type { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking"; | ||
import { UserRepository } from "@calcom/lib/server/repository/user"; | ||
import type { userSelect } from "@calcom/prisma"; | ||
import prisma from "@calcom/prisma"; | ||
|
@@ -14,13 +13,13 @@ type User = Prisma.UserGetPayload<typeof userSelect>; | |
* | ||
*/ | ||
export const getAllCredentials = async ( | ||
user: User & { credentials: CredentialPayload[] }, | ||
eventType: Awaited<ReturnType<typeof getEventTypesFromDB>> | ||
user: { id: number; username: string | null; credentials: CredentialPayload[] }, | ||
eventType: { team: { id: number | null } | null; parentId: number | null } | null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @zomars made some changes here. I think the types casted too broad of an umbrella. I narrowed it down to what's really needed. Thoughts? |
||
) => { | ||
const allCredentials = user.credentials; | ||
|
||
// If it's a team event type query for team credentials | ||
if (eventType.team?.id) { | ||
if (eventType?.team?.id) { | ||
const teamCredentialsQuery = await prisma.credential.findMany({ | ||
where: { | ||
teamId: eventType.team.id, | ||
|
@@ -31,7 +30,7 @@ export const getAllCredentials = async ( | |
} | ||
|
||
// If it's a managed event type, query for the parent team's credentials | ||
if (eventType.parentId) { | ||
if (eventType?.parentId) { | ||
const teamCredentialsQuery = await prisma.team.findFirst({ | ||
where: { | ||
eventTypes: { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check if we're deleting a booking in a recurring series. This change was made in
handleCancelBooking
but missing in theEventManager