Merge pull request #1279 from automatisch/feature/hubspot

feat(hubspot): Implement create contact action
This commit is contained in:
Ömer Faruk Aydın
2023-09-14 11:52:18 +02:00
committed by GitHub
18 changed files with 350 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Create contact',
key: 'createContact',
description: `Create contact on user's account.`,
arguments: [
{
label: 'Company name',
key: 'company',
type: 'string' as const,
required: false,
variables: true,
},
{
label: 'Email',
key: 'email',
type: 'string' as const,
required: false,
variables: true,
},
{
label: 'First name',
key: 'firstName',
type: 'string' as const,
required: false,
variables: true,
},
{
label: 'Last name',
key: 'lastName',
type: 'string' as const,
required: false,
description: 'Last name',
variables: true,
},
{
label: 'Phone',
key: 'phone',
type: 'string' as const,
required: false,
variables: true,
},
{
label: 'Website URL',
key: 'website',
type: 'string' as const,
required: false,
variables: true,
},
{
label: 'Owner ID',
key: 'hubspotOwnerId',
type: 'string' as const,
required: false,
variables: true,
},
],
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 phone = $.step.parameters.phone as string;
const website = $.step.parameters.website as string;
const hubspotOwnerId = $.step.parameters.hubspotOwnerId as string;
const response = await $.http.post(`crm/v3/objects/contacts`, {
properties: {
company,
email,
firstname: firstName,
lastname: lastName,
phone,
website,
hubspot_owner_id: hubspotOwnerId,
},
});
$.setActionItem({ raw: response.data });
},
});

View File

@@ -0,0 +1,3 @@
import createContact from './create-contact';
export default [ createContact ];

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="27px" height="28px" viewBox="0 0 27 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g fill="#f95c35">
<path d="M19.614233,20.1771162 C17.5228041,20.1771162 15.8274241,18.4993457 15.8274241,16.4299995 C15.8274241,14.3602937 17.5228041,12.6825232 19.614233,12.6825232 C21.7056619,12.6825232 23.4010418,14.3602937 23.4010418,16.4299995 C23.4010418,18.4993457 21.7056619,20.1771162 19.614233,20.1771162 M20.7478775,9.21551429 L20.7478775,5.88190722 C21.6271788,5.47091457 22.243053,4.59067833 22.243053,3.56912967 L22.243053,3.49218091 C22.243053,2.08229273 21.0774338,0.928780545 19.6527478,0.928780545 L19.5753548,0.928780545 C18.1506688,0.928780545 16.9850496,2.08229273 16.9850496,3.49218091 L16.9850496,3.56912967 C16.9850496,4.59067833 17.6009238,5.47127414 18.4802251,5.88226679 L18.4802251,9.21551429 C17.1710836,9.4157968 15.9749432,9.95012321 14.9884545,10.7365107 L5.73944086,3.61659339 C5.80048326,3.3846684 5.84335828,3.14591151 5.84372163,2.89492912 C5.84517502,1.29842223 4.53930368,0.00215931486 2.92531356,1.87311107e-06 C1.31205014,-0.00179599501 0.00181863138,1.29087118 1.8932965e-06,2.88773765 C-0.00181484479,4.48460412 1.30405649,5.78086703 2.91804661,5.7826649 C3.44381061,5.78338405 3.93069642,5.63559929 4.35726652,5.39540411 L13.4551275,12.3995387 C12.6815604,13.5552084 12.2281026,14.9395668 12.2281026,16.4299995 C12.2281026,17.9901894 12.7262522,19.433518 13.5677653,20.6204705 L10.8012365,23.3586237 C10.5825013,23.2935408 10.3557723,23.2482346 10.1152362,23.2482346 C8.78938076,23.2482346 7.71423516,24.3118533 7.71423516,25.6239375 C7.71423516,26.9363812 8.78938076,28 10.1152362,28 C11.441455,28 12.5162373,26.9363812 12.5162373,25.6239375 C12.5162373,25.3866189 12.4704555,25.1618854 12.4046896,24.9454221 L15.1414238,22.2371135 C16.3837093,23.1752411 17.9308435,23.7390526 19.614233,23.7390526 C23.6935367,23.7390526 27,20.466573 27,16.4299995 C27,12.7756527 24.2872467,9.7566726 20.7478775,9.21551429"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,20 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import scopes from '../common/scopes';
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 });
}

