From c6788eed2005e1c3ad663fccba7bd4a78e439400 Mon Sep 17 00:00:00 2001 From: ricardo Date: Fri, 22 May 2020 09:47:47 -0400 Subject: [PATCH] :zap: Mailchimp improvements --- .../nodes/Mailchimp/GenericFunctions.ts | 42 +- .../nodes/Mailchimp/Mailchimp.node.ts | 1201 +++++++++++++++-- 2 files changed, 1128 insertions(+), 115 deletions(-) diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts index 6f0aeb0e8c..a78124feed 100644 --- a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -1,13 +1,19 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, + } from 'request'; import { IExecuteFunctions, + IExecuteSingleFunctions, IHookFunctions, ILoadOptionsFunctions, - IExecuteSingleFunctions } from 'n8n-core'; -export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any +import { + IDataObject, + } from 'n8n-workflow'; + +export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, qs: IDataObject = {} ,headers?: object): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('mailchimpApi'); if (credentials === undefined) { @@ -27,6 +33,7 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio const options: OptionsWithUri = { headers: headerWithAuthentication, method, + qs, uri: `https://${datacenter}.${host}${endpoint}`, json: true, }; @@ -34,19 +41,36 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio if (Object.keys(body).length !== 0) { options.body = body; } - try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.response.body.message || error.response.body.Message; - - if (errorMessage !== undefined) { - throw errorMessage; + if (error.response.body && error.response.body.detail) { + throw new Error(`Mailchimp Error: response [${error.statusCode}]: ${error.response.body.detail}`); } - throw error.response.body; + throw new Error(error); } } +export async function mailchimpApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, endpoint: string, method: string, propertyName: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.offset = 0; + query.count = 500; + + do { + responseData = await mailchimpApiRequest.call(this, endpoint, method, body, query); + returnData.push.apply(returnData, responseData[propertyName]); + query.offset += query.count; + } while ( + responseData[propertyName] && responseData[propertyName].length !== 0 + ); + + return returnData; +} + export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any let result; try { diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index 187512ff29..cb54ac24e9 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -1,21 +1,25 @@ -import * as moment from 'moment'; import { - IExecuteSingleFunctions, + IExecuteFunctions, } from 'n8n-core'; + import { IDataObject, ILoadOptionsFunctions, - INodeTypeDescription, INodeExecutionData, - INodeType, INodePropertyOptions, + INodeType, + INodeTypeDescription, } from 'n8n-workflow'; + import { mailchimpApiRequest, + mailchimpApiRequestAllItems, validateJSON, } from './GenericFunctions'; +import * as moment from 'moment'; + enum Status { subscribe = 'subscribe', unsubscribed = 'unsubscribe', @@ -37,7 +41,7 @@ interface ICreateMemberBody { language?: string; vip?: boolean; location?: ILocation; - ips_signup?: string; + ip_signup?: string; timestamp_signup?: string; ip_opt?: string; timestamp_opt?: string; @@ -57,7 +61,7 @@ export class Mailchimp implements INodeType { description: 'Consume Mailchimp API', defaults: { name: 'Mailchimp', - color: '#c02428', + color: '#000000', }, inputs: ['main'], outputs: ['main'], @@ -76,7 +80,10 @@ export class Mailchimp implements INodeType { { name: 'Member', value: 'member', - description: 'Add member to list', + }, + { + name: 'Member Tag', + value: 'memberTag', }, ], default: 'member', @@ -101,10 +108,60 @@ export class Mailchimp implements INodeType { value: 'create', description: 'Create a new member on list', }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a member on list', + }, + { + name: 'Get', + value: 'get', + description: 'Get a member on list', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all members on list', + }, + { + name: 'Update', + value: 'update', + description: 'Update a new member on list', + }, ], default: 'create', description: 'The operation to perform.', }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'memberTag', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Add tags from a list member', + }, + { + name: 'Delete', + value: 'delete', + description: 'Remove tags from a list member', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +/* -------------------------------------------------------------------------- */ +/* member:create */ +/* -------------------------------------------------------------------------- */ { displayName: 'List', name: 'list', @@ -230,8 +287,8 @@ export class Mailchimp implements INodeType { type: 'options', options: [ { - name: 'Email', - value: 'email', + name: 'HTML', + value: 'html', description: '', }, { @@ -244,11 +301,11 @@ export class Mailchimp implements INodeType { description: 'Type of email this member asked to get', }, { - displayName: 'Signup IP', - name: 'ipSignup', + displayName: 'Language', + name: 'language', type: 'string', default: '', - description: 'IP address the subscriber signed up from.', + description: `If set/detected, the subscriber's language.`, }, { displayName: 'Opt-in IP', @@ -257,6 +314,13 @@ export class Mailchimp implements INodeType { default: '', description: 'The IP address the subscriber used to confirm their opt-in status.', }, + { + displayName: 'Signup IP', + name: 'ipSignup', + type: 'string', + default: '', + description: 'IP address the subscriber signed up from.', + }, { displayName: 'Signup Timestamp', name: 'timestampSignup', @@ -265,11 +329,11 @@ export class Mailchimp implements INodeType { description: 'The date and time the subscriber signed up for the list in ISO 8601 format.', }, { - displayName: 'Language', - name: 'language', + displayName: 'Tags', + name: 'tags', type: 'string', default: '', - description: `If set/detected, the subscriber's language.`, + description: `The tags that are associated with a member separeted by ,.`, }, { displayName: 'Vip', @@ -285,14 +349,7 @@ export class Mailchimp implements INodeType { default: '', description: `The date and time the subscribe confirmed their opt-in status in ISO 8601 format.`, }, - { - displayName: 'Tags', - name: 'tags', - type: 'string', - default: '', - description: `The tags that are associated with a member separeted by ,.`, - }, - ] + ], }, { displayName: 'Location', @@ -373,7 +430,13 @@ export class Mailchimp implements INodeType { { displayName: 'Field Name', name: 'name', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getMergeFields', + loadOptionsDependsOn: [ + 'list', + ], + }, required: true, description: 'Merge Field name', default: '', @@ -436,7 +499,723 @@ export class Mailchimp implements INodeType { }, }, }, - ] +/* -------------------------------------------------------------------------- */ +/* member:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'List', + name: 'list', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLists', + }, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'List of lists', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + required: true, + description: `Member's email`, + }, +/* -------------------------------------------------------------------------- */ +/* member:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'List', + name: 'list', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLists', + }, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'List of lists', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + required: true, + description: `Member's email`, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource:[ + 'member', + ], + operation: [ + 'get', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: 'A comma-separated list of fields to return.', + }, + { + displayName: 'Exclude Fields', + name: 'excludeFields', + type: 'string', + default: '', + description: 'A comma-separated list of fields to exclude.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* member:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'List', + name: 'list', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLists', + }, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'getAll', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'List of lists', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + default: 500, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource:[ + 'member', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Before Last Changed', + name: 'beforeLastChanged', + type: 'dateTime', + default: '', + description: 'Restrict results to subscribers whose information changed before the set timeframe.', + }, + { + displayName: 'Before Timestamp Opt', + name: 'beforeTimestampOpt', + type: 'dateTime', + default: '', + description: 'Restrict results to subscribers who opted-in before the set timeframe', + }, + // { + // displayName: 'Fields', + // name: 'fields', + // type: 'string', + // default: '', + // description: 'A comma-separated list of fields to return.', + // }, + // { + // displayName: 'Exclude Fields', + // name: 'excludeFields', + // type: 'string', + // default: '', + // description: 'A comma-separated list of fields to exclude.', + // }, + { + displayName: 'Email Type', + name: 'emailType', + type: 'options', + options: [ + { + name: 'HTML', + value: 'html', + description: '', + }, + { + name: 'Text', + value: 'text', + description: '', + }, + ], + default: '', + description: 'Type of email this member asked to get', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Subscribed', + value: 'subscribed', + description: '', + }, + { + name: 'Unsubscribed', + value: 'unsubscribed', + description: '', + }, + { + name: 'Cleaned', + value: 'cleaned', + description: '', + }, + { + name: 'Pending', + value: 'pending', + description: '', + }, + { + name: 'Transactional', + value: 'transactional', + description: '', + }, + ], + default: '', + description: `Subscriber's current status.`, + }, + { + displayName: 'Since Last Changed', + name: 'sinceLastChanged', + type: 'dateTime', + default: '', + description: 'Restrict results to subscribers whose information changed after the set timeframe.', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* member:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'List', + name: 'list', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLists', + }, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'List of lists', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Email address of the subscriber.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource:[ + 'member', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Email Type', + name: 'emailType', + type: 'options', + options: [ + { + name: 'HTML', + value: 'html', + description: '', + }, + { + name: 'Text', + value: 'text', + description: '', + }, + ], + default: '', + description: 'Type of email this member asked to get', + }, + { + displayName: 'Language', + name: 'language', + type: 'string', + default: '', + description: `If set/detected, the subscriber's language.`, + }, + { + displayName: 'Merge Fields', + name: 'mergeFieldsUi', + placeholder: 'Add Merge Fields', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + '/resource':[ + 'member' + ], + '/operation':[ + 'update', + ], + '/jsonParameters': [ + false, + ], + }, + }, + description: 'An individual merge var and value for a member.', + options: [ + { + name: 'mergeFieldsValues', + displayName: 'Field', + typeOptions: { + multipleValueButtonText: 'Add Field', + }, + values: [ + { + displayName: 'Field Name', + name: 'name', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getMergeFields', + loadOptionsDependsOn: [ + 'list', + ], + }, + required: true, + description: 'Merge Field name', + default: '', + }, + { + displayName: 'Field Value', + name: 'value', + required: true, + type: 'string', + default: '', + description: 'Merge field value.', + }, + ], + }, + ], + }, + { + displayName: 'Opt-in IP', + name: 'ipOptIn', + type: 'string', + default: '', + description: 'The IP address the subscriber used to confirm their opt-in status.', + }, + { + displayName: 'Signup IP', + name: 'ipSignup', + type: 'string', + default: '', + description: 'IP address the subscriber signed up from.', + }, + { + displayName: 'Signup Timestamp', + name: 'timestampSignup', + type: 'dateTime', + default: '', + description: 'The date and time the subscriber signed up for the list in ISO 8601 format.', + }, + { + displayName: 'Skip Merge Validation', + name: 'skipMergeValidation', + type: 'boolean', + default: false, + description: `If skip_merge_validation is true, member data will be accepted without merge field values,
+ even if the merge field is usually required`, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + required: true, + options: [ + { + name: 'Subscribed', + value: 'subscribed', + description: '', + }, + { + name: 'Unsubscribed', + value: 'unsubscribed', + description: '', + }, + { + name: 'Cleaned', + value: 'cleaned', + description: '', + }, + { + name: 'Pending', + value: 'pending', + description: '', + }, + { + name: 'Transactional', + value: 'transactional', + description: '', + }, + ], + default: '', + description: `Subscriber's current status.`, + }, + { + displayName: 'Vip', + name: 'vip', + type: 'boolean', + default: false, + description: `Vip status for subscribers`, + }, + { + displayName: 'Location', + name: 'locationFieldsUi', + type: 'fixedCollection', + placeholder: 'Add Location', + default: {}, + description: `Subscriber location information.n`, + displayOptions: { + show: { + '/resource':[ + 'member' + ], + '/operation':[ + 'update', + ], + '/jsonParameters': [ + false, + ], + }, + }, + options: [ + { + name: 'locationFieldsValues', + displayName: 'Location', + values: [ + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + required: true, + description: 'The location latitude.', + default: '', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + required: true, + description: 'The location longitude.', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Opt-in Timestamp', + name: 'timestampOpt', + type: 'dateTime', + default: '', + description: `The date and time the subscribe confirmed their opt-in status in ISO 8601 format.`, + }, + ], + }, + { + displayName: 'Merge Fields', + name: 'mergeFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: '', + displayOptions: { + show: { + resource:[ + 'member', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + }, + { + displayName: 'Location', + name: 'locationJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: '', + displayOptions: { + show: { + resource:[ + 'member', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + }, +/* -------------------------------------------------------------------------- */ +/* memberTag:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'List', + name: 'list', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLists', + }, + displayOptions: { + show: { + resource: [ + 'memberTag', + ], + operation: [ + 'create', + 'delete', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'List of lists', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'memberTag', + ], + operation: [ + 'create', + 'delete', + ], + }, + }, + default: '', + description: 'Email address of the subscriber.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Tag', + }, + displayOptions: { + show: { + resource:[ + 'memberTag' + ], + operation: [ + 'create', + 'delete', + ], + }, + }, + default: [], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource:[ + 'memberTag', + ], + operation: [ + 'create', + 'delete', + ], + }, + }, + options: [ + { + displayName: 'Is Syncing', + name: 'isSyncing', + type: 'boolean', + default: false, + description: 'When is_syncing is true, automations based on the tags in the request will not fire', + }, + ], + }, + ], }; @@ -447,17 +1226,10 @@ export class Mailchimp implements INodeType { // select them easily async getLists(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - let lists, response; - try { - response = await mailchimpApiRequest.call(this, '/lists', 'GET'); - lists = response.lists; - } catch (err) { - throw new Error(`Mailchimp Error: ${err}`); - } + const { lists } = await mailchimpApiRequest.call(this, '/lists', 'GET'); for (const list of lists) { const listName = list.name; const listId = list.id; - returnData.push({ name: listName, value: listId, @@ -465,94 +1237,311 @@ export class Mailchimp implements INodeType { } return returnData; }, + + // Get all the available merge fields to display them to user so that he can + // select them easily + async getMergeFields(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const listId = this.getCurrentNodeParameter('list'); + const { merge_fields } = await mailchimpApiRequest.call(this, `/lists/${listId}/merge-fields`, 'GET'); + for (const mergeField of merge_fields) { + const mergeFieldName = mergeField.name; + const mergeFieldId = mergeField.tag; + returnData.push({ + name: mergeFieldName, + value: mergeFieldId, + }); + } + return returnData; + }, } }; - async executeSingle(this: IExecuteSingleFunctions): Promise { - let response = {}; - const resource = this.getNodeParameter('resource') as string; - const operation = this.getNodeParameter('operation') as string; + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + const qs: IDataObject = {}; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; - if (resource === 'member') { - if (operation === 'create') { + for (let i = 0; i < length; i++) { + if (resource === 'member') { + //https://mailchimp.com/developer/reference/lists/list-members/#post_/lists/-list_id-/members + if (operation === 'create') { + const listId = this.getNodeParameter('list', i) as string; + const email = this.getNodeParameter('email', i) as string; + const status = this.getNodeParameter('status', i) as Status; + const options = this.getNodeParameter('options', i) as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters', i) as IDataObject; - const listId = this.getNodeParameter('list') as string; - const email = this.getNodeParameter('email') as string; - const status = this.getNodeParameter('status') as Status; - const options = this.getNodeParameter('options') as IDataObject; - const jsonActive = this.getNodeParameter('jsonParameters') as IDataObject; + const body: ICreateMemberBody = { + listId, + email_address: email, + status + }; + if (options.emailType) { + body.email_type = options.emailType as string; + } + if (options.language) { + body.language = options.language as string; + } + if (options.vip) { + body.vip = options.vip as boolean; + } + if (options.ipSignup) { + body.ip_signup = options.ipSignup as string; + } + if (options.ipOptIn) { + body.ip_opt = options.ipOptIn as string; + } + if (options.timestampOpt) { + body.timestamp_opt = moment(options.timestampOpt as string).format('YYYY-MM-DD HH:MM:SS') as string; + } + if (options.timestampSignup) { + body.timestamp_signup = moment(options.timestampSignup as string).format('YYYY-MM-DD HH:MM:SS') as string; + } + if (options.tags) { + // @ts-ignore + body.tags = options.tags.split(',') as string[]; + } + if (!jsonActive) { + const locationValues = (this.getNodeParameter('locationFieldsUi', i) as IDataObject).locationFieldsValues as IDataObject; + if (locationValues) { + const location: ILocation = {}; + for (const key of Object.keys(locationValues)) { + if (key === 'latitude') { + location.latitude = parseFloat(locationValues[key] as string) as number; + } else if (key === 'longitude') { + location.longitude = parseFloat(locationValues[key] as string) as number; + } + } + body.location = location; + } + const mergeFieldsValues = (this.getNodeParameter('mergeFieldsUi', i) as IDataObject).mergeFieldsValues as IDataObject[]; + if (mergeFieldsValues) { + const mergeFields = {}; + for (let i = 0; i < mergeFieldsValues.length; i++) { + // @ts-ignore + mergeFields[mergeFieldsValues[i].name] = mergeFieldsValues[i].value; + } + body.merge_fields = mergeFields; + } + } else { + const locationJson = validateJSON(this.getNodeParameter('locationJson', i) as string); + const mergeFieldsJson = validateJSON(this.getNodeParameter('mergeFieldsJson', i) as string); + if (locationJson) { + body.location = locationJson; + } + if (mergeFieldsJson) { + body.merge_fields = mergeFieldsJson; + } + } + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members`, 'POST', body); + } + //https://mailchimp.com/developer/reference/lists/list-members/ + if (operation === 'delete') { - const body: ICreateMemberBody = { - listId, - email_address: email, - status - }; - if (options.emailType) { - body.email_type = options.emailType as string; + const listId = this.getNodeParameter('list', i) as string; + const email = this.getNodeParameter('email', i) as string; + + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members/${email}/actions/delete-permanent`, 'POST'); + responseData = { success: true }; } - if (options.languaje) { - body.language = options.language as string; + //https://mailchimp.com/developer/reference/lists/list-members/#get_/lists/-list_id-/members/-subscriber_hash- + if (operation === 'get') { + + const listId = this.getNodeParameter('list', i) as string; + const email = this.getNodeParameter('email', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + if (options.fields) { + qs.fields = options.fields as string; + } + + if (options.excludeFields) { + qs.exclude_fields = options.excludeFields as string; + } + + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members/${email}`, 'GET', {}, qs); } - if (options.vip) { - body.vip = options.vip as boolean; + //https://mailchimp.com/developer/reference/lists/list-members/#get_/lists/-list_id-/members + if (operation === 'getAll') { + const listId = this.getNodeParameter('list', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + + if (options.beforeLastChanged) { + qs.before_last_changed = options.beforeLastChanged as string; + } + if (options.beforeTimestampOpt) { + qs.before_timestamp_opt = options.beforeTimestampOpt as string; + } + // TODO + //figure why for some reason when either fields or exclude_fields is set the endpoint returns nothing + // interestingly it works perfect when retriving just one member + + // if (options.fields) { + // qs.fields = options.fields as string; + // } + // if (options.excludeFields) { + // qs.exclude_fields = options.excludeFields as string; + // } + if (options.emailType) { + qs.email_type = options.emailType as string; + } + if (options.status) { + qs.status = options.status as string; + } + if (options.sinceLastChanged) { + qs.since_last_changed = options.sinceLastChanged as string; + } + if (returnAll === true) { + responseData = await mailchimpApiRequestAllItems.call(this, `/lists/${listId}/members`, 'GET', 'members', {}, qs); + } else { + qs.count = this.getNodeParameter('limit', i) as number; + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members`, 'GET', {}, qs); + responseData = responseData.members; + } } - if (options.ipSignup) { - body.ips_signup = options.ipSignup as string; - } - if (options.ipOptIn) { - body.ip_opt = options.ipOptIn as string; - } - if (options.timestampOpt) { - body.timestamp_opt = moment(options.timestampOpt as string).format('YYYY-MM-DD HH:MM:SS') as string; - } - if (options.timestampSignup) { - body.timestamp_signup = moment(options.timestampSignup as string).format('YYYY-MM-DD HH:MM:SS') as string; - } - if (options.tags) { - // @ts-ignore - body.tags = options.tags.split(',') as string[]; - } - if (!jsonActive) { - const locationValues = (this.getNodeParameter('locationFieldsUi') as IDataObject).locationFieldsValues as IDataObject; - if (locationValues) { - const location: ILocation = {}; - for (const key of Object.keys(locationValues)) { - if (key === 'latitude') { - location.latitude = parseInt(locationValues[key] as string, 10) as number; - } else if (key === 'longitude') { - location.longitude = parseInt(locationValues[key] as string, 10) as number; + //https://mailchimp.com/developer/reference/lists/list-members/#put_/lists/-list_id-/members/-subscriber_hash- + if (operation === 'update') { + + const listId = this.getNodeParameter('list', i) as string; + const email = this.getNodeParameter('email', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters', i) as IDataObject; + const body: ICreateMemberBody = { + listId, + email_address: email, + }; + if (updateFields.skipMergeValidation) { + qs.skip_merge_validation = updateFields.skipMergeValidation as boolean; + } + if (updateFields.status) { + body.status = updateFields.status as Status; + } + if (updateFields.emailType) { + body.email_type = updateFields.emailType as string; + } + if (updateFields.language) { + body.language = updateFields.language as string; + } + if (updateFields.vip) { + body.vip = updateFields.vip as boolean; + } + if (updateFields.ipSignup) { + body.ip_signup = updateFields.ipSignup as string; + } + if (updateFields.ipOptIn) { + body.ip_opt = updateFields.ipOptIn as string; + } + if (updateFields.timestampOpt) { + body.timestamp_opt = moment(updateFields.timestampOpt as string).format('YYYY-MM-DD HH:MM:SS') as string; + } + if (updateFields.timestampSignup) { + body.timestamp_signup = moment(updateFields.timestampSignup as string).format('YYYY-MM-DD HH:MM:SS') as string; + } + if (!jsonActive) { + if (updateFields.locationFieldsUi) { + const locationValues = (updateFields.locationFieldsUi as IDataObject).locationFieldsValues as IDataObject; + if (locationValues) { + const location: ILocation = {}; + for (const key of Object.keys(locationValues)) { + if (key === 'latitude') { + location.latitude = parseFloat(locationValues[key] as string) as number; + } else if (key === 'longitude') { + location.longitude = parseFloat(locationValues[key] as string) as number; + } + } + body.location = location; } } - body.location = location; - } - const mergeFieldsValues = (this.getNodeParameter('mergeFieldsUi') as IDataObject).mergeFieldsValues as IDataObject[]; - if (mergeFieldsValues) { - const mergeFields = {}; - for (let i = 0; i < mergeFieldsValues.length; i++) { - // @ts-ignore - mergeFields[mergeFieldsValues[i].name] = mergeFieldsValues[i].value; + if (updateFields.mergeFieldsUi) { + const mergeFieldsValues = (updateFields.mergeFieldsUi as IDataObject).mergeFieldsValues as IDataObject[]; + if (mergeFieldsValues) { + const mergeFields = {}; + for (let i = 0; i < mergeFieldsValues.length; i++) { + // @ts-ignore + mergeFields[mergeFieldsValues[i].name] = mergeFieldsValues[i].value; + } + body.merge_fields = mergeFields; + } + } + } else { + const locationJson = validateJSON(this.getNodeParameter('locationJson', i) as string); + const mergeFieldsJson = validateJSON(this.getNodeParameter('mergeFieldsJson', i) as string); + if (locationJson) { + body.location = locationJson; + } + if (mergeFieldsJson) { + body.merge_fields = mergeFieldsJson; } - body.merge_fields = mergeFields; } - } else { - const locationJson = validateJSON(this.getNodeParameter('locationJson') as string); - const mergeFieldsJson = validateJSON(this.getNodeParameter('mergeFieldsJson') as string); - if (locationJson) { - body.location = locationJson; - } - if (mergeFieldsJson) { - body.merge_fields = mergeFieldsJson; - } - } - try { - response = await mailchimpApiRequest.call(this, `/lists/${listId}/members`, 'POST', body); - } catch (err) { - throw new Error(`Mailchimp Error: ${JSON.stringify(err)}`); + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members/${email}`, 'PUT', body); } } + if (resource === 'memberTag') { + //https://mailchimp.com/developer/reference/lists/list-members/list-member-tags/#post_/lists/-list_id-/members/-subscriber_hash-/tags + if (operation === 'create') { + const listId = this.getNodeParameter('list', i) as string; + const email = this.getNodeParameter('email', i) as string; + const tags = this.getNodeParameter('tags', i) as string[]; + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IDataObject = { + tags: [], + }; + + if (options.isSyncing) { + body.is_syncing = options.isSyncing as boolean; + } + + for (const tag of tags) { + //@ts-ignore + body.tags.push({ + name: tag, + status: 'active', + }); + } + + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members/${email}/tags`, 'POST', body); + responseData = { success: true }; + } + //https://mailchimp.com/developer/reference/lists/list-members/list-member-tags/#post_/lists/-list_id-/members/-subscriber_hash-/tags + if (operation === 'delete') { + + const listId = this.getNodeParameter('list', i) as string; + const email = this.getNodeParameter('email', i) as string; + const tags = this.getNodeParameter('tags', i) as string[]; + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IDataObject = { + tags: [], + }; + + if (options.isSyncing) { + body.is_syncing = options.isSyncing as boolean; + } + + for (const tag of tags) { + //@ts-ignore + body.tags.push({ + name: tag, + status: 'inactive', + }); + } + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members/${email}/tags`, 'POST', body); + responseData = { success: true }; + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } } - return { - json: response, - }; + return [this.helpers.returnJsonArray(returnData)]; } }