Skip to content

Commit

Permalink
feat: Add crowd.dev node and trigger node (#6082)
Browse files Browse the repository at this point in the history
  • Loading branch information
perseus-algol authored Jul 3, 2023
1 parent 7a8b85a commit 238a78f
Show file tree
Hide file tree
Showing 18 changed files with 1,703 additions and 0 deletions.
72 changes: 72 additions & 0 deletions packages/nodes-base/credentials/CrowdDevApi.credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';

export class CrowdDevApi implements ICredentialType {
name = 'crowdDevApi';

displayName = 'crowd.dev API';

documentationUrl = 'crowdDev';

properties: INodeProperties[] = [
{
displayName: 'URL',
name: 'url',
type: 'string',
default: 'https://app.crowd.dev',
},
{
displayName: 'Tenant ID',
name: 'tenantId',
type: 'string',
default: '',
},
{
displayName: 'Token',
name: 'token',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
{
displayName: 'Ignore SSL Issues',
name: 'allowUnauthorizedCerts',
type: 'boolean',
description: 'Whether to connect even if SSL certificate validation is not possible',
default: false,
},
];

// This allows the credential to be used by other parts of n8n
// stating how this credential is injected as part of the request
// An example is the Http Request node that can make generic calls
// reusing this credential
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '={{"Bearer " + $credentials.token}}',
},
},
};

// The block below tells how this credential can be tested
test: ICredentialTestRequest = {
request: {
method: 'POST',
baseURL: '={{$credentials.url.replace(/\\/$/, "") + "/api/tenant/" + $credentials.tenantId}}',
url: '/member/query',
skipSslCertificateValidation: '={{ $credentials.allowUnauthorizedCerts }}',
body: {
limit: 1,
offset: 0,
},
},
};
}
18 changes: 18 additions & 0 deletions packages/nodes-base/nodes/CrowdDev/CrowdDev.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.crowdDev",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Productivity"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/crowdDev"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.crowdDev/"
}
]
}
}
32 changes: 32 additions & 0 deletions packages/nodes-base/nodes/CrowdDev/CrowdDev.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
import { allProperties } from './descriptions';

export class CrowdDev implements INodeType {
description: INodeTypeDescription = {
displayName: 'crowd.dev',
name: 'crowdDev',
icon: 'file:crowdDev.svg',
group: ['transform'],
version: 1,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description:
'crowd.dev is an open-source suite of community and data tools built to unlock community-led growth for your organization.',
defaults: {
name: 'crowd.dev',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'crowdDevApi',
required: true,
},
],
requestDefaults: {
baseURL: '={{$credentials.url}}/api/tenant/{{$credentials.tenantId}}',
json: true,
skipSslCertificateValidation: '={{ $credentials.allowUnauthorizedCerts }}',
},
properties: allProperties,
};
}
18 changes: 18 additions & 0 deletions packages/nodes-base/nodes/CrowdDev/CrowdDevTrigger.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.crowdDevTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Productivity"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/crowdDev"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.crowddevtrigger/"
}
]
}
}
185 changes: 185 additions & 0 deletions packages/nodes-base/nodes/CrowdDev/CrowdDevTrigger.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import type {
IHookFunctions,
IWebhookFunctions,
INodeType,
INodeTypeDescription,
IWebhookResponseData,
IHttpRequestOptions,
} from 'n8n-workflow';

interface ICrowdDevCreds {
url: string;
tenantId: string;
token: string;
allowUnauthorizedCerts: boolean;
}

const credsName = 'crowdDevApi';

const getCreds = async (hookFns: IHookFunctions) =>
hookFns.getCredentials(credsName) as unknown as ICrowdDevCreds;

const createRequest = (
creds: ICrowdDevCreds,
opts: Partial<IHttpRequestOptions>,
): IHttpRequestOptions => {
const defaults: IHttpRequestOptions = {
baseURL: `${creds.url}/api/tenant/${creds.tenantId}`,
url: '',
json: true,
skipSslCertificateValidation: creds.allowUnauthorizedCerts,
};
return Object.assign(defaults, opts);
};

export class CrowdDevTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'crowd.dev Trigger',
name: 'crowdDevTrigger',
icon: 'file:crowdDev.svg',
group: ['trigger'],
version: 1,
description: 'Starts the workflow when crowd.dev events occur.',
defaults: {
name: 'crowd.dev Trigger',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'crowdDevApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Trigger',
name: 'trigger',
description: 'What will trigger an automation',
type: 'options',
required: true,
default: 'new_activity',
options: [
{
name: 'New Activity',
value: 'new_activity',
},
{
name: 'New Member',
value: 'new_member',
},
],
},
],
};

webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const creds = await getCreds(this);
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default') as string;

if (webhookData.webhookId !== undefined) {
try {
const options = createRequest(creds, {
url: `/automation/${webhookData.webhookId}`,
method: 'GET',
});
const data = await this.helpers.httpRequestWithAuthentication.call(
this,
credsName,
options,
);
if (data.settings.url === webhookUrl) {
return true;
}
} catch (error) {
return false;
}
}

// If it did not error then the webhook exists
return false;
},

async create(this: IHookFunctions): Promise<boolean> {
const creds = await getCreds(this);
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default') as string;
const params = {
trigger: this.getNodeParameter('trigger') as string,
};

const options = createRequest(creds, {
url: '/automation',
method: 'POST',
body: {
data: {
settings: {
url: webhookUrl,
},
type: 'webhook',
trigger: params.trigger,
},
},
});

const responseData = await this.helpers.httpRequestWithAuthentication.call(
this,
'crowdDevApi',
options,
);
if (responseData === undefined || responseData.id === undefined) {
// Required data is missing so was not successful
return false;
}

webhookData.webhookId = responseData.id as string;

return true;
},

async delete(this: IHookFunctions): Promise<boolean> {
const creds = await getCreds(this);
const webhookData = this.getWorkflowStaticData('node');

if (webhookData.webhookId !== undefined) {
try {
const options = createRequest(creds, {
url: `/automation/${webhookData.webhookId}`,
method: 'DELETE',
});
await this.helpers.httpRequestWithAuthentication.call(this, credsName, options);
} catch (error) {
return false;
}

// Remove from the static workflow data so that it is clear
// that no webhooks are registered anymore
delete webhookData.webhookId;
delete webhookData.webhookEvents;
delete webhookData.hookSecret;
}

return true;
},
},
};

async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const bodyData = this.getBodyData();

return {
workflowData: [this.helpers.returnJsonArray(bodyData)],
};
}
}
Loading

0 comments on commit 238a78f

Please sign in to comment.