View File

@@ -0,0 +1,48 @@
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: [
{
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,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable } from '@automatisch/types';
import getAccessTokenInfo from '../common/get-access-token-info';
const isStillVerified = async ($: IGlobalVariable) => {
await getAccessTokenInfo($);
return true;
};
export default isStillVerified;

View File

@@ -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;

View File

@@ -0,0 +1,52 @@
import { IGlobalVariable, IField } from '@automatisch/types';
import { URLSearchParams } from 'url';
import getAccessTokenInfo from '../common/get-access-token-info';
const verifyCredentials = 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: '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({
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,
});
};
export default verifyCredentials;

View File

@@ -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;

View File

@@ -0,0 +1,11 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
const getAccessTokenInfo = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await $.http.get(
`/oauth/v1/access-tokens/${$.auth.data.accessToken}`
);
return response.data;
};
export default getAccessTokenInfo;

View File

@@ -0,0 +1,3 @@
const scopes = ['crm.objects.contacts.read', 'crm.objects.contacts.write'];
export default scopes;

View File

View File

@@ -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://automatisch.io/docs/apps/hubspot/connection',
supportsConnections: true,
baseUrl: 'https://www.hubspot.com',
apiBaseUrl: 'https://api.hubapi.com',
primaryColor: 'F95C35',
beforeRequest: [addAuthHeader],
auth,
actions,
});

View File

@@ -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,

View File

@@ -0,0 +1,12 @@
---
favicon: /favicons/hubspot.svg
items:
- name: Create a contact
desc: Create a contact on user's account.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -0,0 +1,22 @@
# 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. 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 HubSpot connection with Automatisch.

View File

@@ -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)

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="27px" height="28px" viewBox="0 0 27 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g fill="#f95c35">
<path d="M19.614233,20.1771162 C17.5228041,20.1771162 15.8274241,18.4993457 15.8274241,16.4299995 C15.8274241,14.3602937 17.5228041,12.6825232 19.614233,12.6825232 C21.7056619,12.6825232 23.4010418,14.3602937 23.4010418,16.4299995 C23.4010418,18.4993457 21.7056619,20.1771162 19.614233,20.1771162 M20.7478775,9.21551429 L20.7478775,5.88190722 C21.6271788,5.47091457 22.243053,4.59067833 22.243053,3.56912967 L22.243053,3.49218091 C22.243053,2.08229273 21.0774338,0.928780545 19.6527478,0.928780545 L19.5753548,0.928780545 C18.1506688,0.928780545 16.9850496,2.08229273 16.9850496,3.49218091 L16.9850496,3.56912967 C16.9850496,4.59067833 17.6009238,5.47127414 18.4802251,5.88226679 L18.4802251,9.21551429 C17.1710836,9.4157968 15.9749432,9.95012321 14.9884545,10.7365107 L5.73944086,3.61659339 C5.80048326,3.3846684 5.84335828,3.14591151 5.84372163,2.89492912 C5.84517502,1.29842223 4.53930368,0.00215931486 2.92531356,1.87311107e-06 C1.31205014,-0.00179599501 0.00181863138,1.29087118 1.8932965e-06,2.88773765 C-0.00181484479,4.48460412 1.30405649,5.78086703 2.91804661,5.7826649 C3.44381061,5.78338405 3.93069642,5.63559929 4.35726652,5.39540411 L13.4551275,12.3995387 C12.6815604,13.5552084 12.2281026,14.9395668 12.2281026,16.4299995 C12.2281026,17.9901894 12.7262522,19.433518 13.5677653,20.6204705 L10.8012365,23.3586237 C10.5825013,23.2935408 10.3557723,23.2482346 10.1152362,23.2482346 C8.78938076,23.2482346 7.71423516,24.3118533 7.71423516,25.6239375 C7.71423516,26.9363812 8.78938076,28 10.1152362,28 C11.441455,28 12.5162373,26.9363812 12.5162373,25.6239375 C12.5162373,25.3866189 12.4704555,25.1618854 12.4046896,24.9454221 L15.1414238,22.2371135 C16.3837093,23.1752411 17.9308435,23.7390526 19.614233,23.7390526 C23.6935367,23.7390526 27,20.466573 27,16.4299995 C27,12.7756527 24.2872467,9.7566726 20.7478775,9.21551429"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB