From 26637cda0860bb8806827cf177b20da2aabb460f Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Thu, 9 Nov 2023 09:47:53 -0500 Subject: [PATCH] fix: When Installing Calendar App, Set Primary Calendar As Selected Calendar (#12291) * GCal set primary calendar on install * Outlook set default calendar as selected cal * Zoho create selected calendar on install * Lark create selected cal on install --- .../app-store/googlecalendar/api/callback.ts | 29 ++++++++++- .../app-store/larkcalendar/api/callback.ts | 26 ++++++++++ .../office365calendar/api/callback.ts | 48 ++++++++++++++++++- .../app-store/zohocalendar/api/callback.ts | 33 ++++++++++++- 4 files changed, 132 insertions(+), 4 deletions(-) diff --git a/packages/app-store/googlecalendar/api/callback.ts b/packages/app-store/googlecalendar/api/callback.ts index 4c80d974ec..2b3d2d90b0 100644 --- a/packages/app-store/googlecalendar/api/callback.ts +++ b/packages/app-store/googlecalendar/api/callback.ts @@ -41,7 +41,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const token = await oAuth2Client.getToken(code); key = token.res?.data; - await prisma.credential.create({ + const credential = await prisma.credential.create({ data: { type: "google_calendar", key, @@ -49,6 +49,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) appId: "google-calendar", }, }); + + // Set the primary calendar as the first selected calendar + + // We can ignore this type error because we just validated the key when we init oAuth2Client + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + oAuth2Client.setCredentials(key); + + const calendar = google.calendar({ + version: "v3", + auth: oAuth2Client, + }); + + const cals = await calendar.calendarList.list({ fields: "items(id,summary,primary,accessRole)" }); + + const primaryCal = cals.data.items?.find((cal) => cal.primary); + + if (primaryCal?.id) { + await prisma.selectedCalendar.create({ + data: { + userId: req.session.user.id, + externalId: primaryCal.id, + credentialId: credential.id, + integration: "google_calendar", + }, + }); + } } if (state?.installGoogleVideo) { diff --git a/packages/app-store/larkcalendar/api/callback.ts b/packages/app-store/larkcalendar/api/callback.ts index b0ce73b934..16df208ac9 100644 --- a/packages/app-store/larkcalendar/api/callback.ts +++ b/packages/app-store/larkcalendar/api/callback.ts @@ -89,6 +89,32 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { }); } + const primaryCalendarResponse = await fetch( + `https://${LARK_HOST}/open-apis/calendar/v4/calendars/primary`, + { + method: "GET", + headers: { + Authorization: `Bearer ${key.access_token}`, + "Content-Type": "application/json", + }, + } + ); + + if (primaryCalendarResponse.status === 200) { + const primaryCalendar = await primaryCalendarResponse.json(); + + if (primaryCalendar.data.calendars.calendar.calendar_id && req.session?.user?.id) { + await prisma.selectedCalendar.create({ + data: { + userId: req.session?.user.id, + integration: "lark_calendar", + externalId: primaryCalendar.data.calendars.calendar.calendar_id as string, + credentialId: currentCredential?.id, + }, + }); + } + } + res.redirect( getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: "calendar", slug: "lark-calendar" }) diff --git a/packages/app-store/office365calendar/api/callback.ts b/packages/app-store/office365calendar/api/callback.ts index 19265a3316..4de204b52c 100644 --- a/packages/app-store/office365calendar/api/callback.ts +++ b/packages/app-store/office365calendar/api/callback.ts @@ -1,6 +1,8 @@ +import type { Calendar as OfficeCalendar } from "@microsoft/microsoft-graph-types-beta"; import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { handleErrorsJson } from "@calcom/lib/errors"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import prisma from "@calcom/prisma"; @@ -65,7 +67,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) responseBody.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in); // set expiry date in seconds delete responseBody.expires_in; - await prisma.credential.create({ + const credential = await prisma.credential.create({ data: { type: "office365_calendar", key: responseBody, @@ -74,6 +76,50 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }, }); + // Set the isDefaultCalendar as selectedCalendar + // If a user has multiple calendars, keep on making calls until we find the default calendar + let defaultCalendar: OfficeCalendar | undefined = undefined; + let requestUrl = "https://graph.microsoft.com/v1.0/me/calendars?$select=id,isDefaultCalendar"; + let finishedParsingCalendars = false; + + while (!finishedParsingCalendars) { + const calRequest = await fetch(requestUrl, { + method: "GET", + headers: { + Authorization: `Bearer ${responseBody.access_token}`, + "Content-Type": "application/json", + }, + }); + let calBody = await handleErrorsJson<{ value: OfficeCalendar[]; "@odata.nextLink"?: string }>(calRequest); + + if (typeof responseBody === "string") { + calBody = JSON.parse(responseBody) as { value: OfficeCalendar[] }; + } + + const findDefaultCalendar = calBody.value.find((calendar) => calendar.isDefaultCalendar); + + if (findDefaultCalendar) { + defaultCalendar = findDefaultCalendar; + } + + if (calBody["@odata.nextLink"]) { + requestUrl = calBody["@odata.nextLink"]; + } else { + finishedParsingCalendars = true; + } + } + + if (defaultCalendar?.id && req.session?.user?.id) { + await prisma.selectedCalendar.create({ + data: { + userId: req.session?.user.id, + integration: "office365_calendar", + externalId: defaultCalendar.id, + credentialId: credential.id, + }, + }); + } + const state = decodeOAuthState(req); return res.redirect( getSafeRedirectUrl(state?.returnTo) ?? diff --git a/packages/app-store/zohocalendar/api/callback.ts b/packages/app-store/zohocalendar/api/callback.ts index 81ea1edf79..f785c27d5a 100644 --- a/packages/app-store/zohocalendar/api/callback.ts +++ b/packages/app-store/zohocalendar/api/callback.ts @@ -5,10 +5,10 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import prisma from "@calcom/prisma"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; import config from "../config.json"; import type { ZohoAuthCredentials } from "../types/ZohoCalendar"; @@ -64,7 +64,36 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { expires_in: Math.round(+new Date() / 1000 + responseBody.expires_in), }; - await createOAuthAppCredential({ appId: config.slug, type: config.type }, key, req); + const credential = await prisma.credential.create({ + data: { + type: config.type, + key, + userId: req.session.user.id, + appId: config.slug, + }, + }); + + const calendarResponse = await fetch("https://calendar.zoho.com/api/v1/calendars", { + method: "GET", + headers: { + Authorization: `Bearer ${key.access_token}`, + "Content-Type": "application/json", + }, + }); + const data = await calendarResponse.json(); + + const primaryCalendar = data.calendars.find((calendar: any) => calendar.isdefault); + + if (primaryCalendar.uid) { + await prisma.selectedCalendar.create({ + data: { + userId: req.session.user.id, + integration: config.type, + externalId: primaryCalendar.uid, + credentialId: credential.id, + }, + }); + } res.redirect( getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: config.variant, slug: config.slug })