diff --git a/packages/app-store/apps.metadata.generated.ts b/packages/app-store/apps.metadata.generated.ts index be1fcb4323..df6120d107 100644 --- a/packages/app-store/apps.metadata.generated.ts +++ b/packages/app-store/apps.metadata.generated.ts @@ -30,7 +30,6 @@ import { metadata as qr_code_meta } from "./qr_code/_metadata"; import { metadata as rainbow_meta } from "./rainbow/_metadata"; import { metadata as raycast_meta } from "./raycast/_metadata"; import { metadata as riverside_meta } from "./riverside/_metadata"; -import { metadata as salesforce_meta } from "./salesforce/_metadata"; import { metadata as sendgrid_meta } from "./sendgrid/_metadata"; import { metadata as signal_meta } from "./signal/_metadata"; import { metadata as sirius_video_meta } from "./sirius_video/_metadata"; @@ -74,7 +73,6 @@ export const appStoreMetadata = { rainbow: rainbow_meta, raycast: raycast_meta, riverside: riverside_meta, - salesforce: salesforce_meta, sendgrid: sendgrid_meta, signal: signal_meta, sirius_video: sirius_video_meta, diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts index eacbf52fdd..e4f65ae098 100644 --- a/packages/app-store/apps.server.generated.ts +++ b/packages/app-store/apps.server.generated.ts @@ -29,7 +29,6 @@ export const apiHandlers = { rainbow: import("./rainbow/api"), raycast: import("./raycast/api"), riverside: import("./riverside/api"), - salesforce: import("./salesforce/api"), sendgrid: import("./sendgrid/api"), signal: import("./signal/api"), sirius_video: import("./sirius_video/api"), diff --git a/packages/app-store/index.ts b/packages/app-store/index.ts index 6c678cd4f9..3fc35571bd 100644 --- a/packages/app-store/index.ts +++ b/packages/app-store/index.ts @@ -15,7 +15,6 @@ import * as jitsivideo from "./jitsivideo"; import * as larkcalendar from "./larkcalendar"; import * as office365calendar from "./office365calendar"; import * as office365video from "./office365video"; -import * as salesforce from "./salesforce"; import * as sendgrid from "./sendgrid"; import * as stripepayment from "./stripepayment"; import * as tandemvideo from "./tandemvideo"; @@ -38,7 +37,6 @@ const appStore = { larkcalendar, office365calendar, office365video, - salesforce, sendgrid, stripepayment, tandemvideo, diff --git a/packages/app-store/salesforce/README.mdx b/packages/app-store/salesforce/README.mdx deleted file mode 100644 index 5188df3107..0000000000 --- a/packages/app-store/salesforce/README.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: Salesforce (Sales Cloud) is a cloud-based application designed to help your salespeople sell smarter and faster by centralizing customer information, logging their interactions with your company, and automating many of the tasks salespeople do every day. -items: - - /api/app-store/salesforce/1.png ---- - -{description} -
-

Features:

- diff --git a/packages/app-store/salesforce/_metadata.ts b/packages/app-store/salesforce/_metadata.ts deleted file mode 100644 index 9c7f2aa320..0000000000 --- a/packages/app-store/salesforce/_metadata.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { AppMeta } from "@calcom/types/App"; - -import config from "./config.json"; - -export const metadata = { - category: "other", - ...config, -} as AppMeta; - -export default metadata; diff --git a/packages/app-store/salesforce/api/add.ts b/packages/app-store/salesforce/api/add.ts deleted file mode 100644 index e56d882e21..0000000000 --- a/packages/app-store/salesforce/api/add.ts +++ /dev/null @@ -1,24 +0,0 @@ -import jsforce from "jsforce"; -import type { NextApiRequest, NextApiResponse } from "next"; - -import { WEBAPP_URL } from "@calcom/lib/constants"; - -import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; - -let consumer_key = ""; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" }); - - const appKeys = await getAppKeysFromSlug("salesforce"); - if (typeof appKeys.consumer_key === "string") consumer_key = appKeys.consumer_key; - if (!consumer_key) return res.status(400).json({ message: "Salesforce client id missing." }); - - const salesforceClient = new jsforce.Connection({ - clientId: consumer_key, - redirectUri: `${WEBAPP_URL}/api/integrations/salesforce/callback`, - }); - - const url = salesforceClient.oauth2.getAuthorizationUrl({ scope: "refresh_token full" }); - res.status(200).json({ url }); -} diff --git a/packages/app-store/salesforce/api/callback.ts b/packages/app-store/salesforce/api/callback.ts deleted file mode 100644 index 178447b9b4..0000000000 --- a/packages/app-store/salesforce/api/callback.ts +++ /dev/null @@ -1,55 +0,0 @@ -import jsforce from "jsforce"; -import type { NextApiRequest, NextApiResponse } from "next"; - -import { WEBAPP_URL } from "@calcom/lib/constants"; -import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; -import prisma from "@calcom/prisma"; - -import { decodeOAuthState } from "../../_utils/decodeOAuthState"; -import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import getInstalledAppPath from "../../_utils/getInstalledAppPath"; - -let consumer_key = ""; -let consumer_secret = ""; -const instance_url = ""; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { code } = req.query; - - if (code === undefined && typeof code !== "string") { - res.status(400).json({ message: "`code` must be a string" }); - return; - } - - if (!req.session?.user?.id) { - return res.status(401).json({ message: "You must be logged in to do this" }); - } - - const appKeys = await getAppKeysFromSlug("salesforce"); - if (typeof appKeys.consumer_key === "string") consumer_key = appKeys.consumer_key; - if (typeof appKeys.consumer_secret === "string") consumer_secret = appKeys.consumer_secret; - if (!consumer_key) return res.status(400).json({ message: "Salesforce consumer key missing." }); - if (!consumer_secret) return res.status(400).json({ message: "Salesforce consumer secret missing." }); - - const conn = new jsforce.Connection({ - clientId: consumer_key, - clientSecret: consumer_secret, - redirectUri: WEBAPP_URL + "/api/integrations/salesforce/callback", - }); - - const salesforceTokenInfo = await conn.oauth2.requestToken(code as string); - - await prisma.credential.create({ - data: { - type: "salesforce_other_calendar", - key: salesforceTokenInfo as any, - userId: req.session.user.id, - appId: "salesforce", - }, - }); - - const state = decodeOAuthState(req); - res.redirect( - getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: "other", slug: "salesforce" }) - ); -} diff --git a/packages/app-store/salesforce/api/index.ts b/packages/app-store/salesforce/api/index.ts deleted file mode 100644 index eb12c1b4ed..0000000000 --- a/packages/app-store/salesforce/api/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as add } from "./add"; -export { default as callback } from "./callback"; diff --git a/packages/app-store/salesforce/components/.gitkeep b/packages/app-store/salesforce/components/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/app-store/salesforce/config.json b/packages/app-store/salesforce/config.json deleted file mode 100644 index 52e0ad3308..0000000000 --- a/packages/app-store/salesforce/config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "/*": "Don't modify slug - If required, do it using cli edit command", - "name": "Salesforce", - "slug": "salesforce", - "type": "salesforce_other_calendar", - "imageSrc": "/api/app-store/salesforce/icon.png", - "logo": "/api/app-store/salesforce/icon.png", - "url": "https://cal.com/apps/salesforce", - "variant": "other_calendar", - "categories": ["other"], - "publisher": "Cal.com", - "email": "help@cal.com", - "description": "Salesforce (Sales Cloud) is a cloud-based application designed to help your salespeople sell smarter and faster by centralizing customer information, logging their interactions with your company, and automating many of the tasks salespeople do every day.", - "extendsFeature": "User", - "__createdUsingCli": true -} diff --git a/packages/app-store/salesforce/index.ts b/packages/app-store/salesforce/index.ts deleted file mode 100644 index 5373eb04ef..0000000000 --- a/packages/app-store/salesforce/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * as api from "./api"; -export * as lib from "./lib"; -export { metadata } from "./_metadata"; diff --git a/packages/app-store/salesforce/lib/CalendarService.ts b/packages/app-store/salesforce/lib/CalendarService.ts deleted file mode 100644 index 75798c1dc8..0000000000 --- a/packages/app-store/salesforce/lib/CalendarService.ts +++ /dev/null @@ -1,275 +0,0 @@ -import jsforce, { TokenResponse } from "jsforce"; -import { RRule } from "rrule"; - -import { getLocation } from "@calcom/lib/CalEventParser"; -import { WEBAPP_URL } from "@calcom/lib/constants"; -import { HttpError } from "@calcom/lib/http-error"; -import logger from "@calcom/lib/logger"; -import prisma from "@calcom/prisma"; -import type { - Calendar, - CalendarEvent, - IntegrationCalendar, - NewCalendarEventType, - Person, -} from "@calcom/types/Calendar"; -import { CredentialPayload } from "@calcom/types/Credential"; - -import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; - -type ExtendedTokenResponse = TokenResponse & { - instance_url: string; -}; - -type ContactSearchResult = { - attributes: { - type: string; - url: string; - }; - Id: string; - Email: string; -}; - -const sfApiErrors = { - INVALID_EVENTWHOIDS: "INVALID_FIELD: No such column 'EventWhoIds' on sobject of type Event", -}; - -export default class SalesforceCalendarService implements Calendar { - private integrationName = ""; - private conn: Promise; - private log: typeof logger; - private calWarnings: string[] = []; - - constructor(credential: CredentialPayload) { - this.integrationName = "salesforce_other_calendar"; - this.conn = this.getClient(credential).then((c) => c); - this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] }); - } - - private getClient = async (credential: CredentialPayload) => { - let consumer_key = ""; - let consumer_secret = ""; - - const appKeys = await getAppKeysFromSlug("salesforce"); - if (typeof appKeys.consumer_key === "string") consumer_key = appKeys.consumer_key; - if (typeof appKeys.consumer_secret === "string") consumer_secret = appKeys.consumer_secret; - if (!consumer_key) - throw new HttpError({ statusCode: 400, message: "Salesforce consumer key is missing." }); - if (!consumer_secret) - throw new HttpError({ statusCode: 400, message: "Salesforce consumer secret missing." }); - - const credentialKey = credential.key as unknown as ExtendedTokenResponse; - - return new jsforce.Connection({ - clientId: consumer_key, - clientSecret: consumer_secret, - redirectUri: WEBAPP_URL + "/api/integrations/salesforce/callback", - instanceUrl: credentialKey.instance_url, - accessToken: credentialKey.access_token, - refreshToken: credentialKey.refresh_token, - }); - }; - - private salesforceContactCreate = async (attendees: Person[]) => { - const conn = await this.conn; - const createdContacts = await Promise.all( - attendees.map(async (attendee) => { - const [FirstName, LastName] = attendee.name ? attendee.name.split(" ") : [attendee.email, ""]; - return await conn - .sobject("Contact") - .create({ - FirstName, - LastName, - Email: attendee.email, - }) - .then((result) => { - if (result.success) { - return { Id: result.id, Email: attendee.email }; - } - }); - }) - ); - return createdContacts.filter( - (contact): contact is Omit => contact !== undefined - ); - }; - - private salesforceContactSearch = async (event: CalendarEvent) => { - const conn = await this.conn; - const search: ContactSearchResult[] = await conn.sobject("Contact").find( - event.attendees.map((att) => ({ Email: att.email })), - ["Id", "Email"] - ); - return search; - }; - - private getSalesforceEventBody = (event: CalendarEvent): string => { - return `${event.organizer.language.translate("invitee_timezone")}: ${ - event.attendees[0].timeZone - } \r\n\r\n ${event.organizer.language.translate("share_additional_notes")}\r\n${ - event.additionalNotes || "-" - }`; - }; - - private salesforceCreateEventApiCall = async ( - event: CalendarEvent, - options: { [key: string]: unknown } - ) => { - const conn = await this.conn; - return await conn.sobject("Event").create({ - StartDateTime: new Date(event.startTime).toISOString(), - EndDateTime: new Date(event.endTime).toISOString(), - Subject: event.title, - Description: this.getSalesforceEventBody(event), - Location: getLocation(event), - ...options, - ...(event.recurringEvent && { - IsRecurrence2: true, - Recurrence2PatternText: new RRule(event.recurringEvent).toString(), - }), - }); - }; - - private salesforceCreateEvent = async ( - event: CalendarEvent, - contacts: Omit[] - ) => { - const createdEvent = await this.salesforceCreateEventApiCall(event, { - EventWhoIds: contacts.map((contact) => contact.Id), - }).catch(async (reason) => { - if (reason === sfApiErrors.INVALID_EVENTWHOIDS) { - this.calWarnings.push( - `Please enable option "Allow Users to Relate Multiple Contacts to Tasks and Events" under - "Setup > Feature Settings > Sales > Activity Settings" to be able to create events with - multiple contact attendees.` - ); - // User has not configured "Allow Users to Relate Multiple Contacts to Tasks and Events" - // proceeding to create the event using just the first attendee as the primary WhoId - return await this.salesforceCreateEventApiCall(event, { - WhoId: contacts[0], - }); - } else { - return Promise.reject(); - } - }); - return createdEvent; - }; - - private salesforceUpdateEvent = async (uid: string, event: CalendarEvent) => { - const conn = await this.conn; - return await conn.sobject("Event").update({ - Id: uid, - StartDateTime: new Date(event.startTime).toISOString(), - EndDateTime: new Date(event.endTime).toISOString(), - Subject: event.title, - Description: this.getSalesforceEventBody(event), - Location: getLocation(event), - ...(event.recurringEvent && { - IsRecurrence2: true, - Recurrence2PatternText: new RRule(event.recurringEvent).toString(), - }), - }); - }; - - private salesforceDeleteEvent = async (uid: string) => { - const conn = await this.conn; - return await conn.sobject("Event").delete(uid); - }; - - async handleEventCreation(event: CalendarEvent, contacts: Omit[]) { - const sfEvent = await this.salesforceCreateEvent(event, contacts); - if (sfEvent.success) { - this.log.debug("event:creation:ok", { sfEvent }); - return Promise.resolve({ - uid: sfEvent.id, - id: sfEvent.id, - type: "salesforce_other_calendar", - password: "", - url: "", - additionalInfo: { contacts, sfEvent, calWarnings: this.calWarnings }, - }); - } - this.log.debug("event:creation:notOk", { event, sfEvent, contacts }); - return Promise.reject({ - calError: "Something went wrong when creating an event in Salesforce", - }); - } - - async createEvent(event: CalendarEvent): Promise { - debugger; - const contacts = await this.salesforceContactSearch(event); - if (contacts.length) { - if (contacts.length == event.attendees.length) { - // All attendees do exist in Salesforce - this.log.debug("contact:search:all", { event, contacts }); - return await this.handleEventCreation(event, contacts); - } else { - // Some attendees don't exist in Salesforce - // Get the existing contacts' email to filter out - this.log.debug("contact:search:notAll", { event, contacts }); - const existingContacts = contacts.map((contact) => contact.Email); - this.log.debug("contact:filter:existing", { existingContacts }); - // Get non existing contacts filtering out existing from attendees - const nonExistingContacts = event.attendees.filter( - (attendee) => !existingContacts.includes(attendee.email) - ); - this.log.debug("contact:filter:nonExisting", { nonExistingContacts }); - // Only create contacts in Salesforce that were not present in the previous contact search - const createContacts = await this.salesforceContactCreate(nonExistingContacts); - this.log.debug("contact:created", { createContacts }); - // Continue with event creation and association only when all contacts are present in Salesforce - if (createContacts.length) { - this.log.debug("contact:creation:ok"); - return await this.handleEventCreation(event, createContacts.concat(contacts)); - } - return Promise.reject({ - calError: "Something went wrong when creating non-existing attendees in Salesforce", - }); - } - } else { - this.log.debug("contact:search:none", { event, contacts }); - const createContacts = await this.salesforceContactCreate(event.attendees); - this.log.debug("contact:created", { createContacts }); - if (createContacts.length) { - this.log.debug("contact:creation:ok"); - return await this.handleEventCreation(event, createContacts); - } - } - return Promise.reject({ - calError: "Something went wrong when searching/creating the attendees in Salesforce", - }); - } - - async updateEvent(uid: string, event: CalendarEvent): Promise { - const updatedEvent = await this.salesforceUpdateEvent(uid, event); - if (updatedEvent.success) { - return Promise.resolve({ - uid: updatedEvent.id, - id: updatedEvent.id, - type: "salesforce_other_calendar", - password: "", - url: "", - additionalInfo: { calWarnings: this.calWarnings }, - }); - } else { - return Promise.reject({ calError: "Something went wrong when updating the event in Salesforce" }); - } - } - - async deleteEvent(uid: string) { - const deletedEvent = await this.salesforceDeleteEvent(uid); - if (deletedEvent.success) { - Promise.resolve(); - } else { - Promise.reject({ calError: "Something went wrong when deleting the event in Salesforce" }); - } - } - - async getAvailability(dateFrom: string, dateTo: string, selectedCalendars: IntegrationCalendar[]) { - return Promise.resolve([]); - } - - async listCalendars(event?: CalendarEvent) { - return Promise.resolve([]); - } -} diff --git a/packages/app-store/salesforce/lib/index.ts b/packages/app-store/salesforce/lib/index.ts deleted file mode 100644 index e168c149df..0000000000 --- a/packages/app-store/salesforce/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as CalendarService } from "./CalendarService"; diff --git a/packages/app-store/salesforce/package.json b/packages/app-store/salesforce/package.json deleted file mode 100644 index 0922d1570f..0000000000 --- a/packages/app-store/salesforce/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "private": true, - "name": "@calcom/salesforce", - "version": "0.0.0", - "main": "./index.ts", - "description": "Salesforce (Sales Cloud) is a cloud-based application designed to help your salespeople sell smarter and faster by centralizing customer information, logging their interactions with your company, and automating many of the tasks salespeople do every day.", - "dependencies": { - "@calcom/lib": "*", - "@calcom/prisma": "*", - "jsforce": "^1.11.0" - }, - "devDependencies": { - "@calcom/types": "*", - "@types/jsforce": "^1.11.0" - } -} diff --git a/packages/app-store/salesforce/static/1.png b/packages/app-store/salesforce/static/1.png deleted file mode 100644 index 50ff69138f..0000000000 Binary files a/packages/app-store/salesforce/static/1.png and /dev/null differ diff --git a/packages/app-store/salesforce/static/icon.png b/packages/app-store/salesforce/static/icon.png deleted file mode 100644 index 884bc21213..0000000000 Binary files a/packages/app-store/salesforce/static/icon.png and /dev/null differ diff --git a/packages/app-store/zapier/components/AppSettings.tsx b/packages/app-store/zapier/components/AppSettings.tsx index 2b25ed4436..c9017e5cd3 100644 --- a/packages/app-store/zapier/components/AppSettings.tsx +++ b/packages/app-store/zapier/components/AppSettings.tsx @@ -20,12 +20,6 @@ const templates: Template[] = [ text: "Create Google Sheets rows for scheduled events", link: "https://workflows.zapier.com/?attempt_id=a086d136-b084-45bc-a6aa-c9f460b05b7d&template=1082047", }, - { - icon: "salesforce.svg", - app: "Salesforce", - text: "Create Salesforce leads from new bookings", - link: "https://zapier.com/editor/170116721?attempt_id=9f939229-ee89-4391-b9c7-f4645c60dbd4", - }, { icon: "todoist.svg", app: "Todoist", diff --git a/packages/prisma/seed-app-store.ts b/packages/prisma/seed-app-store.ts index 1f2c294a52..155976df0c 100644 --- a/packages/prisma/seed-app-store.ts +++ b/packages/prisma/seed-app-store.ts @@ -238,12 +238,6 @@ export default async function main() { client_secret: process.env.HUBSPOT_CLIENT_SECRET, }); } - if (process.env.SALESFORCE_CONSUMER_KEY && process.env.SALESFORCE_CONSUMER_SECRET) { - await createApp("salesforce", "salesforce", ["other"], "salesforce_other_calendar", { - consumer_key: process.env.SALESFORCE_CONSUMER_KEY, - consumer_secret: process.env.SALESFORCE_CONSUMER_SECRET, - }); - } await createApp("wipe-my-cal", "wipemycalother", ["other"], "wipemycal_other"); if (process.env.GIPHY_API_KEY) { await createApp("giphy", "giphy", ["other"], "giphy_other", { diff --git a/turbo.json b/turbo.json index c045dc9626..2a23beb910 100644 --- a/turbo.json +++ b/turbo.json @@ -230,8 +230,6 @@ "$PLAYWRIGHT_TEST_BASE_URL", "$QUICK", "$RAILWAY_STATIC_URL", - "$SALESFORCE_CONSUMER_KEY", - "$SALESFORCE_CONSUMER_SECRET", "$SAML_ADMINS", "$SAML_DATABASE_URL", "$SEND_FEEDBACK_EMAIL",