10000 feat: Reduce bundle size via better imports + install gcal + gvideo b… · calcom/cal.com@b8219c2 · GitHub
[go: up one dir, main page]

Skip to content

Commit

Permalink
feat: Reduce bundle size via better imports + install gcal + gvideo b…
Browse files Browse the repository at this point in the history
…y default for google signups (#17810)

* wip

* use light imports

* fix

* perf: Reduce bundle size via better imports with `googleapis` (#17811)

* fix test

* refactor

* update vite.config.js

* fix

* fix unit test failing
  • Loading branch information
hbjORbj authored Dec 6, 2024
1 parent f75415e commit b8219c2
Show file tree
Hide file tree
Showing 27 changed files with 379 additions and 275 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ NEXT_PUBLIC_FRESHCHAT_HOST=

# Google OAuth credentials
# To enable Login with Google you need to:
# 1. Set `GOOGLE_API_CREDENTIALS` above
# 1. Set `GOOGLE_API_CREDENTIALS` below
# 2. Set `GOOGLE_LOGIN_ENABLED` to `true`
# When self-hosting please ensure you configure the Google integration as an Internal app so no one else can login to your instance
# @see https://support.google.com/cloud/answer/6158849#public-and-internal&zippy=%2Cpublic-and-internal-applications
Expand Down
8 changes: 4 additions & 4 deletions apps/api/v2/src/ee/calendars/services/gcal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Prisma } from "@prisma/client";
import { Request } from "express";
import { google } from "googleapis";
import { OAuth2Client } from "googleapis-common";
import { calendar_v3 } from "@googleapis/calendar";
import { z } from "zod";

import { SUCCESS_STATUS, GOOGLE_CALENDAR_TYPE } from "@calcom/platform-constants";
Expand Down Expand Up @@ -78,7 +79,7 @@ export class GoogleCalendarService implements OAuthCalendarApp {

const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys);

const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri);
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirectUri);
return oAuth2Client;
}

Expand Down Expand Up @@ -134,8 +135,7 @@ export class GoogleCalendarService implements OAuthCalendarApp {

oAuth2Client.setCredentials(key);

const calendar = google.calendar({
version: "v3",
const calendar = new calendar_v3.Calendar({
auth: oAuth2Client,
});

Expand Down
3 changes: 0 additions & 3 deletions apps/api/v2/src/ee/gcal/gcal.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ import {
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
import { Prisma } from "@prisma/client";
import { Request } from "express";
import { google } from "googleapis";
import { z } from "zod";

import { APPS_READ, GOOGLE_CALENDAR_TYPE, SUCCESS_STATUS } from "@calcom/platform-constants";

Expand Down
4 changes: 2 additions & 2 deletions apps/api/v2/src/modules/apps/services/gcal.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AppsRepository } from "@/modules/apps/apps.repository";
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
import { google } from "googleapis";
import { OAuth2Client } from "googleapis-common";
import { z } from "zod";

@Injectable()
Expand All @@ -21,7 +21,7 @@ export class GCalService {

const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys);

const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri);
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirectUri);
return oAuth2Client;
}
}
5 changes: 4 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"@dub/analytics": "^0.0.15",
"@formkit/auto-animate": "1.0.0-beta.5",
"@glidejs/glide": "^3.5.2",
"@googleapis/admin": "^23.0.0",
"@googleapis/calendar": "^9.7.9",
"@googleapis/oauth2": "^1.0.7",
"@hookform/error-message": "^2.0.0",
"@hookform/resolvers": "^2.9.7",
"@next-auth/prisma-adapter": "^1.0.4",
Expand Down Expand Up @@ -85,7 +88,6 @@
"dotenv-cli": "^6.0.0",
"entities": "^4.4.0",
"eslint-config-next": "^13.2.1",
"googleapis": "^84.0.0",
"gray-matter": "^4.0.3",
"handlebars": "^4.7.7",
"ical.js": "^1.4.0",
Expand Down Expand Up @@ -180,6 +182,7 @@
"deasync": "^0.1.30",
"detect-port": "^1.3.0",
"env-cmd": "^10.1.0",
"google-auth-library": "^9.15.0",
"module-alias": "^2.2.2",
"msw": "^0.42.3",
"node-html-parser": "^6.1.10",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/pages/api/teams/googleworkspace/add.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { google } from "googleapis";
import { OAuth2Client } from "googleapis-common";
import type { NextApiRequest, NextApiResponse } from "next";

