fix: Enable Salesforce & CRMs for teams & orgs [CAL-2116] (#9973)
Co-authored-by: Leo Giovanetti <hello@leog.me>
This commit is contained in:
1 parent
6e15de31aa
commit
267422a1f4
8 files changed
+87
-7
No files matched your search
@@ -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 });
|
||||
}
|
||||
@@ -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,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),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -46,6 +46,7 @@ export const userSelect = Prisma.validator<Prisma.UserArgs>()({
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
metadata: true,
|
||||
organizationId: true,
|
||||
...availabilityUserSelect,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user