Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(HighLevel Node): Api v2 support, new node version #9554

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d978833
higthlevel oauth2 api credentials
michael-radency May 29, 2024
ad26240
typo fix
michael-radency May 29, 2024
b392844
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 29, 2024
06bd5c7
update, WIP
michael-radency May 30, 2024
497ab49
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 30, 2024
18f8452
opportunities update wip
michael-radency May 30, 2024
1c48ca1
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 30, 2024
5f5f5dd
create oportunity update
michael-radency May 30, 2024
e0fd0a4
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 30, 2024
9906753
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 31, 2024
6a7b643
opportunities update
michael-radency May 31, 2024
5767d06
users get fix, scopes warning
michael-radency May 31, 2024
129858b
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 31, 2024
cae87ef
scopes cleanup
michael-radency May 31, 2024
80be762
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 31, 2024
a8788df
removed unsuported update properties
michael-radency May 31, 2024
0e15d89
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency May 31, 2024
5ebddf6
removed scopes
michael-radency May 31, 2024
75a8d15
scopes hint
michael-radency May 31, 2024
a70fc07
Merge branch 'master' of https://github.com/n8n-io/n8n into node-783-…
michael-radency Jun 3, 2024
8c06bdb
review updates
michael-radency Jun 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,10 @@ export const TEST_WEBHOOK_TIMEOUT = 2 * TIME.MINUTE;
export const TEST_WEBHOOK_TIMEOUT_BUFFER = 30 * TIME.SECOND;

export const N8N_DOCS_URL = 'https://docs.n8n.io';

export const GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE = [
'oAuth2Api',
'googleOAuth2Api',
'microsoftOAuth2Api',
'highLevelOAuth2Api',
];
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Get, RestController } from '@/decorators';
import { jsonStringify } from 'n8n-workflow';
import { OAuthRequest } from '@/requests';
import { AbstractOAuthController, type CsrfStateParam } from './abstractOAuth.controller';
import { GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE as GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE } from '../../constants';

@RestController('/oauth2-credential')
export class OAuth2CredentialController extends AbstractOAuthController {
Expand All @@ -25,16 +26,11 @@ export class OAuth2CredentialController extends AbstractOAuthController {
// At some point in the past we saved hidden scopes to credentials (but shouldn't)
// Delete scope before applying defaults to make sure new scopes are present on reconnect
// Generic Oauth2 API is an exception because it needs to save the scope
const genericOAuth2 = [
'oAuth2Api',
'googleOAuth2Api',
'microsoftOAuth2Api',
'highLevelOAuth2Api',
];

if (
decryptedDataOriginal?.scope &&
credential.type.includes('OAuth2') &&
!genericOAuth2.includes(credential.type)
!GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE.includes(credential.type)
) {
delete decryptedDataOriginal.scope;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class HighLevelOAuth2Api implements ICredentialType {
displayName: 'Scope',
name: 'scope',
type: 'string',
hint: 'Separate scopes by space',
hint: "Separate scopes by space, scopes needed for node: 'locations.readonly contacts.readonly contacts.write opportunities.readonly opportunities.write users.readonly'",
default: '',
required: true,
},
Expand Down
3 changes: 3 additions & 0 deletions packages/nodes-base/credentials/OAuth2Api.credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export class OAuth2Api implements ICredentialType {
default: '',
required: true,
},
// WARNING: if you are extending from this credentials and allow user to set their own scopes
// you HAVE TO add it to GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE in packages/cli/src/constants.ts
// track any updates to this behavior in N8N-7424
{
displayName: 'Scope',
name: 'scope',
Expand Down
102 changes: 20 additions & 82 deletions packages/nodes-base/nodes/HighLevel/HighLevel.node.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,25 @@
import type { INodeProperties, INodeType, INodeTypeDescription } from 'n8n-workflow';
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
import { VersionedNodeType } from 'n8n-workflow';

import { contactFields, contactNotes, contactOperations } from './description/ContactDescription';
import { opportunityFields, opportunityOperations } from './description/OpportunityDescription';
import { taskFields, taskOperations } from './description/TaskDescription';
import {
getPipelineStages,
getTimezones,
getUsers,
highLevelApiPagination,
} from './GenericFunctions';
import { HighLevelV1 } from './v1/HighLevelV1.node';
import { HighLevelV2 } from './v2/HighLevelV2.node';

const ressources: INodeProperties[] = [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Contact',
value: 'contact',
},
{
name: 'Opportunity',
value: 'opportunity',
},
{
name: 'Task',
value: 'task',
},
],
default: 'contact',
required: true,
},
];
export class HighLevel extends VersionedNodeType {
constructor() {
const baseDescription: INodeTypeBaseDescription = {
displayName: 'HighLevel',
name: 'highLevel',
icon: 'file:highLevel.svg',
group: ['transform'],
defaultVersion: 2,
description: 'Consume HighLevel API',
};

export class HighLevel implements INodeType {
description: INodeTypeDescription = {
displayName: 'HighLevel',
name: 'highLevel',
icon: 'file:highLevel.svg',
group: ['transform'],
version: 1,
description: 'Consume HighLevel API',
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
defaults: {
name: 'HighLevel',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'highLevelApi',
required: true,
},
],
requestDefaults: {
baseURL: 'https://rest.gohighlevel.com/v1',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
requestOperations: {
pagination: highLevelApiPagination,
},
properties: [
...ressources,
...contactOperations,
...contactNotes,
...contactFields,
...opportunityOperations,
...opportunityFields,
...taskOperations,
...taskFields,
],
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new HighLevelV1(baseDescription),
2: new HighLevelV2(baseDescription),
};

methods = {
loadOptions: {
getPipelineStages,
getUsers,
getTimezones,
},
};
super(nodeVersions, baseDescription);
}
}
102 changes: 102 additions & 0 deletions packages/nodes-base/nodes/HighLevel/v1/HighLevelV1.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
import type {
INodeProperties,
INodeType,
INodeTypeBaseDescription,
INodeTypeDescription,
} from 'n8n-workflow';

import { contactFields, contactNotes, contactOperations } from './description/ContactDescription';
import { opportunityFields, opportunityOperations } from './description/OpportunityDescription';
import { taskFields, taskOperations } from './description/TaskDescription';
import {
getPipelineStages,
getTimezones,
getUsers,
highLevelApiPagination,
} from './GenericFunctions';

const resources: INodeProperties[] = [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Contact',
value: 'contact',
},
{
name: 'Opportunity',
value: 'opportunity',
},
{
name: 'Task',
value: 'task',
},
],
default: 'contact',
required: true,
},
];

const versionDescription: INodeTypeDescription = {
displayName: 'HighLevel',
name: 'highLevel',
icon: 'file:highLevel.svg',
group: ['transform'],
version: 1,
description: 'Consume HighLevel API v1',
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
defaults: {
name: 'HighLevel',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'highLevelApi',
required: true,
},
],
requestDefaults: {
baseURL: 'https://rest.gohighlevel.com/v1',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
requestOperations: {
pagination: highLevelApiPagination,
},
properties: [
...resources,
...contactOperations,
...contactNotes,
...contactFields,
...opportunityOperations,
...opportunityFields,
...taskOperations,
...taskFields,
],
};

export class HighLevelV1 implements INodeType {
description: INodeTypeDescription;

constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
...versionDescription,
};
}

methods = {
loadOptions: {
getPipelineStages,
getUsers,
getTimezones,
},
};
}
Loading
Loading