diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index 472edeaf02..a46450133b 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -165,6 +165,7 @@ export default class GoogleCalendarService implements Calendar { type: "google_calendar", password: "", url: "", + iCalUID: event.data.iCalUID, }); } ); @@ -201,6 +202,9 @@ export default class GoogleCalendarService implements Calendar { id: String(event.organizer.id), organizer: true, responseStatus: "accepted", + email: event.destinationCalendar?.externalId + ? event.destinationCalendar.externalId + : event.organizer.email, }, // eslint-disable-next-line ...eventAttendees, @@ -268,6 +272,7 @@ export default class GoogleCalendarService implements Calendar { type: "google_calendar", password: "", url: "", + iCalUID: evt.data.iCalUID, }); } return resolve(evt?.data); diff --git a/packages/app-store/office365calendar/lib/CalendarService.ts b/packages/app-store/office365calendar/lib/CalendarService.ts index 7eadee15bc..21fd11a85e 100644 --- a/packages/app-store/office365calendar/lib/CalendarService.ts +++ b/packages/app-store/office365calendar/lib/CalendarService.ts @@ -73,7 +73,9 @@ export default class Office365CalendarService implements Calendar { body: JSON.stringify(this.translateEvent(event)), }); - return handleErrorsJson(response); + const responseJson = await handleErrorsJson(response); + + return { ...responseJson, iCalUID: responseJson.iCalUId }; } catch (error) { this.log.error(error); @@ -88,7 +90,9 @@ export default class Office365CalendarService implements Calendar { body: JSON.stringify(this.translateEvent(event)), }); - return handleErrorsRaw(response); + const responseJson = await handleErrorsJson(response); + + return { ...responseJson, iCalUID: responseJson.iCalUId }; } catch (error) { this.log.error(error); diff --git a/packages/core/CalendarManager.ts b/packages/core/CalendarManager.ts index 241c8239d0..4d5bf3103e 100644 --- a/packages/core/CalendarManager.ts +++ b/packages/core/CalendarManager.ts @@ -265,6 +265,7 @@ export const createEvent = async ( type: credential.type, success, uid, + iCalUID: creationResult?.iCalUID || undefined, createdEvent: creationResult, originalEvent: calEvent, calError, diff --git a/packages/core/EventManager.ts b/packages/core/EventManager.ts index 34345884a5..d8bc62d544 100644 --- a/packages/core/EventManager.ts +++ b/packages/core/EventManager.ts @@ -10,15 +10,12 @@ import { MeetLocationType } from "@calcom/app-store/locations"; import getApps from "@calcom/app-store/utils"; import prisma from "@calcom/prisma"; import { createdEventSchema } from "@calcom/prisma/zod-utils"; -import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar"; +import type { NewCalendarEventType } from "@calcom/types/Calendar"; +import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar"; import type { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential"; import type { Event } from "@calcom/types/Event"; -import type { - CreateUpdateResult, - EventResult, - PartialBooking, - PartialReference, -} from "@calcom/types/EventManager"; +import type { EventResult } from "@calcom/types/EventManager"; +import type { CreateUpdateResult, PartialBooking, PartialReference } from "@calcom/types/EventManager"; import { createEvent, updateEvent } from "./CalendarManager"; import { createMeeting, updateMeeting } from "./videoClient"; @@ -125,12 +122,24 @@ export default class EventManager { // Create the calendar event with the proper video call data results.push(...(await this.createAllCalendarEvents(clonedCalEvent))); + // Since the result can be a new calendar event or video event, we have to create a type guard + // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates + const isCalendarResult = ( + result: (typeof results)[number] + ): result is EventResult => { + return result.type.includes("_calendar"); + }; + const referencesToCreate = results.map((result) => { let createdEventObj: createdEventSchema | null = null; if (typeof result?.createdEvent === "string") { createdEventObj = createdEventSchema.parse(JSON.parse(result.createdEvent)); } + if (isCalendarResult(result)) { + evt.iCalUID = result.iCalUID || undefined; + } + return { type: result.type, uid: createdEventObj ? createdEventObj.id : result.createdEvent?.id?.toString() ?? "", diff --git a/packages/emails/templates/attendee-scheduled-email.ts b/packages/emails/templates/attendee-scheduled-email.ts index 46ed34cca6..e33f77fb55 100644 --- a/packages/emails/templates/attendee-scheduled-email.ts +++ b/packages/emails/templates/attendee-scheduled-email.ts @@ -37,21 +37,21 @@ export default class AttendeeScheduledEmail extends BaseEmail { // ics appends "RRULE:" already, so removing it from RRule generated string recurrenceRule = new RRule(this.calEvent.recurringEvent).toString().replace("RRULE:", ""); } - const partstat: ParticipationStatus = "NEEDS-ACTION"; + const partstat: ParticipationStatus = "ACCEPTED"; const role: ParticipationRole = "REQ-PARTICIPANT"; const icsEvent = createEvent({ + uid: this.calEvent.iCalUID || this.calEvent.uid!, start: dayjs(this.calEvent.startTime) .utc() .toArray() .slice(0, 6) .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, startInputType: "utc", - productId: "calendso/ics", + productId: "calcom/ics", title: this.calEvent.title, description: this.getTextBody(), duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") }, organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email }, - attendees: [ ...this.calEvent.attendees.map((attendee: Person) => ({ name: attendee.name, diff --git a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts index 97967572bf..295d435ead 100644 --- a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts +++ b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts @@ -47,7 +47,7 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche .slice(0, 6) .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, startInputType: "utc", - productId: "calendso/ics", + productId: "calcom/ics", title: this.t("ics_event_title", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, diff --git a/packages/emails/templates/organizer-requested-to-reschedule-email.ts b/packages/emails/templates/organizer-requested-to-reschedule-email.ts index 8293c7053a..7a601af696 100644 --- a/packages/emails/templates/organizer-requested-to-reschedule-email.ts +++ b/packages/emails/templates/organizer-requested-to-reschedule-email.ts @@ -54,7 +54,7 @@ export default class OrganizerRequestedToRescheduleEmail extends OrganizerSchedu .slice(0, 6) .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, startInputType: "utc", - productId: "calendso/ics", + productId: "calcom/ics", title: this.t("ics_event_title", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, diff --git a/packages/emails/templates/organizer-scheduled-email.ts b/packages/emails/templates/organizer-scheduled-email.ts index 02828a5992..c24f39e563 100644 --- a/packages/emails/templates/organizer-scheduled-email.ts +++ b/packages/emails/templates/organizer-scheduled-email.ts @@ -35,13 +35,14 @@ export default class OrganizerScheduledEmail extends BaseEmail { recurrenceRule = new RRule(this.calEvent.recurringEvent).toString().replace("RRULE:", ""); } const icsEvent = createEvent({ + uid: this.calEvent.iCalUID || this.calEvent.uid!, start: dayjs(this.calEvent.startTime) .utc() .toArray() .slice(0, 6) .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, startInputType: "utc", - productId: "calendso/ics", + productId: "calcom/ics", title: this.calEvent.title, description: this.getTextBody(), duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") }, diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index cb04e6f34d..a2d49967a1 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1057,6 +1057,10 @@ async function handler( const results = updateManager.results; + const calendarResult = results.find((result) => result.type.includes("_calendar")); + + evt.iCalUID = calendarResult.updatedEvent.iCalUID || undefined; + if (results.length > 0 && results.some((res) => !res.success)) { const error = { errorCode: "BookingReschedulingMeetingFailed", @@ -1183,7 +1187,15 @@ async function handler( const copyEvent = cloneDeep(evt); - await eventManager.reschedule(copyEvent, rescheduleUid, newTimeSlotBooking.id); + const updateManager = await eventManager.reschedule(copyEvent, rescheduleUid, newTimeSlotBooking.id); + + const results = updateManager.results; + + const calendarResult = results.find((result) => result.type.includes("_calendar")); + + evt.iCalUID = Array.isArray(calendarResult?.updatedEvent) + ? calendarResult?.updatedEvent[0]?.iCalUID + : calendarResult?.updatedEvent?.iCalUID || undefined; // TODO send reschedule emails to attendees of the old booking await sendRescheduledEmails({ @@ -1266,7 +1278,15 @@ async function handler( const copyEvent = cloneDeep(evt); - await eventManager.reschedule(copyEvent, rescheduleUid, newTimeSlotBooking.id); + const updateManager = await eventManager.reschedule(copyEvent, rescheduleUid, newTimeSlotBooking.id); + + const results = updateManager.results; + + const calendarResult = results.find((result) => result.type.includes("_calendar")); + + evt.iCalUID = Array.isArray(calendarResult?.updatedEvent) + ? calendarResult?.updatedEvent[0]?.iCalUID + : calendarResult?.updatedEvent?.iCalUID || undefined; await sendRescheduledSeatEmail(copyEvent, seatAttendee as Person); const filteredAttendees = originalRescheduledBooking?.attendees.filter((attendee) => { @@ -1575,7 +1595,7 @@ async function handler( return prisma.booking.create(createBookingObj); } - let results: EventResult[] = []; + let results: EventResult[] = []; let referencesToCreate: PartialReference[] = []; type Booking = Prisma.PromiseReturnType; @@ -1716,6 +1736,11 @@ async function handler( log.error(`Booking ${organizerUser.name} failed`, error, results); } else { const metadata: AdditionalInformation = {}; + const calendarResult = results.find((result) => result.type.includes("_calendar")); + + evt.iCalUID = Array.isArray(calendarResult?.updatedEvent) + ? calendarResult?.updatedEvent[0]?.iCalUID + : calendarResult?.updatedEvent?.iCalUID || undefined; if (results.length) { // TODO: Handle created event metadata more elegantly diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index 730e83817d..4a83a761fa 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -235,6 +235,7 @@ export const createdEventSchema = z id: z.string(), password: z.union([z.string(), z.undefined()]), onlineMeetingUrl: z.string().nullable(), + iCalUID: z.string().optional(), }) .passthrough(); diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index f97079faa8..85d372d5b0 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -61,6 +61,7 @@ export type NewCalendarEventType = { password: string; url: string; additionalInfo: AdditionalInfo; + iCalUID?: string | null; }; export type CalendarEventType = { @@ -172,6 +173,7 @@ export interface CalendarEvent { seatsShowAttendees?: boolean | null; attendeeSeatId?: string; seatsPerTimeSlot?: number | null; + iCalUID?: string | null; // It has responses to all the fields(system + user) responses?: CalEventResponses | null; diff --git a/packages/types/EventManager.d.ts b/packages/types/EventManager.d.ts index 97759dbcf5..1f60ba13f5 100644 --- a/packages/types/EventManager.d.ts +++ b/packages/types/EventManager.d.ts @@ -1,7 +1,4 @@ -import { DestinationCalendar } from "@prisma/client"; - import type { CalendarEvent } from "./Calendar"; -import type { Event } from "./Event"; export interface PartialReference { id?: number; @@ -19,6 +16,7 @@ export interface EventResult { appName: string; success: boolean; uid: string; + iCalUID?: string | null; createdEvent?: T; updatedEvent?: T | T[]; originalEvent: CalendarEvent;