diff --git a/README.md b/README.md index 6148a6d..09e8d19 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # n8n-nodes-starter -![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/docs/images/n8n-logo.png) +![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png) Example starter module for custom n8n nodes. diff --git a/credentials/ExampleCredentials.credentials.ts b/credentials/FriendGridApi.credentials.ts similarity index 50% rename from credentials/ExampleCredentials.credentials.ts rename to credentials/FriendGridApi.credentials.ts index 7e84255..9a4d4f4 100644 --- a/credentials/ExampleCredentials.credentials.ts +++ b/credentials/FriendGridApi.credentials.ts @@ -3,25 +3,19 @@ import { NodePropertyTypes, } from 'n8n-workflow'; - -export class ExampleCredentials implements ICredentialType { - name = 'exampleCredentials'; - displayName = 'Example Credentials'; +export class FriendGridApi implements ICredentialType { + name = 'friendGridApi'; + displayName = 'FriendGrid API'; + documentationUrl = 'friendGrid'; properties = [ // The credentials to get from user and save encrypted. // Properties can be defined exactly in the same way // as node properties. { - displayName: 'User', - name: 'user', - type: 'string' as NodePropertyTypes, - default: '', - }, - { - displayName: 'Access Token', - name: 'accessToken', + displayName: 'API Key', + name: 'apiKey', type: 'string' as NodePropertyTypes, default: '', }, ]; -} +} \ No newline at end of file diff --git a/nodes/ExampleNode/ExampleNode.node.ts b/nodes/ExampleNode/ExampleNode.node.ts deleted file mode 100644 index 4cd9240..0000000 --- a/nodes/ExampleNode/ExampleNode.node.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { IExecuteFunctions } from 'n8n-core'; -import { - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; - -export class ExampleNode implements INodeType { - description: INodeTypeDescription = { - displayName: 'Example Node', - name: 'exampleNode', - group: ['transform'], - version: 1, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Basic Example Node', - defaults: { - name: 'Example Node', - color: '#772244', - }, - inputs: ['main'], - outputs: ['main'], - properties: [ - // Node properties which the user gets displayed and - // can change on the node. - { - displayName: 'My String', - name: 'myString', - type: 'string', - default: '', - placeholder: 'Placeholder value', - description: 'The description text', - } - ] - }; - - async execute(this: IExecuteFunctions): Promise { - - const items = this.getInputData(); - - let item: INodeExecutionData; - let myString: string; - - // Iterates over all input items and add the key "myString" with the - // value the parameter "myString" resolves to. - // (This could be a different value for each item in case it contains an expression) - for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - myString = this.getNodeParameter('myString', itemIndex, '') as string; - item = items[itemIndex]; - - item.json['myString'] = myString; - } - - return this.prepareOutputData(items); - - } -} diff --git a/nodes/FriendGrid/ContactDescription.ts b/nodes/FriendGrid/ContactDescription.ts new file mode 100644 index 0000000..e19775a --- /dev/null +++ b/nodes/FriendGrid/ContactDescription.ts @@ -0,0 +1,82 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const contactOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a contact', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const contactFields = [ + /*-------------------------------------------------------------------------- */ + /* contact:create */ + /* ------------------------------------------------------------------------- */ + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'contact', + ], + }, + }, + default:'', + description:'Primary email for the contact', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + }, + ], + }, +] as INodeProperties[]; \ No newline at end of file diff --git a/nodes/FriendGrid/FriendGrid.node.ts b/nodes/FriendGrid/FriendGrid.node.ts new file mode 100644 index 0000000..36d139d --- /dev/null +++ b/nodes/FriendGrid/FriendGrid.node.ts @@ -0,0 +1,107 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + contactFields, + contactOperations, +} from './ContactDescription'; + +import { + friendGridApiRequest, +} from './GenericFunctions'; + +export class FriendGrid implements INodeType { + description: INodeTypeDescription = { + displayName: 'FriendGrid', + name: 'friendGrid', + icon: 'file:friendGrid.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume FriendGrid API', + defaults: { + name: 'FriendGrid', + color: '#1A82e2', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'friendGridApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + }, + ], + default: 'contact', + required: true, + description: 'Resource to consume', + }, + ...contactOperations, + ...contactFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + let responseData; + const returnData: IDataObject[] = []; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < items.length; i++) { + try { + if (resource === 'contact') { + if (operation === 'create') { + + const email = this.getNodeParameter('email', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const data: IDataObject = { + email, + }; + + Object.assign(data, additionalFields); + + const body: IDataObject = { + contacts: [ + data, + ], + }; + + responseData = await friendGridApiRequest.call(this, 'POST', '/contact', body); + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.message }); + continue; + } + throw error; + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} \ No newline at end of file diff --git a/nodes/FriendGrid/GenericFunctions.ts b/nodes/FriendGrid/GenericFunctions.ts new file mode 100644 index 0000000..3aabeda --- /dev/null +++ b/nodes/FriendGrid/GenericFunctions.ts @@ -0,0 +1,53 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +export async function friendGridApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, endpoint: string, body?: object, query?: object, uri?: string): Promise { // tslint:disable-line:no-any + + //Get credentials the user provided for this node + const credentials = await this.getCredentials('friendGridApi') as IDataObject; + + if (credentials === undefined) { + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); + } + + //Make http request according to + const options: OptionsWithUri = { + method, + headers: { + 'Accept': ' application/json', + 'Authorization': `Bearer ${credentials.apiKey}`, + }, + qs: query, + body, + uri: uri || `https://api.sendgrid.com/v3/marketing/${endpoint}`, + json: true, + }; + + if (Object.keys(options.qs).length === 0) { + delete options.qs; + } + if (Object.keys(options.body).length === 0) { + delete options.body; + } + + try { + return this.helpers.request!(options); + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} \ No newline at end of file diff --git a/nodes/FriendGrid/friendGrid.svg b/nodes/FriendGrid/friendGrid.svg new file mode 100644 index 0000000..71cc4c5 --- /dev/null +++ b/nodes/FriendGrid/friendGrid.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index ac210b7..e26412d 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,10 @@ ], "n8n": { "credentials": [ - "dist/credentials/ExampleCredentials.credentials.js" + "dist/credentials/FriendGridApi.credentials.js" ], "nodes": [ - "dist/nodes/ExampleNode/ExampleNode.node.js" + "dist/nodes/FriendGrid/FriendGrid.node.js" ] }, "devDependencies": {