
* patch applied * patch applied * We shouldn't pollute global css * Build fixes * Updates typings * WIP extracting zoom to package * Revert "Upgrades next to 12.1 (#1895)" (#1903) This reverts commitede0e98e1f
. * Tweak/gitignore prisma zod (#1905) * Extracts ignored createEventTypeBaseInput * Adds postinstall script * Revert "Tweak/gitignore prisma zod (#1905)" (#1906) This reverts commit15bfeb30d7
. * Eslint fixes (#1898) * Eslint fixes * Docs build fixes * Upgrade to next 12.1 (#1904) * Upgrades next to 12.1 * Fixes build * Updaters e2e test pipelines Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> * Fix URL by removing slash and backslash (#1733) * Fix URl by removing slash and backslash * Implement slugify * Add data type * Fixing folder structure * Solve zod-utils conflict * Build fixes (#1929) * Build fixes * Fixes type error * WIP * Conflict fixes * Removes unused file * TODO * WIP * Type fixes * Linting * WIP * Moved App definition to types * WIP * WIP * WIP * WIP WIP * Renamed zoomvideo app * Import fix * Daily.co app (#2022) * Daily.co app * Update packages/app-store/dailyvideo/lib/VideoApiAdapter.ts Co-authored-by: Omar López <zomars@me.com> * Update packages/app-store/dailyvideo/lib/VideoApiAdapter.ts Co-authored-by: Omar López <zomars@me.com> * Missing deps for newly added contants to lib Co-authored-by: Omar López <zomars@me.com> * WIP * WIP * WIP * Daily fixes * Updated type info * Slack Oauth integration - api route ideas * Adds getLocationOptions * Type fixes * Adds location option for daily video * Revert "Slack Oauth integration - api route ideas" This reverts commit 35ffa78e929339c4badb98cdab4e4b953ecc7cca. * Slack Oauth + verify sig * Revert "Slack Oauth + verify sig" This reverts commit ee95795e0f0ae6d06be4e0a423afb8c315d9af7d. * Huddle01 migration to app store (#2038) * Jitsi Video App migration * Removing uneeded dependencies * Missed unused reference * Missing dependency `@calcom/lib` is needed in the `locationOption.ts` file * Huddle01 migration to app store * Jitsi Video App migration (#2027) * Jitsi Video App migration * Removing uneeded dependencies * Missed unused reference * Missing dependency `@calcom/lib` is needed in the `locationOption.ts` file Co-authored-by: Omar López <zomars@me.com> * Monorepo/app store MS Teams Integration (#2080) * Create teamsvideo package * Remove zoom specific refrences * Add teams video files * Rename to office365_video * Add call back to add crednetial type office365_teams * Rename to office_video to match type * Add MS Teams as a location option * Rename files * Add teams reponse interface and create meeting * Comment out Daily imports * Add check for Teams integration * Add token checking functions * Change template to create event rather than meeting * Add comment to test between create link and event * Add teams URL to booking * Ask for just onlineMeeting permission * Add MS Teams logo * Add message to have an enterprise account * Remove comments * Comment back hasDailyIntegration * Comment back daily credentials * Update link to MS Graph section of README * Move API calls to package Co-authored-by: Omar López <zomars@me.com> * Re-adds missing module for transpiling * Adds email as required field for app store metadata * WIP: migrates tandem to app store * Cleanup * Migrates tandem api routes to app store * Fixes tandem api handlers * Big WIP WIP * Build fixes * WIP * Fixes annoying circular dependency bug I've spent a whole day on this.... * Location option cleanup * Type fixes * Update EventManager.ts * Update CalendarManager.ts * Moves CalendarService back to lib * Moves apple calendar to App Store * Cleanup * More cleanup * Migrates apple calendar * Returns all connected calendars credentials * No tsx needed in calcom/lib * Update auth.ts * Reordering * Update i18n.utils.ts * WIP: Google Meet * Type fixes * Type fixes * Cleanup * Update LinkIconButton.tsx * Update TrialBanner.tsx * Cleanup * Cleanup * Type fixes * Update _appRegistry.ts * Update fonts.css * Update CalEventParser.ts * Delete yarn.lock.rej * Update eslint-preset.js * Delete zoom.tsx * Type fixes * Migrates caldav to app store * Cleanup * Type fixes * Adds caldav to app store * Test fixes * Updates integration tests * Moar test fixes * Redirection fixes * Redirection fixes * Update timeFormat.ts * Update booking-pages.test.ts * Connect button fixes * Fix empty item * Cal fixes andrea (#2234) * Fixes #2178 * Fixes #2178 * Update apps/web/components/availability/Schedule.tsx * Update apps/web/components/availability/Schedule.tsx Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Peer Richelsen <peer@cal.com> * added meta viewport to disable zoom on input focus on mobile (#2238) * Update lint.yml (#2211) Co-authored-by: Peer Richelsen <peeroke@gmail.com> * Fix prisma client bundle makes app slow (#2237) Co-authored-by: Omar López <zomars@me.com> * Slider fixes * Removed unused code * Full Shell when unauthed * App sidebar responsive fixes * Adds dynamic install button * Fix for duplicate connected calendars * Various fixes * Display notification on app delete * Reuse connect button * Adds CalDav button * Deprecates ConnectIntegration * Simplify install button * Adds Google Calendar connect button * Adds Office 365 Install button * Migrates Stripe to App Store * Zoom Install Button (#2244) * Fix minor css, app image load from static path * Fix app logos remote img src (#2252) * Adds missing exports * Cleanup * Disables install button for globally enabled apps * Update EventManager.ts * Stripe fixes * Disables example app Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Juan Esteban Nieto Cifuentes <89233604+Jenietoc@users.noreply.github.com> Co-authored-by: Leo Giovanetti <hello@leog.me> Co-authored-by: Sean Brydon <seanbrydon.me@gmail.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Bailey Pumfleet <pumfleet@hey.com> Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Co-authored-by: andreaestefania12 <andreaestefania12@hotmail.com> Co-authored-by: Peer Richelsen <peer@cal.com> Co-authored-by: Demian Caldelas <denik.works@protonmail.com> Co-authored-by: Alan <alannnc@gmail.com>
332 lines
10 KiB
TypeScript
332 lines
10 KiB
TypeScript
import { Credential, Prisma } from "@prisma/client";
|
|
import { GetTokenResponse } from "google-auth-library/build/src/auth/oauth2client";
|
|
import { Auth, calendar_v3, google } from "googleapis";
|
|
|
|
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
|
|
import CalendarService from "@calcom/lib/CalendarService";
|
|
import logger from "@calcom/lib/logger";
|
|
import prisma from "@calcom/prisma";
|
|
import type {
|
|
Calendar,
|
|
CalendarEvent,
|
|
EventBusyDate,
|
|
IntegrationCalendar,
|
|
NewCalendarEventType,
|
|
} from "@calcom/types/Calendar";
|
|
|
|
const GOOGLE_API_CREDENTIALS = process.env.GOOGLE_API_CREDENTIALS || "";
|
|
|
|
export default class GoogleCalendarService implements Calendar {
|
|
private url = "";
|
|
private integrationName = "";
|
|
private auth: { getToken: () => Promise<MyGoogleAuth> };
|
|
private log: typeof logger;
|
|
|
|
constructor(credential: Credential) {
|
|
this.integrationName = "google_calendar";
|
|
|
|
this.auth = this.googleAuth(credential);
|
|
|
|
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
|
}
|
|
|
|
private googleAuth = (credential: Credential) => {
|
|
const { client_secret, client_id, redirect_uris } = JSON.parse(GOOGLE_API_CREDENTIALS).web;
|
|
|
|
const myGoogleAuth = new MyGoogleAuth(client_id, client_secret, redirect_uris[0]);
|
|
|
|
const googleCredentials = credential.key as Auth.Credentials;
|
|
myGoogleAuth.setCredentials(googleCredentials);
|
|
|
|
const isExpired = () => myGoogleAuth.isTokenExpiring();
|
|
|
|
const refreshAccessToken = () =>
|
|
myGoogleAuth
|
|
.refreshToken(googleCredentials.refresh_token)
|
|
.then((res: GetTokenResponse) => {
|
|
const token = res.res?.data;
|
|
googleCredentials.access_token = token.access_token;
|
|
googleCredentials.expiry_date = token.expiry_date;
|
|
return prisma.credential
|
|
.update({
|
|
where: {
|
|
id: credential.id,
|
|
},
|
|
data: {
|
|
key: googleCredentials as Prisma.InputJsonValue,
|
|
},
|
|
})
|
|
.then(() => {
|
|
myGoogleAuth.setCredentials(googleCredentials);
|
|
return myGoogleAuth;
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
this.log.error("Error refreshing google token", err);
|
|
|
|
return myGoogleAuth;
|
|
});
|
|
|
|
return {
|
|
getToken: () => (!isExpired() ? Promise.resolve(myGoogleAuth) : refreshAccessToken()),
|
|
};
|
|
};
|
|
|
|
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
|
|
return new Promise((resolve, reject) =>
|
|
this.auth.getToken().then((myGoogleAuth) => {
|
|
const payload: calendar_v3.Schema$Event = {
|
|
summary: event.title,
|
|
description: getRichDescription(event),
|
|
start: {
|
|
dateTime: event.startTime,
|
|
timeZone: event.organizer.timeZone,
|
|
},
|
|
end: {
|
|
dateTime: event.endTime,
|
|
timeZone: event.organizer.timeZone,
|
|
},
|
|
attendees: event.attendees.map((attendee) => ({
|
|
...attendee,
|
|
responseStatus: "accepted",
|
|
})),
|
|
reminders: {
|
|
useDefault: true,
|
|
},
|
|
};
|
|
|
|
if (event.location) {
|
|
payload["location"] = getLocation(event);
|
|
}
|
|
|
|
if (event.conferenceData && event.location === "integrations:google:meet") {
|
|
payload["conferenceData"] = event.conferenceData;
|
|
}
|
|
|
|
const calendar = google.calendar({
|
|
version: "v3",
|
|
auth: myGoogleAuth,
|
|
});
|
|
calendar.events.insert(
|
|
{
|
|
auth: myGoogleAuth,
|
|
calendarId: event.destinationCalendar?.externalId
|
|
? event.destinationCalendar.externalId
|
|
: "primary",
|
|
requestBody: payload,
|
|
conferenceDataVersion: 1,
|
|
},
|
|
function (err, event) {
|
|
if (err || !event?.data) {
|
|
console.error("There was an error contacting google calendar service: ", err);
|
|
return reject(err);
|
|
}
|
|
return resolve({
|
|
uid: "",
|
|
...event.data,
|
|
id: event.data.id || "",
|
|
additionalInfo: {
|
|
hangoutLink: event.data.hangoutLink || "",
|
|
},
|
|
type: "google_calendar",
|
|
password: "",
|
|
url: "",
|
|
});
|
|
}
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
|
|
return new Promise((resolve, reject) =>
|
|
this.auth.getToken().then((myGoogleAuth) => {
|
|
const payload: calendar_v3.Schema$Event = {
|
|
summary: event.title,
|
|
description: getRichDescription(event),
|
|
start: {
|
|
dateTime: event.startTime,
|
|
timeZone: event.organizer.timeZone,
|
|
},
|
|
end: {
|
|
dateTime: event.endTime,
|
|
timeZone: event.organizer.timeZone,
|
|
},
|
|
attendees: event.attendees,
|
|
reminders: {
|
|
useDefault: true,
|
|
},
|
|
};
|
|
|
|
if (event.location) {
|
|
payload["location"] = getLocation(event);
|
|
}
|
|
|
|
const calendar = google.calendar({
|
|
version: "v3",
|
|
auth: myGoogleAuth,
|
|
});
|
|
calendar.events.update(
|
|
{
|
|
auth: myGoogleAuth,
|
|
calendarId: event.destinationCalendar?.externalId
|
|
? event.destinationCalendar.externalId
|
|
: "primary",
|
|
eventId: uid,
|
|
sendNotifications: true,
|
|
sendUpdates: "all",
|
|
requestBody: payload,
|
|
},
|
|
function (err, event) {
|
|
if (err) {
|
|
console.error("There was an error contacting google calendar service: ", err);
|
|
|
|
return reject(err);
|
|
}
|
|
return resolve(event?.data);
|
|
}
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
async deleteEvent(uid: string, event: CalendarEvent): Promise<void> {
|
|
return new Promise((resolve, reject) =>
|
|
this.auth.getToken().then((myGoogleAuth) => {
|
|
const calendar = google.calendar({
|
|
version: "v3",
|
|
auth: myGoogleAuth,
|
|
});
|
|
calendar.events.delete(
|
|
{
|
|
auth: myGoogleAuth,
|
|
calendarId: event.destinationCalendar?.externalId
|
|
? event.destinationCalendar.externalId
|
|
: "primary",
|
|
eventId: uid,
|
|
sendNotifications: true,
|
|
sendUpdates: "all",
|
|
},
|
|
function (err, event) {
|
|
if (err) {
|
|
console.error("There was an error contacting google calendar service: ", err);
|
|
return reject(err);
|
|
}
|
|
return resolve(event?.data);
|
|
}
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
async getAvailability(
|
|
dateFrom: string,
|
|
dateTo: string,
|
|
selectedCalendars: IntegrationCalendar[]
|
|
): Promise<EventBusyDate[]> {
|
|
return new Promise((resolve, reject) =>
|
|
this.auth.getToken().then((myGoogleAuth) => {
|
|
const calendar = google.calendar({
|
|
version: "v3",
|
|
auth: myGoogleAuth,
|
|
});
|
|
const selectedCalendarIds = selectedCalendars
|
|
.filter((e) => e.integration === this.integrationName)
|
|
.map((e) => e.externalId);
|
|
if (selectedCalendarIds.length === 0 && selectedCalendars.length > 0) {
|
|
// Only calendars of other integrations selected
|
|
resolve([]);
|
|
return;
|
|
}
|
|
|
|
(selectedCalendarIds.length === 0
|
|
? calendar.calendarList
|
|
.list()
|
|
.then((cals) => cals.data.items?.map((cal) => cal.id).filter(Boolean) || [])
|
|
: Promise.resolve(selectedCalendarIds)
|
|
)
|
|
.then((calsIds) => {
|
|
calendar.freebusy.query(
|
|
{
|
|
requestBody: {
|
|
timeMin: dateFrom,
|
|
timeMax: dateTo,
|
|
items: calsIds.map((id) => ({ id: id })),
|
|
},
|
|
},
|
|
(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 || "",
|
|
});
|
|
});
|
|
return c;
|
|
}, [] as typeof result);
|
|
}
|
|
resolve(result);
|
|
}
|
|
);
|
|
})
|
|
.catch((err) => {
|
|
this.log.error("There was an error contacting google calendar service: ", err);
|
|
|
|
reject(err);
|
|
});
|
|
})
|
|
);
|
|
}
|
|
|
|
async listCalendars(): Promise<IntegrationCalendar[]> {
|
|
return new Promise((resolve, reject) =>
|
|
this.auth.getToken().then((myGoogleAuth) => {
|
|
const calendar = google.calendar({
|
|
version: "v3",
|
|
auth: myGoogleAuth,
|
|
});
|
|
|
|
calendar.calendarList
|
|
.list()
|
|
.then((cals) => {
|
|
resolve(
|
|
cals.data.items?.map((cal) => {
|
|
const calendar: IntegrationCalendar = {
|
|
externalId: cal.id ?? "No id",
|
|
integration: this.integrationName,
|
|
name: cal.summary ?? "No name",
|
|
primary: cal.primary ?? false,
|
|
};
|
|
return calendar;
|
|
}) || []
|
|
);
|
|
})
|
|
.catch((err: Error) => {
|
|
this.log.error("There was an error contacting google calendar service: ", err);
|
|
|
|
reject(err);
|
|
});
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
class MyGoogleAuth extends google.auth.OAuth2 {
|
|
constructor(client_id: string, client_secret: string, redirect_uri: string) {
|
|
super(client_id, client_secret, redirect_uri);
|
|
}
|
|
|
|
isTokenExpiring() {
|
|
return super.isTokenExpiring();
|
|
}
|
|
|
|
async refreshToken(token: string | null | undefined) {
|
|
return super.refreshToken(token);
|
|
}
|
|
}
|