Merge branch 'main' into fix/after-meeting-ends-migration

This commit is contained in:
kodiakhq[bot] 2022-09-01 04:45:39 +00:00 committed by GitHub
commit 3d6a2cde87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 78 deletions

View File

@ -1,10 +1,8 @@
import { Credential, Prisma } from "@prisma/client"; import { Credential, Prisma } from "@prisma/client";
import { GetTokenResponse } from "google-auth-library/build/src/auth/oauth2client"; import { calendar_v3, google } from "googleapis";
import { Auth, calendar_v3, google } from "googleapis";
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
import CalendarService from "@calcom/lib/CalendarService"; import CalendarService from "@calcom/lib/CalendarService";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { import type {
@ -15,81 +13,63 @@ import type {
NewCalendarEventType, NewCalendarEventType,
} from "@calcom/types/Calendar"; } from "@calcom/types/Calendar";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import { getGoogleAppKeys } from "./getGoogleAppKeys";
import { googleCredentialSchema } from "./googleCredentialSchema";
interface GoogleCalError extends Error { interface GoogleCalError extends Error {
code?: number; code?: number;
} }
export default class GoogleCalendarService implements Calendar { export default class GoogleCalendarService implements Calendar {
private url = "";
private integrationName = ""; private integrationName = "";
private auth: Promise<{ getToken: () => Promise<MyGoogleAuth> }>; private auth: { getToken: () => Promise<MyGoogleAuth> };
private log: typeof logger; private log: typeof logger;
private client_id = "";
private client_secret = "";
private redirect_uri = "";
constructor(credential: Credential) { constructor(credential: Credential) {
this.integrationName = "google_calendar"; this.integrationName = "google_calendar";
this.auth = this.googleAuth(credential);
this.auth = this.googleAuth(credential).then((m) => m);
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] }); this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
} }
private googleAuth = async (credential: Credential) => { private googleAuth = (credential: Credential) => {
const appKeys = await getAppKeysFromSlug("google-calendar"); const googleCredentials = googleCredentialSchema.parse(credential.key);
if (typeof appKeys.client_id === "string") this.client_id = appKeys.client_id;
if (typeof appKeys.client_secret === "string") this.client_secret = appKeys.client_secret; async function getGoogleAuth() {
if (typeof appKeys.redirect_uris === "object" && Array.isArray(appKeys.redirect_uris)) { const { client_id, client_secret, redirect_uris } = await getGoogleAppKeys();
this.redirect_uri = appKeys.redirect_uris[0] as string; const myGoogleAuth = new MyGoogleAuth(client_id, client_secret, redirect_uris[0]);
myGoogleAuth.setCredentials(googleCredentials);
return myGoogleAuth;
} }
if (!this.client_id) throw new HttpError({ statusCode: 400, message: "Google client_id missing." });
if (!this.client_secret)
throw new HttpError({ statusCode: 400, message: "Google client_secret missing." });
if (!this.redirect_uri) throw new HttpError({ statusCode: 400, message: "Google redirect_uri missing." });
const myGoogleAuth = new MyGoogleAuth(this.client_id, this.client_secret, this.redirect_uri); const refreshAccessToken = async (myGoogleAuth: Awaited<ReturnType<typeof getGoogleAuth>>) => {
try {
const googleCredentials = credential.key as Auth.Credentials; const { res } = await myGoogleAuth.refreshToken(googleCredentials.refresh_token);
myGoogleAuth.setCredentials(googleCredentials); const token = res?.data;
googleCredentials.access_token = token.access_token;
const isExpired = () => myGoogleAuth.isTokenExpiring(); googleCredentials.expiry_date = token.expiry_date;
const key = googleCredentialSchema.parse(googleCredentials);
const refreshAccessToken = () => await prisma.credential.update({
myGoogleAuth where: { id: credential.id },
.refreshToken(googleCredentials.refresh_token) data: { key },
.then(async (res: GetTokenResponse) => {
const token = res.res?.data;
googleCredentials.access_token = token.access_token;
googleCredentials.expiry_date = token.expiry_date;
await prisma.credential.update({
where: {
id: credential.id,
},
data: {
key: googleCredentials as Prisma.InputJsonValue,
},
});
myGoogleAuth.setCredentials(googleCredentials);
return myGoogleAuth;
})
.catch((err) => {
this.log.error("Error refreshing google token", err);
return myGoogleAuth;
}); });
myGoogleAuth.setCredentials(googleCredentials);
} catch (err) {
this.log.error("Error refreshing google token", err);
}
return myGoogleAuth;
};
return { return {
getToken: () => (!isExpired() ? Promise.resolve(myGoogleAuth) : refreshAccessToken()), getToken: async () => {
const myGoogleAuth = await getGoogleAuth();
const isExpired = () => myGoogleAuth.isTokenExpiring();
return !isExpired() ? Promise.resolve(myGoogleAuth) : refreshAccessToken(myGoogleAuth);
},
}; };
}; };
async createEvent(calEventRaw: CalendarEvent): Promise<NewCalendarEventType> { async createEvent(calEventRaw: CalendarEvent): Promise<NewCalendarEventType> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const auth = await this.auth; const myGoogleAuth = await this.auth.getToken();
const myGoogleAuth = await auth.getToken();
const payload: calendar_v3.Schema$Event = { const payload: calendar_v3.Schema$Event = {
summary: calEventRaw.title, summary: calEventRaw.title,
description: getRichDescription(calEventRaw), description: getRichDescription(calEventRaw),
@ -168,8 +148,7 @@ export default class GoogleCalendarService implements Calendar {
async updateEvent(uid: string, event: CalendarEvent, externalCalendarId: string): Promise<any> { async updateEvent(uid: string, event: CalendarEvent, externalCalendarId: string): Promise<any> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const auth = await this.auth; const myGoogleAuth = await this.auth.getToken();
const myGoogleAuth = await auth.getToken();
const payload: calendar_v3.Schema$Event = { const payload: calendar_v3.Schema$Event = {
summary: event.title, summary: event.title,
description: getRichDescription(event), description: getRichDescription(event),
@ -254,8 +233,7 @@ export default class GoogleCalendarService implements Calendar {
async deleteEvent(uid: string, event: CalendarEvent, externalCalendarId?: string | null): Promise<void> { async deleteEvent(uid: string, event: CalendarEvent, externalCalendarId?: string | null): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const auth = await this.auth; const myGoogleAuth = await this.auth.getToken();
const myGoogleAuth = await auth.getToken();
const calendar = google.calendar({ const calendar = google.calendar({
version: "v3", version: "v3",
auth: myGoogleAuth, auth: myGoogleAuth,
@ -295,8 +273,7 @@ export default class GoogleCalendarService implements Calendar {
selectedCalendars: IntegrationCalendar[] selectedCalendars: IntegrationCalendar[]
): Promise<EventBusyDate[]> { ): Promise<EventBusyDate[]> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const auth = await this.auth; const myGoogleAuth = await this.auth.getToken();
const myGoogleAuth = await auth.getToken();
const calendar = google.calendar({ const calendar = google.calendar({
version: "v3", version: "v3",
auth: myGoogleAuth, auth: myGoogleAuth,
@ -326,22 +303,19 @@ export default class GoogleCalendarService implements Calendar {
}, },
}, },
(err, apires) => { (err, apires) => {
if (err) { if (err) return reject(err);
reject(err); // If there's no calendar we just skip
} if (!apires?.data.calendars) return resolve([]);
let result: Prisma.PromiseReturnType<CalendarService["getAvailability"]> = []; const result = Object.values(apires.data.calendars).reduce((c, i) => {
i.busy?.forEach((busyTime) => {
if (apires?.data.calendars) { c.push({
result = Object.values(apires.data.calendars).reduce((c, i) => { start: busyTime.start || "",
i.busy?.forEach((busyTime) => { end: busyTime.end || "",
c.push({
start: busyTime.start || "",
end: busyTime.end || "",
});
}); });
return c; });
}, [] as typeof result); return c;
} }, [] as Prisma.PromiseReturnType<CalendarService["getAvailability"]>);
resolve(result); resolve(result);
} }
); );
@ -356,8 +330,7 @@ export default class GoogleCalendarService implements Calendar {
async listCalendars(): Promise<IntegrationCalendar[]> { async listCalendars(): Promise<IntegrationCalendar[]> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const auth = await this.auth; const myGoogleAuth = await this.auth.getToken();
const myGoogleAuth = await auth.getToken();
const calendar = google.calendar({ const calendar = google.calendar({
version: "v3", version: "v3",
auth: myGoogleAuth, auth: myGoogleAuth,

View File

@ -0,0 +1,13 @@
import { z } from "zod";
import getParsedAppKeysFromSlug from "../../_utils/getParsedAppKeysFromSlug";
const googleAppKeysSchema = z.object({
client_id: z.string(),
client_secret: z.string(),
redirect_uris: z.array(z.string()),
});
export const getGoogleAppKeys = async () => {
return getParsedAppKeysFromSlug("google-calendar", googleAppKeysSchema);
};

View File

@ -0,0 +1,9 @@
import { z } from "zod";
export const googleCredentialSchema = z.object({
scope: z.string(),
token_type: z.literal("Bearer"),
expiry_date: z.number(),
access_token: z.string(),
refresh_token: z.string(),
});