import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
Expand All @@ -20,7 +20,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)

// use differnt callback to normal calendar connection
const redirect_uri = `${WEBAPP_URL}/api/teams/googleworkspace/callback`;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);

const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/pages/api/teams/googleworkspace/callback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { google } from "googleapis";
import { OAuth2Client } from "googleapis-common";
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";

Expand Down Expand Up @@ -37,7 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json({ message: "Google client_secret missing." });

const redirect_uri = `${WEBAPP_URL}/api/teams/googleworkspace/callback`;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);

if (!code) {
throw new Error("No code provided");
Expand Down
17 changes: 17 additions & 0 deletions packages/app-store/_utils/oauth/updateProfilePhotoGoogle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { oauth2_v2 } from "@googleapis/oauth2";
import type { OAuth2Client } from "googleapis-common";

import logger from "@calcom/lib/logger";
import { UserRepository } from "@calcom/lib/server/repository/user";

export async function updateProfilePhotoGoogle(oAuth2Client: OAuth2Client, userId: number) {
try {
const oauth2 = new oauth2_v2.Oauth2({ auth: oAuth2Client });
const userDetails = await oauth2.userinfo.get();
if (userDetails.data?.picture) {
await UserRepository.updateAvatar({ id: userId, avatarUrl: userDetails.data.picture });
}
} catch (error) {
logger.error("Error updating avatarUrl from google calendar connect", error);
}
}
9 changes: 4 additions & 5 deletions packages/app-store/googlecalendar/api/add.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { google } from "googleapis";
import { OAuth2Client } from "googleapis-common";
import type { NextApiRequest, NextApiResponse } from "next";

import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
import { GOOGLE_CALENDAR_SCOPES, SCOPE_USERINFO_PROFILE, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";

import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
import { SCOPES } from "../lib/constants";
import { getGoogleAppKeys } from "../lib/getGoogleAppKeys";

async function getHandler(req: NextApiRequest, res: NextApiResponse) {
// Get token from Google Calendar API
const { client_id, client_secret } = await getGoogleAppKeys();
const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);

const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: SCOPES,
scope: [SCOPE_USERINFO_PROFILE, ...GOOGLE_CALENDAR_SCOPES],
// A refresh token is only returned the first time the user
// consents to providing access. For illustration purposes,
// setting the prompt to 'consent' will force this consent
Expand Down
67 changes: 39 additions & 28 deletions packages/app-store/googlecalendar/api/callback.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { google } from "googleapis";
import { calendar_v3 } from "@googleapis/calendar";
import { OAuth2Client } from "googleapis-common";
import type { NextApiRequest, NextApiResponse } from "next";

import { updateProfilePhotoGoogle } from "@calcom/app-store/_utils/oauth/updateProfilePhotoGoogle";
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar";
import { WEBAPP_URL, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
import {
GOOGLE_CALENDAR_SCOPES,
SCOPE_USERINFO_PROFILE,
WEBAPP_URL,
WEBAPP_URL_FOR_OAUTH,
} from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { getAllCalendars, updateProfilePhoto } from "@calcom/lib/google";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import { CredentialRepository } from "@calcom/lib/server/repository/credential";
import { GoogleRepository } from "@calcom/lib/server/repository/google";
import { Prisma } from "@calcom/prisma/client";

import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
import { REQUIRED_SCOPES, SCOPE_USERINFO_PROFILE } from "../lib/constants";
import { getGoogleAppKeys } from "../lib/getGoogleAppKeys";

async function getHandler(req: NextApiRequest, res: NextApiResponse) {
Expand All @@ -40,14 +45,14 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {

const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`;

const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);

if (code) {
const token = await oAuth2Client.getToken(code);
const key = token.tokens;
const grantedScopes = token.tokens.scope?.split(" ") ?? [];
// Check if we have granted all required permissions
const hasMissingRequiredScopes = REQUIRED_SCOPES.some((scope) => !grantedScopes.includes(scope));
const hasMissingRequiredScopes = GOOGLE_CALENDAR_SCOPES.some((scope) => !grantedScopes.includes(scope));
if (hasMissingRequiredScopes) {
if (!state?.fromApp) {
throw new HttpError({
Expand All @@ -63,29 +68,26 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
return;
}

// Set the primary calendar as the first selected calendar

oAuth2Client.setCredentials(key);

const calendar = google.calendar({
version: "v3",
auth: oAuth2Client,
const gcalCredential = await CredentialRepository.create({
userId: req.session.user.id,
key,
appId: "google-calendar",
type: "google_calendar",
});

const cals = await getAllCalendars(calendar);

const primaryCal = cals.find((cal) => cal.primary) ?? cals[0];

// Only attempt to update the user's profile photo if the user has granted the required scope
if (grantedScopes.includes(SCOPE_USERINFO_PROFILE)) {
await updateProfilePhoto(oAuth2Client, req.session.user.id);
}
const gCalService = new GoogleCalendarService({
...gcalCredential,
user: null,
});

const gcalCredential = await GoogleRepository.createGoogleCalendarCredential({
key,
userId: req.session.user.id,
const calendar = new calendar_v3.Calendar({
auth: oAuth2Client,
});

const primaryCal = await gCalService.getPrimaryCalendar(calendar);

// If we still don't have a primary calendar skip creating the selected calendar.
// It can be toggled on later.
if (!primaryCal?.id) {
Expand All @@ -96,6 +98,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
return;
}

// Only attempt to update the user's profile photo if the user has granted the required scope
if (grantedScopes.includes(SCOPE_USERINFO_PROFILE)) {
await updateProfilePhotoGoogle(oAuth2Client, req.session.user.id);
}

const selectedCalendarWhereUnique = {
userId: req.session.user.id,
externalId: primaryCal.id,
Expand All @@ -105,10 +112,8 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
// Wrapping in a try/catch to reduce chance of race conditions-
// also this improves performance for most of the happy-paths.
try {
await GoogleRepository.upsertSelectedCalendar({
credentialId: gcalCredential.id,
await gCalService.upsertSelectedCalendar({
externalId: selectedCalendarWhereUnique.externalId,
userId: selectedCalendarWhereUnique.userId,
});
} catch (error) {
let errorMessage = "something_went_wrong";
Expand Down Expand Up @@ -145,8 +150,9 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
return;
}

const existingGoogleMeetCredential = await GoogleRepository.findGoogleMeetCredential({
const existingGoogleMeetCredential = await CredentialRepository.findFirstByUserIdAndType({
userId: req.session.user.id,
type: "google_video",
});

// If the user already has a google meet credential, there's nothing to do in here
Expand All @@ -159,7 +165,12 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
}

// Create a new google meet credential
await GoogleRepository.createGoogleMeetsCredential({ userId: req.session.user.id });
await CredentialRepository.create({
userId: req.session.user.id,
type: "google_video",
key: {},
appId: "google-meet",
});
res.redirect(
getSafeRedirectUrl(`${WEBAPP_URL}/apps/installed/conferencing?hl=google-meet`) ??
getInstalledAppPath({ variant: "conferencing", slug: "google-meet" })
Expand Down
20 changes: 17 additions & 3 deletions packages/app-store/googlecalendar/lib/CalendarService.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import prismock from "../../../../tests/libs/__mocks__/prisma";
import oAuthManagerMock, { defaultMockOAuthManager } from "../../tests/__mocks__/OAuthManager";
import { googleapisMock, setCredentialsMock } from "./__mocks__/googleapis";
import { adminMock, calendarMock, setCredentialsMock } from "./__mocks__/googleapis";

import { expect, test, vi } from "vitest";
import { expect, test, beforeEach, vi } from "vitest";
import "vitest-fetch-mock";

import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache";
Expand All @@ -22,7 +22,21 @@ vi.mock("./getGoogleAppKeys", () => ({
redirect_uris: ["http://localhost:3000/api/integrations/googlecalendar/callback"],
}),
}));
googleapisMock.google;

vi.mock("googleapis-common", () => ({
OAuth2Client: vi.fn().mockImplementation(() => ({
setCredentials: setCredentialsMock,
})),
}));
vi.mock("@googleapis/admin", () => adminMock);
vi.mock("@googleapis/calendar", () => calendarMock);

beforeEach(() => {
vi.clearAllMocks();
setCredentialsMock.mockClear();
calendarMock.calendar_v3.Calendar.mockClear();
adminMock.admin_directory_v1.Admin.mockClear();
});

const googleTestCredentialKey = {
scope: "https://www.googleapis.com/auth/calendar.events",
Expand Down
Loading

0 comments on commit b8219c2

Please sign in to comment.
0