fix: Enable Salesforce & CRMs for teams & orgs [CAL-2116] (#9973)

Co-authored-by: Leo Giovanetti <hello@leog.me>
This commit is contained in:
Joe Au-YeungandLeo Giovanetti authored and GitHub committed 2023-07-11 13:11:21 +05:30
1 parent 6e15de31aa
commit 267422a1f4
8 files changed
+87 -7

No files matched your search

+8 -1
View File
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"];
@@ -18,6 +19,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." });
const redirectUri = WEBAPP_URL + "/api/integrations/hubspot/callback";
const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" "));
const url = hubspotClient.oauth.getAuthorizationUrl(
client_id,
redirectUri,
scopes.join(" "),
undefined,
encodeOAuthState(req)
);
res.status(200).json({ url });
}
+5 -1
View File
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
let consumer_key = "";
@@ -19,6 +20,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
redirectUri: `${WEBAPP_URL}/api/integrations/salesforce/callback`,
});
const url = salesforceClient.oauth2.getAuthorizationUrl({ scope: "refresh_token full" });
const url = salesforceClient.oauth2.getAuthorizationUrl({
scope: "refresh_token full",
state: encodeOAuthState(req),
});
res.status(200).json({ url });
}
@@ -79,7 +79,7 @@ export default class SalesforceCalendarService implements Calendar {
.sobject("Contact")
.create({
FirstName,
LastName,
LastName: LastName || "",
Email: attendee.email,
})
.then((result) => {
+3
View File
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import appConfig from "../config.json";
@@ -14,6 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (!clientId) return res.status(400).json({ message: "Zoho Bigin client_id missing." });
const redirectUri = WEBAPP_URL + `/api/integrations/${appConfig.slug}/callback`;
const authUrl = axios.getUri({
url: "https://accounts.zoho.com/oauth/v2/auth",
params: {
@@ -22,6 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
response_type: "code",
redirect_uri: redirectUri,
access_type: "offline",
state: encodeOAuthState(req),
},
});
+3 -1
View File
@@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
let client_id = "";
@@ -10,8 +11,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const appKeys = await getAppKeysFromSlug("zohocrm");
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
if (!client_id) return res.status(400).json({ message: "zohocrm client id missing." });
const state = encodeOAuthState(req);
const redirectUri = WEBAPP_URL + "/api/integrations/zohocrm/callback";
const url = `https://accounts.zoho.com/oauth/v2/auth?scope=ZohoCRM.modules.ALL,ZohoCRM.users.READ,AaaServer.profile.READ&client_id=${client_id}&response_type=code&access_type=offline&redirect_uri=${redirectUri}`;
const url = `https://accounts.zoho.com/oauth/v2/auth?scope=ZohoCRM.modules.ALL,ZohoCRM.users.READ,AaaServer.profile.READ&client_id=${client_id}&response_type=code&access_type=offline&redirect_uri=${redirectUri}&state=${state}`;
res.status(200).json({ url });
}
@@ -127,6 +127,64 @@ async function refreshCredentials(credentials: Array<Credential>): Promise<Array
return await async.mapLimit(credentials, 5, refreshCredential);
}
/**
* Gets credentials from the user, team, and org if applicable
*
*/
const getAllCredentials = async (
user: User & { credentials: Credential[] },
eventType: Awaited<ReturnType<typeof getEventTypesFromDB>>
) => {
const allCredentials = user.credentials;
// If it's a team event type query for team credentials
if (eventType.team?.id) {
const teamCredentialsQuery = await prisma.credential.findMany({
where: {
teamId: eventType.team.id,
},
});
allCredentials.push(...teamCredentialsQuery);
}
// If it's a managed event type, query for the parent team's credentials
if (eventType.parentId) {
const teamCredentialsQuery = await prisma.team.findFirst({
where: {
eventTypes: {
some: {
id: eventType.parentId,
},
},
},
select: {
credentials: true,
},
});
if (teamCredentialsQuery?.credentials) {
allCredentials.push(...teamCredentialsQuery?.credentials);
}
}
// If the user is a part of an organization, query for the organization's credentials
if (user?.organizationId) {
const org = await prisma.team.findUnique({
where: {
id: user.organizationId,
},
select: {
credentials: true,
},
});
if (org?.credentials) {
allCredentials.push(...org.credentials);
}
}
return allCredentials;
};
const isWithinAvailableHours = (
timeSlot: { start: ConfigType; end: ConfigType },
{
@@ -267,6 +325,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
seatsShowAttendees: true,
bookingLimits: true,
durationLimits: true,
parentId: true,
owner: {
select: {
hideBranding: true,
@@ -837,6 +896,8 @@ async function handler(
const [organizerUser] = users;
const tOrganizer = await getTranslation(organizerUser?.locale ?? "en", "common");
const allCredentials = await getAllCredentials(organizerUser, eventType);
// use host default
if (isTeamEventType && locationBodyString === OrganizerDefaultConferencingAppType) {
const metadataParseResult = userMetadataSchema.safeParse(organizerUser.metadata);
@@ -1115,7 +1176,7 @@ async function handler(
},
});
const credentials = await refreshCredentials(organizerUser.credentials);
const credentials = await refreshCredentials(allCredentials);
const eventManager = new EventManager({ ...organizerUser, credentials });
if (!originalRescheduledBooking) {
@@ -1535,7 +1596,7 @@ async function handler(
copyEvent.uid = booking.uid;
await sendScheduledSeatsEmails(copyEvent, invitee[0], newSeat, !!eventType.seatsShowAttendees);
const credentials = await refreshCredentials(organizerUser.credentials);
const credentials = await refreshCredentials(allCredentials);
const eventManager = new EventManager({ ...organizerUser, credentials });
await eventManager.updateCalendarAttendees(evt, booking);
@@ -1839,7 +1900,7 @@ async function handler(
}
// After polling videoBusyTimes, credentials might have been changed due to refreshment, so query them again.
const credentials = await refreshCredentials(organizerUser.credentials);
const credentials = await refreshCredentials(allCredentials);
const eventManager = new EventManager({ ...organizerUser, credentials });
function handleAppsStatus(
+2
View File
@@ -50,6 +50,7 @@ const user: User & { credentials: Credential[] } = {
darkBrandColor: "#efefef",
allowDynamicBooking: true,
timeFormat: 12,
organizationId: null,
};
const customInputs: CustomInputSchema[] = [];
@@ -90,6 +91,7 @@ const commons = {
durationLimits: null,
hidden: false,
userId: 0,
parentId: null,
owner: null,
workflows: [],
users: [user],
+1
View File
@@ -46,6 +46,7 @@ export const userSelect = Prisma.validator<Prisma.UserArgs>()({
brandColor: true,
darkBrandColor: true,
metadata: true,
organizationId: true,
...availabilityUserSelect,
},
});