feat: install gcal + gvideo by default for google signups (#17773)
* feat: install gcal + gvideo by default for google signups * add upsertSelectedCalendar * fix type check
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { google } from "googleapis";
|
||||
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) {
|
||||
@@ -16,7 +15,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
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
|
||||
|
||||
@@ -2,18 +2,21 @@ import { google } from "googleapis";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
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 { GoogleService } from "@calcom/lib/server/service/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) {
|
||||
@@ -47,7 +50,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
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({
|
||||
@@ -72,19 +75,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
auth: oAuth2Client,
|
||||
});
|
||||
|
||||
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 gcalCredential = await GoogleRepository.createGoogleCalendarCredential({
|
||||
key,
|
||||
userId: req.session.user.id,
|
||||
});
|
||||
const primaryCal = await GoogleService.getPrimaryCalendar(calendar);
|
||||
|
||||
// If we still don't have a primary calendar skip creating the selected calendar.
|
||||
// It can be toggled on later.
|
||||
@@ -96,6 +87,16 @@ 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 GoogleService.updateProfilePhoto(oAuth2Client, req.session.user.id);
|
||||
}
|
||||
|
||||
const gcalCredential = await GoogleService.createGoogleCalendarCredential({
|
||||
key,
|
||||
userId: req.session.user.id,
|
||||
});
|
||||
|
||||
const selectedCalendarWhereUnique = {
|
||||
userId: req.session.user.id,
|
||||
externalId: primaryCal.id,
|
||||
@@ -105,7 +106,7 @@ 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({
|
||||
await GoogleService.upsertSelectedCalendar({
|
||||
credentialId: gcalCredential.id,
|
||||
externalId: selectedCalendarWhereUnique.externalId,
|
||||
userId: selectedCalendarWhereUnique.userId,
|
||||
@@ -145,7 +146,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingGoogleMeetCredential = await GoogleRepository.findGoogleMeetCredential({
|
||||
const existingGoogleMeetCredential = await GoogleService.findGoogleMeetCredential({
|
||||
userId: req.session.user.id,
|
||||
});
|
||||
|
||||
@@ -159,7 +160,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
// Create a new google meet credential
|
||||
await GoogleRepository.createGoogleMeetsCredential({ userId: req.session.user.id });
|
||||
await GoogleService.createGoogleMeetsCredential({ userId: req.session.user.id });
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(`${WEBAPP_URL}/apps/installed/conferencing?hl=google-meet`) ??
|
||||
getInstalledAppPath({ variant: "conferencing", slug: "google-meet" })
|
||||
|
||||
@@ -18,10 +18,9 @@ import {
|
||||
CREDENTIAL_SYNC_SECRET_HEADER_NAME,
|
||||
} from "@calcom/lib/constants";
|
||||
import { formatCalEvent } from "@calcom/lib/formatCalendarEvent";
|
||||
import { getAllCalendars } from "@calcom/lib/google";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { GoogleRepository } from "@calcom/lib/server/repository/google";
|
||||
import { GoogleService } from "@calcom/lib/server/service/google";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type {
|
||||
Calendar,
|
||||
@@ -543,7 +542,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
}
|
||||
async function getCalIds() {
|
||||
if (selectedCalendarIds.length !== 0) return selectedCalendarIds;
|
||||
const cals = await getAllCalendars(calendar, ["id"]);
|
||||
const cals = await GoogleService.getAllCalendars(calendar, ["id"]);
|
||||
if (!cals.length) return [];
|
||||
return cals.reduce((c, cal) => (cal.id ? [...c, cal.id] : c), [] as string[]);
|
||||
}
|
||||
@@ -607,7 +606,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
data: {
|
||||
items: await getAllCalendars(calendar),
|
||||
items: await GoogleService.getAllCalendars(calendar),
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -652,7 +651,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
},
|
||||
});
|
||||
const response = res.data;
|
||||
await GoogleRepository.upsertSelectedCalendar({
|
||||
await GoogleService.upsertSelectedCalendar({
|
||||
userId: this.credential.userId!,
|
||||
externalId: calendarId,
|
||||
credentialId: this.credential.id,
|
||||
@@ -686,7 +685,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
.catch((err) => {
|
||||
console.warn(JSON.stringify(err));
|
||||
});
|
||||
await GoogleRepository.upsertSelectedCalendar({
|
||||
await GoogleService.upsertSelectedCalendar({
|
||||
userId: this.credential.userId!,
|
||||
externalId: calendarId,
|
||||
credentialId: this.credential.id,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const SCOPE_USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile";
|
||||
export const SCOPE_CALENDAR_READONLY = "https://www.googleapis.com/auth/calendar.readonly";
|
||||
export const SCOPE_CALENDAR_EVENT = "https://www.googleapis.com/auth/calendar.events";
|
||||
|
||||
export const REQUIRED_SCOPES = [SCOPE_CALENDAR_READONLY, SCOPE_CALENDAR_EVENT];
|
||||
|
||||
export const SCOPES = [...REQUIRED_SCOPES, SCOPE_USERINFO_PROFILE];
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
export async function getAllCalendars(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry[]> {
|
||||
let allCalendars: calendar_v3.Schema$CalendarListEntry[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
allCalendars = [...allCalendars, ...(response.data.items ?? [])];
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return allCalendars;
|
||||
} catch (error) {
|
||||
logger.error("Error fetching all Google Calendars", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Membership, Team, UserPermissionRole } from "@prisma/client";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { google } from "googleapis";
|
||||
import type { AuthOptions, Session, User } from "next-auth";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { encode } from "next-auth/jwt";
|
||||
@@ -15,7 +16,12 @@ import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/Imperso
|
||||
import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HOSTED_CAL_FEATURES, IS_CALCOM } from "@calcom/lib/constants";
|
||||
import {
|
||||
GOOGLE_CALENDAR_SCOPES,
|
||||
GOOGLE_OAUTH_SCOPES,
|
||||
HOSTED_CAL_FEATURES,
|
||||
IS_CALCOM,
|
||||
} from "@calcom/lib/constants";
|
||||
import { ENABLE_PROFILE_SWITCHER, IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "@calcom/lib/crypto";
|
||||
import { defaultCookies } from "@calcom/lib/default-cookies";
|
||||
@@ -25,6 +31,7 @@ import { randomString } from "@calcom/lib/random";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
import { GoogleService } from "@calcom/lib/server/service/google";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
|
||||
@@ -251,6 +258,13 @@ if (IS_GOOGLE_LOGIN_ENABLED) {
|
||||
clientId: GOOGLE_CLIENT_ID,
|
||||
clientSecret: GOOGLE_CLIENT_SECRET,
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
authorization: {
|
||||
params: {
|
||||
scope: [...GOOGLE_OAUTH_SCOPES, ...GOOGLE_CALENDAR_SCOPES].join(" "),
|
||||
access_type: "offline",
|
||||
prompt: "consent",
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -608,6 +622,55 @@ export const getOptions = ({
|
||||
return await autoMergeIdentities();
|
||||
}
|
||||
|
||||
const grantedScopes = account.scope?.split(" ") ?? [];
|
||||
if (
|
||||
account.provider === "google" &&
|
||||
!(await GoogleService.findFirstGoogleCalendarCredential({
|
||||
userId: user.id as number,
|
||||
})) &&
|
||||
GOOGLE_CALENDAR_SCOPES.every((scope) => grantedScopes.includes(scope))
|
||||
) {
|
||||
// Installing Google Calendar by default
|
||||
const credentialkey = {
|
||||
access_token: account.access_token,
|
||||
refresh_token: account.refresh_token,
|
||||
id_token: account.id_token,
|
||||
token_type: account.token_type,
|
||||
expires_at: account.expires_at,
|
||||
};
|
||||
|
||||
const gcalCredential = await GoogleService.createGoogleCalendarCredential({
|
||||
userId: user.id as number,
|
||||
key: credentialkey,
|
||||
});
|
||||
|
||||
if (
|
||||
!(await GoogleService.findGoogleMeetCredential({
|
||||
userId: user.id as number,
|
||||
}))
|
||||
) {
|
||||
await GoogleService.createGoogleMeetsCredential({
|
||||
userId: user.id as number,
|
||||
});
|
||||
}
|
||||
|
||||
const oAuth2Client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET);
|
||||
oAuth2Client.setCredentials(credentialkey);
|
||||
const calendar = google.calendar({
|
||||
version: "v3",
|
||||
auth: oAuth2Client,
|
||||
});
|
||||
const primaryCal = await GoogleService.getPrimaryCalendar(calendar);
|
||||
if (primaryCal?.id) {
|
||||
await GoogleService.createSelectedCalendar({
|
||||
credentialId: gcalCredential.id,
|
||||
userId: user.id as number,
|
||||
externalId: primaryCal.id,
|
||||
});
|
||||
}
|
||||
await GoogleService.updateProfilePhoto(oAuth2Client, user.id as number);
|
||||
}
|
||||
|
||||
return {
|
||||
...token,
|
||||
id: existingUser.id,
|
||||
@@ -632,7 +695,6 @@ export const getOptions = ({
|
||||
log.debug("callbacks:session - Session callback called", safeStringify({ session, token, user }));
|
||||
const licenseKeyService = await LicenseKeySingleton.getInstance();
|
||||
const hasValidLicense = await licenseKeyService.checkLicense();
|
||||
|
||||
const profileId = token.profileId;
|
||||
const calendsoSession: Session = {
|
||||
...session,
|
||||
|
||||
@@ -192,3 +192,10 @@ export const RECORDING_IN_PROGRESS_ICON = IS_PRODUCTION
|
||||
? `${WEBAPP_URL}/stop-recording.svg`
|
||||
: `https://app.cal.com/stop-recording.svg`;
|
||||
|
||||
export const SCOPE_USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile";
|
||||
export const SCOPE_USERINFO_EMAIL = "https://www.googleapis.com/auth/userinfo.email";
|
||||
export const GOOGLE_OAUTH_SCOPES = [SCOPE_USERINFO_PROFILE, SCOPE_USERINFO_EMAIL];
|
||||
export const GOOGLE_CALENDAR_SCOPES = [
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.readonly",
|
||||
];
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { OAuth2Client } from "google-auth-library";
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
import { google } from "googleapis";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
import { UserRepository } from "./server/repository/user";
|
||||
|
||||
export async function getAllCalendars(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry[]> {
|
||||
let allCalendars: calendar_v3.Schema$CalendarListEntry[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
allCalendars = [...allCalendars, ...(response.data.items ?? [])];
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return allCalendars;
|
||||
} catch (error) {
|
||||
logger.error("Error fetching all Google Calendars", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateProfilePhoto(oAuth2Client: OAuth2Client, userId: number) {
|
||||
try {
|
||||
const oauth2 = google.oauth2({ version: "v2", 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);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,15 @@ export class CredentialRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async findFirstByAppIdAndUserId({ appId, userId }: { appId: string; userId: number }) {
|
||||
return await prisma.credential.findFirst({
|
||||
where: {
|
||||
appId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async findFirstByUserIdAndType({ userId, type }: { userId: number; type: string }) {
|
||||
return await prisma.credential.findFirst({ where: { userId, type } });
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { Credentials } from "google-auth-library";
|
||||
|
||||
import { CredentialRepository } from "./credential";
|
||||
import { SelectedCalendarRepository } from "./selectedCalendar";
|
||||
|
||||
export class GoogleRepository {
|
||||
static async createGoogleCalendarCredential({ userId, key }: { userId: number; key: Credentials }) {
|
||||
return await CredentialRepository.create({
|
||||
type: "google_calendar",
|
||||
key,
|
||||
userId,
|
||||
appId: "google-calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async createGoogleMeetsCredential({ userId }: { userId: number }) {
|
||||
return await CredentialRepository.create({
|
||||
type: "google_video",
|
||||
key: {},
|
||||
userId,
|
||||
appId: "google-meet",
|
||||
});
|
||||
}
|
||||
|
||||
static async createSelectedCalendar(data: { credentialId: number; userId: number; externalId: string }) {
|
||||
return await SelectedCalendarRepository.create({
|
||||
...data,
|
||||
integration: "google_calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async upsertSelectedCalendar(
|
||||
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration">
|
||||
) {
|
||||
return await SelectedCalendarRepository.upsert({
|
||||
...data,
|
||||
integration: "google_calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async findGoogleMeetCredential({ userId }: { userId: number }) {
|
||||
return await CredentialRepository.findFirstByUserIdAndType({
|
||||
userId,
|
||||
type: "google_video",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,8 @@ import type { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
|
||||
type SelectedCalendarCreateInput = {
|
||||
credentialId: number;
|
||||
userId: number;
|
||||
externalId: string;
|
||||
integration: string;
|
||||
};
|
||||
|
||||
export class SelectedCalendarRepository {
|
||||
static async create(data: SelectedCalendarCreateInput) {
|
||||
static async create(data: Prisma.SelectedCalendarUncheckedCreateInput) {
|
||||
return await prisma.selectedCalendar.create({
|
||||
data: {
|
||||
...data,
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { Credentials } from "google-auth-library";
|
||||
import type { OAuth2Client } from "google-auth-library";
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
import { google } from "googleapis";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
import { CredentialRepository } from "../repository/credential";
|
||||
import { SelectedCalendarRepository } from "../repository/selectedCalendar";
|
||||
import { UserRepository } from "../repository/user";
|
||||
|
||||
export class GoogleService {
|
||||
static async createGoogleCalendarCredential({ userId, key }: { userId: number; key: Credentials }) {
|
||||
return await CredentialRepository.create({
|
||||
type: "google_calendar",
|
||||
key,
|
||||
userId,
|
||||
appId: "google-calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async createGoogleMeetsCredential({ userId }: { userId: number }) {
|
||||
return await CredentialRepository.create({
|
||||
type: "google_video",
|
||||
key: {},
|
||||
userId,
|
||||
appId: "google-meet",
|
||||
});
|
||||
}
|
||||
|
||||
static async createSelectedCalendar(
|
||||
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration">
|
||||
) {
|
||||
return await SelectedCalendarRepository.create({
|
||||
...data,
|
||||
integration: "google_calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async upsertSelectedCalendar(
|
||||
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration">
|
||||
) {
|
||||
return await SelectedCalendarRepository.upsert({
|
||||
...data,
|
||||
integration: "google_calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async findGoogleMeetCredential({ userId }: { userId: number }) {
|
||||
return await CredentialRepository.findFirstByUserIdAndType({
|
||||
userId,
|
||||
type: "google_video",
|
||||
});
|
||||
}
|
||||
|
||||
static async findFirstGoogleCalendarCredential({ userId }: { userId: number }) {
|
||||
return await CredentialRepository.findFirstByAppIdAndUserId({
|
||||
appId: "google-calendar",
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
static async getAllCalendars(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry[]> {
|
||||
let allCalendars: calendar_v3.Schema$CalendarListEntry[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
allCalendars = [...allCalendars, ...(response.data.items ?? [])];
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return allCalendars;
|
||||
} catch (error) {
|
||||
logger.error("Error fetching all Google Calendars", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getPrimaryCalendar(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry | null> {
|
||||
let pageToken: string | undefined;
|
||||
let firstCalendar: calendar_v3.Schema$CalendarListEntry | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
const cals = response.data.items ?? [];
|
||||
const primaryCal = cals.find((cal: calendar_v3.Schema$CalendarListEntry) => cal.primary);
|
||||
if (primaryCal) {
|
||||
return primaryCal;
|
||||
}
|
||||
|
||||
// Store the first calendar in case no primary is found
|
||||
if (cals.length > 0 && !firstCalendar) {
|
||||
firstCalendar = cals[0];
|
||||
}
|
||||
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
// should not be reached because Google Cal always has a primary cal
|
||||
return firstCalendar ?? null;
|
||||
} catch (error) {
|
||||
logger.error("Error in `getPrimaryCalendar`", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async updateProfilePhoto(oAuth2Client: OAuth2Client, userId: number) {
|
||||
try {
|
||||
const oauth2 = google.oauth2({ version: "v2", 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user