From 8ea8067788fe341d8d0b7e5203c465321e47fcfd Mon Sep 17 00:00:00 2001 From: Vitalii Mykytiuk Date: Tue, 5 Sep 2023 12:29:03 +0300 Subject: [PATCH 1/9] feat(hubspot): Implement create contact action --- .../hubspot/actions/create-contact/index.ts | 92 +++++++++++++++++++ .../backend/src/apps/hubspot/actions/index.ts | 3 + .../src/apps/hubspot/assets/favicon.svg | 8 ++ .../backend/src/apps/hubspot/auth/index.ts | 20 ++++ .../apps/hubspot/auth/is-still-verified.ts | 9 ++ .../apps/hubspot/auth/verify-credentials.ts | 12 +++ .../apps/hubspot/common/add-auth-header.ts | 14 +++ packages/backend/src/apps/hubspot/index.d.ts | 0 packages/backend/src/apps/hubspot/index.ts | 18 ++++ packages/docs/pages/.vitepress/config.js | 9 ++ packages/docs/pages/apps/hubspot/actions.md | 12 +++ .../docs/pages/apps/hubspot/connection.md | 11 +++ packages/docs/pages/guide/available-apps.md | 1 + .../docs/pages/public/favicons/hubspot.svg | 8 ++ 14 files changed, 217 insertions(+) create mode 100644 packages/backend/src/apps/hubspot/actions/create-contact/index.ts create mode 100644 packages/backend/src/apps/hubspot/actions/index.ts create mode 100644 packages/backend/src/apps/hubspot/assets/favicon.svg create mode 100644 packages/backend/src/apps/hubspot/auth/index.ts create mode 100644 packages/backend/src/apps/hubspot/auth/is-still-verified.ts create mode 100644 packages/backend/src/apps/hubspot/auth/verify-credentials.ts create mode 100644 packages/backend/src/apps/hubspot/common/add-auth-header.ts create mode 100644 packages/backend/src/apps/hubspot/index.d.ts create mode 100644 packages/backend/src/apps/hubspot/index.ts create mode 100644 packages/docs/pages/apps/hubspot/actions.md create mode 100644 packages/docs/pages/apps/hubspot/connection.md create mode 100644 packages/docs/pages/public/favicons/hubspot.svg diff --git a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts new file mode 100644 index 00000000..8c6c6317 --- /dev/null +++ b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts @@ -0,0 +1,92 @@ +import defineAction from '../../../../helpers/define-action'; + +export default defineAction({ + name: 'Create contact', + key: 'createContact', + description: `Create contact on user's account.`, + arguments: [ + { + label: 'Company', + key: 'company', + type: 'string' as const, + required: false, + description: 'company name', + variables: true, + }, + { + label: 'E-mail', + key: 'email', + type: 'string' as const, + required: false, + description: 'Contact email', + variables: true, + }, + { + label: 'First name', + key: 'firstname', + type: 'string' as const, + required: false, + description: 'Contact First name', + variables: true, + }, + { + label: 'Last name', + key: 'lastname', + type: 'string' as const, + required: false, + description: 'Contact Last name', + variables: true, + }, + { + label: 'Phone', + key: 'phone', + type: 'string' as const, + required: false, + description: 'Contact phone number', + variables: true, + }, + { + label: 'Webiste URL', + key: 'website', + type: 'string' as const, + required: false, + description: 'Contact Webiste URL', + variables: true, + }, + { + label: 'Owner ID', + key: 'hubspot_owner_id', + type: 'string' as const, + required: false, + description: 'Contact Owner ID', + variables: true, + }, + ], + + async run($) { + const company = $.step.parameters.company as string || undefined; + const email = $.step.parameters.email as string || undefined; + const firstname = $.step.parameters.firstname as string || undefined; + const lastname = $.step.parameters.lastname as string || undefined; + const phone = $.step.parameters.phone as string || undefined; + const website = $.step.parameters.website as string || undefined; + const hubspot_owner_id = $.step.parameters.hubspot_owner_id as number || undefined; + + const response = await $.http.post( + `crm/v3/objects/contacts`, + { + properties: { + company, + email, + firstname, + lastname, + phone, + website, + hubspot_owner_id, + } + } + ); + + $.setActionItem({ raw: response.data }); + }, +}); diff --git a/packages/backend/src/apps/hubspot/actions/index.ts b/packages/backend/src/apps/hubspot/actions/index.ts new file mode 100644 index 00000000..fe753ed8 --- /dev/null +++ b/packages/backend/src/apps/hubspot/actions/index.ts @@ -0,0 +1,3 @@ +import createContact from './create-contact'; + +export default [ createContact ]; diff --git a/packages/backend/src/apps/hubspot/assets/favicon.svg b/packages/backend/src/apps/hubspot/assets/favicon.svg new file mode 100644 index 00000000..c21891fb --- /dev/null +++ b/packages/backend/src/apps/hubspot/assets/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/backend/src/apps/hubspot/auth/index.ts b/packages/backend/src/apps/hubspot/auth/index.ts new file mode 100644 index 00000000..02064ff5 --- /dev/null +++ b/packages/backend/src/apps/hubspot/auth/index.ts @@ -0,0 +1,20 @@ +import verifyCredentials from "./verify-credentials"; +import isStillVerified from "./is-still-verified"; + +export default { + fields: [ + { + key: 'accessToken', + label: 'Access Token', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: null, + clickToCopy: false, + }, + ], + verifyCredentials, + isStillVerified +}; diff --git a/packages/backend/src/apps/hubspot/auth/is-still-verified.ts b/packages/backend/src/apps/hubspot/auth/is-still-verified.ts new file mode 100644 index 00000000..4470a643 --- /dev/null +++ b/packages/backend/src/apps/hubspot/auth/is-still-verified.ts @@ -0,0 +1,9 @@ +import { IGlobalVariable } from '@automatisch/types'; +import verifyCredentials from "./verify-credentials"; + +const isStillVerified = async ($: IGlobalVariable) => { + await verifyCredentials($); + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/hubspot/auth/verify-credentials.ts b/packages/backend/src/apps/hubspot/auth/verify-credentials.ts new file mode 100644 index 00000000..cff860ba --- /dev/null +++ b/packages/backend/src/apps/hubspot/auth/verify-credentials.ts @@ -0,0 +1,12 @@ +import { IGlobalVariable } from '@automatisch/types'; + +const verifyCredentials = async ($: IGlobalVariable) => { + await $.http.get( + `/crm/v3/objects/contacts?limit=1`, + ); + await $.auth.set({ + screenName: $.auth.data?.displayName, + }); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/hubspot/common/add-auth-header.ts b/packages/backend/src/apps/hubspot/common/add-auth-header.ts new file mode 100644 index 00000000..d16f394f --- /dev/null +++ b/packages/backend/src/apps/hubspot/common/add-auth-header.ts @@ -0,0 +1,14 @@ +import { TBeforeRequest } from '@automatisch/types'; + +const addAuthHeader: TBeforeRequest = ($, requestConfig) => { + if (requestConfig.additionalProperties?.skipAddingAuthHeader) return requestConfig; + + if ($.auth.data?.accessToken) { + const authorizationHeader = `Bearer ${$.auth.data.accessToken}`; + requestConfig.headers.Authorization = authorizationHeader; + } + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/hubspot/index.d.ts b/packages/backend/src/apps/hubspot/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/backend/src/apps/hubspot/index.ts b/packages/backend/src/apps/hubspot/index.ts new file mode 100644 index 00000000..3855131c --- /dev/null +++ b/packages/backend/src/apps/hubspot/index.ts @@ -0,0 +1,18 @@ +import defineApp from '../../helpers/define-app'; +import addAuthHeader from './common/add-auth-header'; +import actions from './actions'; +import auth from './auth'; + +export default defineApp({ + name: 'Hubspot', + key: 'hubspot', + iconUrl: '{BASE_URL}/apps/hubspot/assets/favicon.svg', + authDocUrl: 'https://developers.hubspot.com/docs/api/crm/contacts', + supportsConnections: true, + baseUrl: 'https://www.hubspot.com', + apiBaseUrl: 'https://api.hubapi.com', + primaryColor: '000000', + beforeRequest: [addAuthHeader], + auth, + actions, +}); diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index d87539a5..76dc9d9e 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -271,6 +271,15 @@ export default defineConfig({ { text: 'Connection', link: '/apps/spotify/connection' }, ], }, + { + text: 'Hubspot', + collapsible: true, + collapsed: true, + items: [ + { text: 'Actions', link: '/apps/hubspot/actions' }, + { text: 'Connection', link: '/apps/hubspot/connection' }, + ], + }, { text: 'Strava', collapsible: true, diff --git a/packages/docs/pages/apps/hubspot/actions.md b/packages/docs/pages/apps/hubspot/actions.md new file mode 100644 index 00000000..f2ae7746 --- /dev/null +++ b/packages/docs/pages/apps/hubspot/actions.md @@ -0,0 +1,12 @@ +--- +favicon: /favicons/hubspot.svg +items: + - name: Create a contact + desc: Create a contact on user's account. +--- + + + + diff --git a/packages/docs/pages/apps/hubspot/connection.md b/packages/docs/pages/apps/hubspot/connection.md new file mode 100644 index 00000000..df4467b9 --- /dev/null +++ b/packages/docs/pages/apps/hubspot/connection.md @@ -0,0 +1,11 @@ +# Spotify + +:::info +This page explains the steps you need to follow to set up the Hubspot connection in Automatisch. If any of the steps are outdated, please let us know! +::: + +1. Create new app +1. Create new app token +1. Paste **API TOKEN** value into Automatisch as **Access Token**, respectively. +1. Click **Submit** button on Automatisch. +1. Now, you can start using the Spotify connection with Automatisch. diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index 63578431..e76f7f3c 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -28,6 +28,7 @@ The following integrations are currently supported by Automatisch. - [Slack](/apps/slack/actions) - [SMTP](/apps/smtp/actions) - [Spotify](/apps/spotify/actions) +- [Hubspot](/apps/hubspot/actions) - [Strava](/apps/strava/actions) - [Stripe](/apps/stripe/triggers) - [Telegram](/apps/telegram-bot/actions) diff --git a/packages/docs/pages/public/favicons/hubspot.svg b/packages/docs/pages/public/favicons/hubspot.svg new file mode 100644 index 00000000..c21891fb --- /dev/null +++ b/packages/docs/pages/public/favicons/hubspot.svg @@ -0,0 +1,8 @@ + + + + + + + + From 83bb400df1dd30584e82841122531d523b488167 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 8 Sep 2023 12:25:50 +0200 Subject: [PATCH 2/9] chore: Change hubspot auth doc url --- packages/backend/src/apps/hubspot/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/apps/hubspot/index.ts b/packages/backend/src/apps/hubspot/index.ts index 3855131c..aa71e901 100644 --- a/packages/backend/src/apps/hubspot/index.ts +++ b/packages/backend/src/apps/hubspot/index.ts @@ -7,7 +7,7 @@ export default defineApp({ name: 'Hubspot', key: 'hubspot', iconUrl: '{BASE_URL}/apps/hubspot/assets/favicon.svg', - authDocUrl: 'https://developers.hubspot.com/docs/api/crm/contacts', + authDocUrl: 'https://automatisch.io/docs/apps/hubspot/connection', supportsConnections: true, baseUrl: 'https://www.hubspot.com', apiBaseUrl: 'https://api.hubapi.com', From aae88fe1adcf819b3e7b7936fd130c048a428275 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 8 Sep 2023 17:43:32 +0200 Subject: [PATCH 3/9] docs(hubspot): Adjust connection page for OAuth setup --- .../docs/pages/apps/hubspot/connection.md | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/docs/pages/apps/hubspot/connection.md b/packages/docs/pages/apps/hubspot/connection.md index df4467b9..f1260ef7 100644 --- a/packages/docs/pages/apps/hubspot/connection.md +++ b/packages/docs/pages/apps/hubspot/connection.md @@ -1,11 +1,22 @@ -# Spotify +# Hubspot :::info This page explains the steps you need to follow to set up the Hubspot connection in Automatisch. If any of the steps are outdated, please let us know! ::: -1. Create new app -1. Create new app token -1. Paste **API TOKEN** value into Automatisch as **Access Token**, respectively. -1. Click **Submit** button on Automatisch. -1. Now, you can start using the Spotify connection with Automatisch. +1. Go to the [HubSpot Developer page](https://developers.hubspot.com/). +2. Login into your developer account. +3. Click on the **Manage apps** button. +4. Click on the **Create app** button. +5. Fill the **Public app name** field with the name of your API app. +6. Go to the **Auth** tab. +7. Fill the **Redirect URL(s)** field with the OAuth Redirect URL from the Automatisch connection creation page. +8. Go to the **Scopes** tab. +9. Select the scopes you want to use with Automatisch. +10. Click on the **Create App** button. +11. Go back to the **Auth** tab. +12. Copy the **Client ID** and **Client Secret** values. +13. Paste the **Client ID** value into Automatisch as **Client ID**, respectively. +14. Paste the **Client Secret** value into Automatisch as **Client Secret**, respectively. +15. Click the **Submit** button on Automatisch. +16. Now, you can start using the Spotify connection with Automatisch. From b12f39916facf046041f2838c473b0a04c1a5eca Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 8 Sep 2023 18:07:00 +0200 Subject: [PATCH 4/9] feat(hubspot): Implement generate auth url for OAuth --- .../apps/hubspot/auth/generate-auth-url.ts | 21 +++++++++++ .../backend/src/apps/hubspot/auth/index.ts | 36 ++++++++++++++++--- packages/backend/src/apps/hubspot/index.ts | 2 +- 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 packages/backend/src/apps/hubspot/auth/generate-auth-url.ts diff --git a/packages/backend/src/apps/hubspot/auth/generate-auth-url.ts b/packages/backend/src/apps/hubspot/auth/generate-auth-url.ts new file mode 100644 index 00000000..33ecbb09 --- /dev/null +++ b/packages/backend/src/apps/hubspot/auth/generate-auth-url.ts @@ -0,0 +1,21 @@ +import { IField, IGlobalVariable } from '@automatisch/types'; +import { URLSearchParams } from 'url'; + +const scopes = ['crm.objects.contacts.read', 'crm.objects.contacts.write']; + +export default async function generateAuthUrl($: IGlobalVariable) { + const oauthRedirectUrlField = $.app.auth.fields.find( + (field: IField) => field.key == 'oAuthRedirectUrl' + ); + const callbackUrl = oauthRedirectUrlField.value as string; + + const searchParams = new URLSearchParams({ + client_id: $.auth.data.clientId as string, + redirect_uri: callbackUrl, + scope: scopes.join(' '), + }); + + const url = `https://app.hubspot.com/oauth/authorize?${searchParams.toString()}`; + + await $.auth.set({ url }); +} diff --git a/packages/backend/src/apps/hubspot/auth/index.ts b/packages/backend/src/apps/hubspot/auth/index.ts index 02064ff5..a5509677 100644 --- a/packages/backend/src/apps/hubspot/auth/index.ts +++ b/packages/backend/src/apps/hubspot/auth/index.ts @@ -1,11 +1,35 @@ -import verifyCredentials from "./verify-credentials"; -import isStillVerified from "./is-still-verified"; +import generateAuthUrl from './generate-auth-url'; +import verifyCredentials from './verify-credentials'; +import isStillVerified from './is-still-verified'; export default { fields: [ { - key: 'accessToken', - label: 'Access Token', + key: 'oAuthRedirectUrl', + label: 'OAuth Redirect URL', + type: 'string' as const, + required: true, + readOnly: true, + value: '{WEB_APP_URL}/app/hubspot/connections/add', + placeholder: null, + description: + 'When asked to input an OAuth callback or redirect URL in HubSpot OAuth, enter the URL above.', + clickToCopy: true, + }, + { + key: 'clientId', + label: 'Client ID', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: null, + clickToCopy: false, + }, + { + key: 'clientSecret', + label: 'Client Secret', type: 'string' as const, required: true, readOnly: false, @@ -15,6 +39,8 @@ export default { clickToCopy: false, }, ], + + generateAuthUrl, verifyCredentials, - isStillVerified + isStillVerified, }; diff --git a/packages/backend/src/apps/hubspot/index.ts b/packages/backend/src/apps/hubspot/index.ts index aa71e901..672bb9b4 100644 --- a/packages/backend/src/apps/hubspot/index.ts +++ b/packages/backend/src/apps/hubspot/index.ts @@ -4,7 +4,7 @@ import actions from './actions'; import auth from './auth'; export default defineApp({ - name: 'Hubspot', + name: 'HubSpot', key: 'hubspot', iconUrl: '{BASE_URL}/apps/hubspot/assets/favicon.svg', authDocUrl: 'https://automatisch.io/docs/apps/hubspot/connection', From dd1e8240b87e260bbe24f05276ac5485b8f52580 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 8 Sep 2023 19:35:20 +0200 Subject: [PATCH 5/9] feat(hubspot): Implement verify credentials for OAuth --- .../hubspot/actions/create-contact/index.ts | 39 +++++++-------- .../apps/hubspot/auth/generate-auth-url.ts | 3 +- .../backend/src/apps/hubspot/auth/index.ts | 2 + .../apps/hubspot/auth/is-still-verified.ts | 5 +- .../src/apps/hubspot/auth/refresh-token.ts | 28 +++++++++++ .../apps/hubspot/auth/verify-credentials.ts | 48 +++++++++++++++++-- .../hubspot/common/get-access-token-info.ts | 11 +++++ .../backend/src/apps/hubspot/common/scopes.ts | 3 ++ 8 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 packages/backend/src/apps/hubspot/auth/refresh-token.ts create mode 100644 packages/backend/src/apps/hubspot/common/get-access-token-info.ts create mode 100644 packages/backend/src/apps/hubspot/common/scopes.ts diff --git a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts index 8c6c6317..4ca0ef67 100644 --- a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts +++ b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts @@ -64,28 +64,25 @@ export default defineAction({ ], async run($) { - const company = $.step.parameters.company as string || undefined; - const email = $.step.parameters.email as string || undefined; - const firstname = $.step.parameters.firstname as string || undefined; - const lastname = $.step.parameters.lastname as string || undefined; - const phone = $.step.parameters.phone as string || undefined; - const website = $.step.parameters.website as string || undefined; - const hubspot_owner_id = $.step.parameters.hubspot_owner_id as number || undefined; + const company = $.step.parameters.company as string; + const email = $.step.parameters.email as string; + const firstname = $.step.parameters.firstname as string; + const lastname = $.step.parameters.lastname as string; + const phone = $.step.parameters.phone as string; + const website = $.step.parameters.website as string; + const hubspot_owner_id = $.step.parameters.hubspot_owner_id as string; - const response = await $.http.post( - `crm/v3/objects/contacts`, - { - properties: { - company, - email, - firstname, - lastname, - phone, - website, - hubspot_owner_id, - } - } - ); + const response = await $.http.post(`crm/v3/objects/contacts`, { + properties: { + company, + email, + firstname, + lastname, + phone, + website, + hubspot_owner_id, + }, + }); $.setActionItem({ raw: response.data }); }, diff --git a/packages/backend/src/apps/hubspot/auth/generate-auth-url.ts b/packages/backend/src/apps/hubspot/auth/generate-auth-url.ts index 33ecbb09..d8f33dd4 100644 --- a/packages/backend/src/apps/hubspot/auth/generate-auth-url.ts +++ b/packages/backend/src/apps/hubspot/auth/generate-auth-url.ts @@ -1,7 +1,6 @@ import { IField, IGlobalVariable } from '@automatisch/types'; import { URLSearchParams } from 'url'; - -const scopes = ['crm.objects.contacts.read', 'crm.objects.contacts.write']; +import scopes from '../common/scopes'; export default async function generateAuthUrl($: IGlobalVariable) { const oauthRedirectUrlField = $.app.auth.fields.find( diff --git a/packages/backend/src/apps/hubspot/auth/index.ts b/packages/backend/src/apps/hubspot/auth/index.ts index a5509677..c7f57dbf 100644 --- a/packages/backend/src/apps/hubspot/auth/index.ts +++ b/packages/backend/src/apps/hubspot/auth/index.ts @@ -1,6 +1,7 @@ import generateAuthUrl from './generate-auth-url'; import verifyCredentials from './verify-credentials'; import isStillVerified from './is-still-verified'; +import refreshToken from './refresh-token'; export default { fields: [ @@ -43,4 +44,5 @@ export default { generateAuthUrl, verifyCredentials, isStillVerified, + refreshToken, }; diff --git a/packages/backend/src/apps/hubspot/auth/is-still-verified.ts b/packages/backend/src/apps/hubspot/auth/is-still-verified.ts index 4470a643..cccd73be 100644 --- a/packages/backend/src/apps/hubspot/auth/is-still-verified.ts +++ b/packages/backend/src/apps/hubspot/auth/is-still-verified.ts @@ -1,8 +1,9 @@ import { IGlobalVariable } from '@automatisch/types'; -import verifyCredentials from "./verify-credentials"; +import getAccessTokenInfo from '../common/get-access-token-info'; const isStillVerified = async ($: IGlobalVariable) => { - await verifyCredentials($); + await getAccessTokenInfo($); + return true; }; diff --git a/packages/backend/src/apps/hubspot/auth/refresh-token.ts b/packages/backend/src/apps/hubspot/auth/refresh-token.ts new file mode 100644 index 00000000..c1fc8a9d --- /dev/null +++ b/packages/backend/src/apps/hubspot/auth/refresh-token.ts @@ -0,0 +1,28 @@ +import { IGlobalVariable, IField } from '@automatisch/types'; +import { URLSearchParams } from 'url'; + +const refreshToken = async ($: IGlobalVariable) => { + const oauthRedirectUrlField = $.app.auth.fields.find( + (field: IField) => field.key == 'oAuthRedirectUrl' + ); + + const callbackUrl = oauthRedirectUrlField.value as string; + + const params = new URLSearchParams({ + grant_type: 'refresh_token', + client_id: $.auth.data.clientId as string, + client_secret: $.auth.data.clientSecret as string, + redirect_uri: callbackUrl, + refresh_token: $.auth.data.refreshToken as string, + }); + + const { data } = await $.http.post('/oauth/v1/token', params.toString()); + + await $.auth.set({ + accessToken: data.access_token, + expiresIn: data.expires_in, + refreshToken: data.refresh_token, + }); +}; + +export default refreshToken; diff --git a/packages/backend/src/apps/hubspot/auth/verify-credentials.ts b/packages/backend/src/apps/hubspot/auth/verify-credentials.ts index cff860ba..130bf171 100644 --- a/packages/backend/src/apps/hubspot/auth/verify-credentials.ts +++ b/packages/backend/src/apps/hubspot/auth/verify-credentials.ts @@ -1,11 +1,51 @@ -import { IGlobalVariable } from '@automatisch/types'; +import { IGlobalVariable, IField } from '@automatisch/types'; +import { URLSearchParams } from 'url'; +import getAccessTokenInfo from '../common/get-access-token-info'; const verifyCredentials = async ($: IGlobalVariable) => { - await $.http.get( - `/crm/v3/objects/contacts?limit=1`, + const oauthRedirectUrlField = $.app.auth.fields.find( + (field: IField) => field.key == 'oAuthRedirectUrl' ); + const callbackUrl = oauthRedirectUrlField.value as string; + const params = new URLSearchParams({ + grant_type: 'authorization_code', + client_id: $.auth.data.clientId as string, + client_secret: $.auth.data.clientSecret as string, + redirect_uri: callbackUrl, + code: $.auth.data.code as string, + }); + + const { data: verifiedCredentials } = await $.http.post( + '/oauth/v1/token', + params.toString() + ); + + const { + access_token: accessToken, + refresh_token: refreshToken, + expires_in: expiresIn, + } = verifiedCredentials; + await $.auth.set({ - screenName: $.auth.data?.displayName, + accessToken, + refreshToken, + expiresIn, + }); + + const accessTokenInfo = await getAccessTokenInfo($); + + await $.auth.set({ + screenName: accessTokenInfo.user, + hubDomain: accessTokenInfo.hub_domain, + scopes: accessTokenInfo.scopes, + scopeToScopeGroupPks: accessTokenInfo.scope_to_scope_group_pks, + trialScopes: accessTokenInfo.trial_scopes, + trialScopeToScoreGroupPks: accessTokenInfo.trial_scope_to_scope_group_pks, + hubId: accessTokenInfo.hub_id, + appId: accessTokenInfo.app_id, + userId: accessTokenInfo.user_id, + expiresIn: accessTokenInfo.expires_in, + tokenType: accessTokenInfo.token_type, }); }; diff --git a/packages/backend/src/apps/hubspot/common/get-access-token-info.ts b/packages/backend/src/apps/hubspot/common/get-access-token-info.ts new file mode 100644 index 00000000..55ed8ff2 --- /dev/null +++ b/packages/backend/src/apps/hubspot/common/get-access-token-info.ts @@ -0,0 +1,11 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +const getAccessTokenInfo = async ($: IGlobalVariable): Promise => { + const response = await $.http.get( + `/oauth/v1/access-tokens/${$.auth.data.accessToken}` + ); + + return response.data; +}; + +export default getAccessTokenInfo; diff --git a/packages/backend/src/apps/hubspot/common/scopes.ts b/packages/backend/src/apps/hubspot/common/scopes.ts new file mode 100644 index 00000000..38cb30a3 --- /dev/null +++ b/packages/backend/src/apps/hubspot/common/scopes.ts @@ -0,0 +1,3 @@ +const scopes = ['crm.objects.contacts.read', 'crm.objects.contacts.write']; + +export default scopes; From d6453a8ed0eeee1dcabc7e9500727ddea7680640 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Wed, 13 Sep 2023 13:45:37 +0200 Subject: [PATCH 6/9] chore: Use camelCase convention for hubspot actions --- .../hubspot/actions/create-contact/index.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts index 4ca0ef67..b814224d 100644 --- a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts +++ b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts @@ -14,7 +14,7 @@ export default defineAction({ variables: true, }, { - label: 'E-mail', + label: 'Email', key: 'email', type: 'string' as const, required: false, @@ -23,7 +23,7 @@ export default defineAction({ }, { label: 'First name', - key: 'firstname', + key: 'firstName', type: 'string' as const, required: false, description: 'Contact First name', @@ -31,7 +31,7 @@ export default defineAction({ }, { label: 'Last name', - key: 'lastname', + key: 'lastName', type: 'string' as const, required: false, description: 'Contact Last name', @@ -55,7 +55,7 @@ export default defineAction({ }, { label: 'Owner ID', - key: 'hubspot_owner_id', + key: 'hubspotOwnerId', type: 'string' as const, required: false, description: 'Contact Owner ID', @@ -66,21 +66,21 @@ export default defineAction({ async run($) { const company = $.step.parameters.company as string; const email = $.step.parameters.email as string; - const firstname = $.step.parameters.firstname as string; - const lastname = $.step.parameters.lastname as string; + const firstName = $.step.parameters.firstName as string; + const lastName = $.step.parameters.lastName as string; const phone = $.step.parameters.phone as string; const website = $.step.parameters.website as string; - const hubspot_owner_id = $.step.parameters.hubspot_owner_id as string; + const hubspotOwnerId = $.step.parameters.hubspotOwnerId as string; const response = await $.http.post(`crm/v3/objects/contacts`, { properties: { company, email, - firstname, - lastname, + firstname: firstName, + lastname: lastName, phone, website, - hubspot_owner_id, + hubspot_owner_id: hubspotOwnerId, }, }); From a128907a4e6b21cc36eae65115276f68732949ef Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Wed, 13 Sep 2023 13:45:56 +0200 Subject: [PATCH 7/9] fix(hubspot): Correct website URL typo and primary color --- .../backend/src/apps/hubspot/actions/create-contact/index.ts | 2 +- packages/backend/src/apps/hubspot/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts index b814224d..104e629a 100644 --- a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts +++ b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts @@ -46,7 +46,7 @@ export default defineAction({ variables: true, }, { - label: 'Webiste URL', + label: 'Website URL', key: 'website', type: 'string' as const, required: false, diff --git a/packages/backend/src/apps/hubspot/index.ts b/packages/backend/src/apps/hubspot/index.ts index 672bb9b4..9f35bade 100644 --- a/packages/backend/src/apps/hubspot/index.ts +++ b/packages/backend/src/apps/hubspot/index.ts @@ -11,7 +11,7 @@ export default defineApp({ supportsConnections: true, baseUrl: 'https://www.hubspot.com', apiBaseUrl: 'https://api.hubapi.com', - primaryColor: '000000', + primaryColor: 'F95C35', beforeRequest: [addAuthHeader], auth, actions, From ae5dd0cad67f36798b8df9ec9f3eafb311af3731 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Wed, 13 Sep 2023 13:49:47 +0200 Subject: [PATCH 8/9] fix(hubspot): Remove redundant field descriptions --- .../src/apps/hubspot/actions/create-contact/index.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts index 104e629a..9d1622c8 100644 --- a/packages/backend/src/apps/hubspot/actions/create-contact/index.ts +++ b/packages/backend/src/apps/hubspot/actions/create-contact/index.ts @@ -6,11 +6,10 @@ export default defineAction({ description: `Create contact on user's account.`, arguments: [ { - label: 'Company', + label: 'Company name', key: 'company', type: 'string' as const, required: false, - description: 'company name', variables: true, }, { @@ -18,7 +17,6 @@ export default defineAction({ key: 'email', type: 'string' as const, required: false, - description: 'Contact email', variables: true, }, { @@ -26,7 +24,6 @@ export default defineAction({ key: 'firstName', type: 'string' as const, required: false, - description: 'Contact First name', variables: true, }, { @@ -34,7 +31,7 @@ export default defineAction({ key: 'lastName', type: 'string' as const, required: false, - description: 'Contact Last name', + description: 'Last name', variables: true, }, { @@ -42,7 +39,6 @@ export default defineAction({ key: 'phone', type: 'string' as const, required: false, - description: 'Contact phone number', variables: true, }, { @@ -50,7 +46,6 @@ export default defineAction({ key: 'website', type: 'string' as const, required: false, - description: 'Contact Webiste URL', variables: true, }, { @@ -58,7 +53,6 @@ export default defineAction({ key: 'hubspotOwnerId', type: 'string' as const, required: false, - description: 'Contact Owner ID', variables: true, }, ], From e77f7ee0bf405fa9f1c02304a0b68877b6cad84d Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Wed, 13 Sep 2023 23:15:06 +0200 Subject: [PATCH 9/9] docs(hubspot): Order alphabetically & correct connection name typo --- packages/docs/pages/.vitepress/config.js | 18 +++++++++--------- packages/docs/pages/apps/hubspot/connection.md | 4 ++-- packages/docs/pages/guide/available-apps.md | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 76dc9d9e..c8844642 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -151,6 +151,15 @@ export default defineConfig({ { text: 'Connection', link: '/apps/http-request/connection' }, ], }, + { + text: 'HubSpot', + collapsible: true, + collapsed: true, + items: [ + { text: 'Actions', link: '/apps/hubspot/actions' }, + { text: 'Connection', link: '/apps/hubspot/connection' }, + ], + }, { text: 'Mattermost', collapsible: true, @@ -271,15 +280,6 @@ export default defineConfig({ { text: 'Connection', link: '/apps/spotify/connection' }, ], }, - { - text: 'Hubspot', - collapsible: true, - collapsed: true, - items: [ - { text: 'Actions', link: '/apps/hubspot/actions' }, - { text: 'Connection', link: '/apps/hubspot/connection' }, - ], - }, { text: 'Strava', collapsible: true, diff --git a/packages/docs/pages/apps/hubspot/connection.md b/packages/docs/pages/apps/hubspot/connection.md index f1260ef7..37864d08 100644 --- a/packages/docs/pages/apps/hubspot/connection.md +++ b/packages/docs/pages/apps/hubspot/connection.md @@ -1,4 +1,4 @@ -# Hubspot +# HubSpot :::info This page explains the steps you need to follow to set up the Hubspot connection in Automatisch. If any of the steps are outdated, please let us know! @@ -19,4 +19,4 @@ This page explains the steps you need to follow to set up the Hubspot connection 13. Paste the **Client ID** value into Automatisch as **Client ID**, respectively. 14. Paste the **Client Secret** value into Automatisch as **Client Secret**, respectively. 15. Click the **Submit** button on Automatisch. -16. Now, you can start using the Spotify connection with Automatisch. +16. Now, you can start using the HubSpot connection with Automatisch. diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index e76f7f3c..45a52157 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -15,6 +15,7 @@ The following integrations are currently supported by Automatisch. - [Google Forms](/apps/google-forms/triggers) - [Google Sheets](/apps/google-sheets/triggers) - [HTTP Request](/apps/http-request/actions) +- [HubSpot](/apps/hubspot/actions) - [Mattermost](/apps/mattermost/actions) - [Notion](/apps/notion/triggers) - [Ntfy](/apps/ntfy/actions) @@ -28,7 +29,6 @@ The following integrations are currently supported by Automatisch. - [Slack](/apps/slack/actions) - [SMTP](/apps/smtp/actions) - [Spotify](/apps/spotify/actions) -- [Hubspot](/apps/hubspot/actions) - [Strava](/apps/strava/actions) - [Stripe](/apps/stripe/triggers) - [Telegram](/apps/telegram-bot/actions)