Merge branch 'main' into fix/after-meeting-ends-migration
This commit is contained in:
commit
3d6a2cde87
|
@ -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;
|
|
||||||
if (typeof appKeys.redirect_uris === "object" && Array.isArray(appKeys.redirect_uris)) {
|
|
||||||
this.redirect_uri = appKeys.redirect_uris[0] as string;
|
|
||||||
}
|
|
||||||
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);
|
async function getGoogleAuth() {
|
||||||
|
const { client_id, client_secret, redirect_uris } = await getGoogleAppKeys();
|
||||||
const googleCredentials = credential.key as Auth.Credentials;
|
const myGoogleAuth = new MyGoogleAuth(client_id, client_secret, redirect_uris[0]);
|
||||||
myGoogleAuth.setCredentials(googleCredentials);
|
myGoogleAuth.setCredentials(googleCredentials);
|
||||||
|
return myGoogleAuth;
|
||||||
|
}
|
||||||
|
|
||||||
const isExpired = () => myGoogleAuth.isTokenExpiring();
|
const refreshAccessToken = async (myGoogleAuth: Awaited<ReturnType<typeof getGoogleAuth>>) => {
|
||||||
|
try {
|
||||||
const refreshAccessToken = () =>
|
const { res } = await myGoogleAuth.refreshToken(googleCredentials.refresh_token);
|
||||||
myGoogleAuth
|
const token = res?.data;
|
||||||
.refreshToken(googleCredentials.refresh_token)
|
|
||||||
.then(async (res: GetTokenResponse) => {
|
|
||||||
const token = res.res?.data;
|
|
||||||
googleCredentials.access_token = token.access_token;
|
googleCredentials.access_token = token.access_token;
|
||||||
googleCredentials.expiry_date = token.expiry_date;
|
googleCredentials.expiry_date = token.expiry_date;
|
||||||
|
const key = googleCredentialSchema.parse(googleCredentials);
|
||||||
await prisma.credential.update({
|
await prisma.credential.update({
|
||||||
where: {
|
where: { id: credential.id },
|
||||||
id: credential.id,
|
data: { key },
|
||||||
},
|
|
||||||
data: {
|
|
||||||
key: googleCredentials as Prisma.InputJsonValue,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
myGoogleAuth.setCredentials(googleCredentials);
|
myGoogleAuth.setCredentials(googleCredentials);
|
||||||
return myGoogleAuth;
|
} catch (err) {
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.log.error("Error refreshing google token", err);
|
this.log.error("Error refreshing google token", err);
|
||||||
|
}
|
||||||
return myGoogleAuth;
|
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,13 +303,10 @@ 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) => {
|
||||||
|
|
||||||
if (apires?.data.calendars) {
|
|
||||||
result = Object.values(apires.data.calendars).reduce((c, i) => {
|
|
||||||
i.busy?.forEach((busyTime) => {
|
i.busy?.forEach((busyTime) => {
|
||||||
c.push({
|
c.push({
|
||||||
start: busyTime.start || "",
|
start: busyTime.start || "",
|
||||||
|
@ -340,8 +314,8 @@ export default class GoogleCalendarService implements Calendar {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return c;
|
return c;
|
||||||
}, [] as typeof result);
|
}, [] 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,
|
||||||
|
|
|
@ -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
Block a user