When rescheduling update specific calendar (#3375)

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
pull/3420/head
Joe Au-Yeung 2022-07-18 11:37:47 -04:00 committed by GitHub
parent a9ad9a6a39
commit 6fc3e17b47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 59 deletions

View File

@ -376,6 +376,7 @@ async function handler(req: NextApiRequest) {
select: {
id: true,
attendees: true,
userId: true,
references: {
select: {
type: true,

View File

@ -500,12 +500,13 @@ const loggedInViewerRouter = createProtectedRouter()
There are connected calendars, but no destination calendar
So create a default destination calendar with the first primary connected calendar
*/
const { integration = "", externalId = "" } = connectedCalendars[0].primary ?? {};
const { integration = "", externalId = "", credentialId } = connectedCalendars[0].primary ?? {};
user.destinationCalendar = await ctx.prisma.destinationCalendar.create({
data: {
userId: user.id,
integration,
externalId,
credentialId,
},
});
} else {
@ -546,7 +547,7 @@ const loggedInViewerRouter = createProtectedRouter()
}),
async resolve({ ctx, input }) {
const { user } = ctx;
const { integration, externalId, eventTypeId, bookingId } = input;
const { integration, externalId, eventTypeId } = input;
const calendarCredentials = getCalendarCredentials(user.credentials, user.id);
const connectedCalendars = await getConnectedCalendars(calendarCredentials, user.selectedCalendars);
const allCals = connectedCalendars.map((cal) => cal.calendars ?? []).flat();
@ -562,7 +563,6 @@ const loggedInViewerRouter = createProtectedRouter()
let where;
if (eventTypeId) where = { eventTypeId };
else if (bookingId) where = { bookingId };
else where = { userId: user.id };
await ctx.prisma.destinationCalendar.upsert({

View File

@ -8,7 +8,12 @@ import getApps from "@calcom/app-store/utils";
import prisma from "@calcom/prisma";
import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar";
import type { Event } from "@calcom/types/Event";
import type { CreateUpdateResult, EventResult, PartialBooking } from "@calcom/types/EventManager";
import type {
CreateUpdateResult,
EventResult,
PartialBooking,
PartialReference,
} from "@calcom/types/EventManager";
import { createEvent, updateEvent } from "./CalendarManager";
import { LocationType } from "./location";
@ -141,6 +146,7 @@ export default class EventManager {
meetingPassword: result.createdEvent?.password,
meetingUrl: result.createdEvent?.url,
externalCalendarId: evt.destinationCalendar?.externalId,
credentialId: evt.destinationCalendar?.credentialId,
};
});
@ -175,6 +181,7 @@ export default class EventManager {
},
select: {
id: true,
userId: true,
references: {
// NOTE: id field removed from select as we don't require for deletingMany
// but was giving error on recreate for reschedule, probably because promise.all() didn't finished
@ -185,6 +192,7 @@ export default class EventManager {
meetingPassword: true,
meetingUrl: true,
externalCalendarId: true,
credentialId: true,
},
},
destinationCalendar: true,
@ -381,36 +389,47 @@ export default class EventManager {
event: CalendarEvent,
booking: PartialBooking
): Promise<Array<EventResult<NewCalendarEventType>>> {
return async.mapLimit(this.calendarCredentials, 5, async (credential: Credential) => {
try {
// HACK:
// Right now if two calendars are connected and a booking is created it has two bookingReferences, one is having uid null and the other is having valid uid.
// I don't know why yet - But we should work on fixing that. But even after the fix as there can be multiple references in an existing booking the following ref.uid check would still be required
// We should ignore the one with uid null, the other one is valid.
// Also, we should store(if not already) that which is the calendarCredential for the valid bookingReference, instead of going through all credentials one by one
const [bookingRef] = booking.references
? booking.references.filter((ref) => ref.type === credential.type && !!ref.uid)
: [];
let calendarReference: PartialReference | undefined = undefined,
credential;
try {
// Bookings should only have one calendar reference
calendarReference = booking.references.filter((reference) => reference.type.includes("_calendar"))[0];
if (!bookingRef) throw new Error("bookingRef");
if (!calendarReference) throw new Error("bookingRef");
const { uid: bookingRefUid, externalCalendarId: bookingExternalCalendarId } = bookingRef;
const { uid: bookingRefUid, externalCalendarId: bookingExternalCalendarId } = calendarReference;
if (!bookingExternalCalendarId) throw new Error("externalCalendarId");
if (!bookingExternalCalendarId) throw new Error("externalCalendarId");
return updateEvent(credential, event, bookingRefUid, bookingExternalCalendarId);
} catch (error) {
let message = `Tried to 'updateAllCalendarEvents' but there was no '{thing}' for '${credential.type}', userId: '${credential.userId}', bookingId: '${booking.id}'`;
if (error instanceof Error) message = message.replace("{thing}", error.message);
console.error(message);
return Promise.resolve({
type: credential.type,
const result = [];
if (calendarReference.credentialId) {
credential = this.calendarCredentials.filter(
(credential) => credential.id === calendarReference?.credentialId
)[0];
result.push(updateEvent(credential, event, bookingRefUid, bookingExternalCalendarId));
} else {
const credentials = this.calendarCredentials.filter(
(credential) => credential.type === calendarReference?.type
);
for (const credential of credentials) {
result.push(updateEvent(credential, event, bookingRefUid, bookingExternalCalendarId));
}
}
return Promise.all(result);
} catch (error) {
let message = `Tried to 'updateAllCalendarEvents' but there was no '{thing}' for '${credential?.type}', userId: '${credential?.userId}', bookingId: '${booking?.id}'`;
if (error instanceof Error) message = message.replace("{thing}", error.message);
console.error(message);
return Promise.resolve([
{
type: calendarReference?.type || "calendar",
success: false,
uid: "",
originalEvent: event,
});
}
});
},
]);
}
}
/**

View File

@ -0,0 +1,23 @@
/*
Warnings:
- You are about to drop the column `bookingId` on the `DestinationCalendar` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "DestinationCalendar" DROP CONSTRAINT "DestinationCalendar_bookingId_fkey";
-- DropIndex
DROP INDEX "DestinationCalendar_bookingId_key";
-- AlterTable
ALTER TABLE "Booking" ADD COLUMN "destinationCalendarId" INTEGER;
-- AlterTable
ALTER TABLE "BookingReference" ADD COLUMN "credentialId" INTEGER;
-- AlterTable
ALTER TABLE "DestinationCalendar" DROP COLUMN "bookingId";
-- AddForeignKey
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_destinationCalendarId_fkey" FOREIGN KEY ("destinationCalendarId") REFERENCES "DestinationCalendar"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -111,8 +111,7 @@ model DestinationCalendar {
externalId String
user User? @relation(fields: [userId], references: [id])
userId Int? @unique
booking Booking? @relation(fields: [bookingId], references: [id])
bookingId Int? @unique
booking Booking[]
eventType EventType? @relation(fields: [eventTypeId], references: [id])
eventTypeId Int? @unique
credentialId Int?
@ -238,6 +237,7 @@ model BookingReference {
bookingId Int?
externalCalendarId String?
deleted Boolean?
credentialId Int?
}
model Attendee {
@ -266,36 +266,37 @@ model DailyEventReference {
}
model Booking {
id Int @id @default(autoincrement())
uid String @unique
user User? @relation(fields: [userId], references: [id])
userId Int?
references BookingReference[]
eventType EventType? @relation(fields: [eventTypeId], references: [id])
eventTypeId Int?
title String
description String?
customInputs Json?
startTime DateTime
endTime DateTime
attendees Attendee[]
location String?
dailyRef DailyEventReference?
createdAt DateTime @default(now())
updatedAt DateTime?
status BookingStatus @default(ACCEPTED)
paid Boolean @default(false)
payment Payment[]
destinationCalendar DestinationCalendar?
cancellationReason String?
rejectionReason String?
dynamicEventSlugRef String?
dynamicGroupSlugRef String?
rescheduled Boolean?
fromReschedule String?
recurringEventId String?
smsReminderNumber String?
workflowReminders WorkflowReminder[]
id Int @id @default(autoincrement())
uid String @unique
user User? @relation(fields: [userId], references: [id])
userId Int?
references BookingReference[]
eventType EventType? @relation(fields: [eventTypeId], references: [id])
eventTypeId Int?
title String
description String?
customInputs Json?
startTime DateTime
endTime DateTime
attendees Attendee[]
location String?
dailyRef DailyEventReference?
createdAt DateTime @default(now())
updatedAt DateTime?
status BookingStatus @default(ACCEPTED)
paid Boolean @default(false)
payment Payment[]
destinationCalendar DestinationCalendar? @relation(fields: [destinationCalendarId], references: [id])
destinationCalendarId Int?
cancellationReason String?
rejectionReason String?
dynamicEventSlugRef String?
dynamicGroupSlugRef String?
rescheduled Boolean?
fromReschedule String?
recurringEventId String?
smsReminderNumber String?
workflowReminders WorkflowReminder[]
}
model Schedule {

View File

@ -1,3 +1,5 @@
import { DestinationCalendar } from "@prisma/client";
import type { CalendarEvent } from "./Calendar";
import type { Event } from "./Event";
@ -9,6 +11,7 @@ export interface PartialReference {
meetingPassword?: string | null;
meetingUrl?: string | null;
externalCalendarId?: string | null;
credentialId?: number | null;
}
export interface EventResult<T> {
@ -27,5 +30,7 @@ export interface CreateUpdateResult {
export interface PartialBooking {
id: number;
userId: number | null;
references: Array<PartialReference>;
credentialId?: number;
}