Refactors google calendar service (#3933)

* Refactors google calendar service

* Update CalendarService.ts

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