From c2a57fd72be77f4c554cf071a81144541b21ab7e Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:29:08 -0400 Subject: [PATCH] split date ranges for calling /freebusy endpoint (#11962) Co-authored-by: CarinaWolli --- .../googlecalendar/lib/CalendarService.ts | 153 +++++++++++------- 1 file changed, 97 insertions(+), 56 deletions(-) diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index fa726e6047..f3af3a9cff 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -4,6 +4,7 @@ import type { calendar_v3 } from "googleapis"; import { google } from "googleapis"; import { MeetLocationType } from "@calcom/app-store/locations"; +import dayjs from "@calcom/dayjs"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; import type CalendarService from "@calcom/lib/CalendarService"; @@ -369,57 +370,75 @@ export default class GoogleCalendarService implements Calendar { timeMin: string; timeMax: string; items: { id: string }[]; - }): Promise { + }): Promise { const calendar = await this.authedCalendar(); const flags = await getFeatureFlagMap(prisma); + + let freeBusyResult: calendar_v3.Schema$FreeBusyResponse = {}; if (!flags["calendar-cache"]) { this.log.warn("Calendar Cache is disabled - Skipping"); const { timeMin, timeMax, items } = args; const apires = await calendar.freebusy.query({ requestBody: { timeMin, timeMax, items }, }); - return apires.data; + + freeBusyResult = apires.data; + } else { + const { timeMin: _timeMin, timeMax: _timeMax, items } = args; + const { timeMin, timeMax } = handleMinMax(_timeMin, _timeMax); + const key = JSON.stringify({ timeMin, timeMax, items }); + const cached = await prisma.calendarCache.findUnique({ + where: { + credentialId_key: { + credentialId: this.credential.id, + key, + }, + expiresAt: { gte: new Date(Date.now()) }, + }, + }); + + if (cached) { + freeBusyResult = cached.value as unknown as calendar_v3.Schema$FreeBusyResponse; + } else { + const apires = await calendar.freebusy.query({ + requestBody: { timeMin, timeMax, items }, + }); + + // Skipping await to respond faster + await prisma.calendarCache.upsert({ + where: { + credentialId_key: { + credentialId: this.credential.id, + key, + }, + }, + update: { + value: JSON.parse(JSON.stringify(apires.data)), + expiresAt: new Date(Date.now() + CACHING_TIME), + }, + create: { + value: JSON.parse(JSON.stringify(apires.data)), + credentialId: this.credential.id, + key, + expiresAt: new Date(Date.now() + CACHING_TIME), + }, + }); + + freeBusyResult = apires.data; + } } - const { timeMin: _timeMin, timeMax: _timeMax, items } = args; - const { timeMin, timeMax } = handleMinMax(_timeMin, _timeMax); - const key = JSON.stringify({ timeMin, timeMax, items }); - const cached = await prisma.calendarCache.findUnique({ - where: { - credentialId_key: { - credentialId: this.credential.id, - key, - }, - expiresAt: { gte: new Date(Date.now()) }, - }, - }); + if (!freeBusyResult.calendars) return null; - if (cached) return cached.value as unknown as calendar_v3.Schema$FreeBusyResponse; - - const apires = await calendar.freebusy.query({ - requestBody: { timeMin, timeMax, items }, - }); - - // Skipping await to respond faster - await prisma.calendarCache.upsert({ - where: { - credentialId_key: { - credentialId: this.credential.id, - key, - }, - }, - update: { - value: JSON.parse(JSON.stringify(apires.data)), - expiresAt: new Date(Date.now() + CACHING_TIME), - }, - create: { - value: JSON.parse(JSON.stringify(apires.data)), - credentialId: this.credential.id, - key, - expiresAt: new Date(Date.now() + CACHING_TIME), - }, - }); - - return apires.data; + const result = Object.values(freeBusyResult.calendars).reduce((c, i) => { + i.busy?.forEach((busyTime) => { + c.push({ + start: busyTime.start || "", + end: busyTime.end || "", + }); + }); + return c; + }, [] as Prisma.PromiseReturnType); + return result; } async getAvailability( @@ -444,22 +463,44 @@ export default class GoogleCalendarService implements Calendar { try { const calsIds = await getCalIds(); - const freeBusyData = await this.getCacheOrFetchAvailability({ - timeMin: dateFrom, - timeMax: dateTo, - items: calsIds.map((id) => ({ id })), - }); - if (!freeBusyData?.calendars) throw new Error("No response from google calendar"); - const result = Object.values(freeBusyData.calendars).reduce((c, i) => { - i.busy?.forEach((busyTime) => { - c.push({ - start: busyTime.start || "", - end: busyTime.end || "", - }); + const originalStartDate = dayjs(dateFrom); + const originalEndDate = dayjs(dateTo); + const diff = originalEndDate.diff(originalStartDate, "days"); + + // /freebusy from google api only allows a date range of 90 days + if (diff <= 90) { + const freeBusyData = await this.getCacheOrFetchAvailability({ + timeMin: dateFrom, + timeMax: dateTo, + items: calsIds.map((id) => ({ id })), }); - return c; - }, [] as Prisma.PromiseReturnType); - return result; + if (!freeBusyData) throw new Error("No response from google calendar"); + + return freeBusyData; + } else { + const busyData = []; + + const loopsNumber = Math.ceil(diff / 90); + + let startDate = originalStartDate; + let endDate = originalStartDate.add(90, "days"); + + for (let i = 0; i < loopsNumber; i++) { + if (endDate.isAfter(originalEndDate)) endDate = originalEndDate; + + busyData.push( + ...((await this.getCacheOrFetchAvailability({ + timeMin: startDate.format(), + timeMax: endDate.format(), + items: calsIds.map((id) => ({ id })), + })) || []) + ); + + startDate = endDate.add(1, "minutes"); + endDate = startDate.add(90, "days"); + } + return busyData; + } } catch (error) { this.log.error("There was an error contacting google calendar service: ", error); throw error;