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
parent
84cff3613a
commit
fe67b99096
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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(),
|
||||
});
|
Loading…
Reference in New Issue