diff --git a/packages/nodes-base/credentials/WordpressApi.credentials.ts b/packages/nodes-base/credentials/WordpressApi.credentials.ts new file mode 100644 index 0000000000..abe75d55d1 --- /dev/null +++ b/packages/nodes-base/credentials/WordpressApi.credentials.ts @@ -0,0 +1,29 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class WordpressApi implements ICredentialType { + name = 'wordpressApi'; + displayName = 'Wordpress API'; + properties = [ + { + displayName: 'Username', + name: 'username', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Wordpress/GenericFunctions.ts b/packages/nodes-base/nodes/Wordpress/GenericFunctions.ts new file mode 100644 index 0000000000..7229766c45 --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/GenericFunctions.ts @@ -0,0 +1,74 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions, + BINARY_ENCODING, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function wordpressApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('wordpressApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const data = Buffer.from(`${credentials!.username}:${credentials!.password}`).toString(BINARY_ENCODING); + const headerWithAuthentication = Object.assign({}, + { Authorization: `Basic ${data}`, Accept: 'application/json', 'Content-Type': 'application/json' }); + + let options: OptionsWithUri = { + headers: headerWithAuthentication, + method, + qs, + body, + uri: uri ||`${credentials!.domain}/wp-json/wp/v2${resource}`, + json: true + }; + options = Object.assign({}, options, option); + if (Object.keys(options.body).length === 0) { + delete options.body; + } + try { + return await this.helpers.request!(options); + } catch (error) { + let errorMessage = error.message; + if (error.response.body) { + errorMessage = error.response.body.message || error.response.body.Message || error.message; + } + + throw new Error(errorMessage); + } +} + +export async function wordpressApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.per_page = 10; + query.page = 0; + + let uri: string | undefined; + + do { + query.page++; + responseData = await wordpressApiRequest.call(this, method, endpoint, body, query, uri, { resolveWithFullResponse: true }); + returnData.push.apply(returnData, responseData.body); + } while ( + responseData.headers['x-wp-totalpages'] !== undefined && + parseInt(responseData.headers['x-wp-totalpages'], 10) < query.page + ); + + return returnData; +} + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} diff --git a/packages/nodes-base/nodes/Wordpress/PostDescription.ts b/packages/nodes-base/nodes/Wordpress/PostDescription.ts new file mode 100644 index 0000000000..752667748c --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/PostDescription.ts @@ -0,0 +1,810 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const postOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'post', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a post', + }, + { + name: 'Update', + value: 'update', + description: 'Update a post', + }, + { + name: 'Get', + value: 'get', + description: 'Get a post', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all posts', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a post', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const postFields = [ + +/* -------------------------------------------------------------------------- */ +/* post:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Title', + name: 'title', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'create', + ] + }, + }, + description: 'The title for the post', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Content', + name: 'content', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'The content for the post', + }, + { + displayName: 'Slug', + name: 'slug', + type: 'string', + default: '', + description: 'An alphanumeric identifier for the object unique to its type.', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: 'A password to protect access to the content and excerpt.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Publish', + value: 'publish' + }, + { + name: 'Future', + value: 'future' + }, + { + name: 'Draft', + value: 'draft' + }, + { + name: 'Pending', + value: 'pending' + }, + { + name: 'Private', + value: 'private' + }, + ], + default: '', + description: 'A named status for the post.', + }, + { + displayName: 'Comment Status', + name: 'commentStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open' + }, + { + name: 'Close', + value: 'closed' + }, + ], + default: '', + description: 'Whether or not comments are open on the post.', + }, + { + displayName: 'Ping Status', + name: 'pingStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open' + }, + { + name: 'Close', + value: 'closed' + }, + ], + default: '', + description: 'Whether or not comments are open on the post.', + }, + { + displayName: 'Format', + name: 'format', + type: 'options', + options: [ + { + name: 'Standard', + value: 'standard' + }, + { + name: 'Aside', + value: 'aside' + }, + { + name: 'Chat', + value: 'chat' + }, + { + name: 'Gallery', + value: 'gallery' + }, + { + name: 'Link', + value: 'link' + }, + { + name: 'Image', + value: 'image' + }, + { + name: 'Quote', + value: 'quote' + }, + { + name: 'Status', + value: 'status' + }, + { + name: 'Video', + value: 'video' + }, + { + name: 'Audio', + value: 'audio' + }, + ], + default: '', + description: 'Whether or not comments are open on the post.', + }, + { + displayName: 'Sticky', + name: 'sticky', + type: 'boolean', + default: false, + description: 'Whether or not the object should be treated as sticky.', + }, + { + displayName: 'Categories', + name: 'categories', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getCategories', + }, + default: [], + description: 'The terms assigned to the object in the category taxonomy.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + default: [], + description: 'The terms assigned to the object in the post_tag taxonomy.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* post:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Post ID', + name: 'postId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'update', + ] + }, + }, + description: 'Unique identifier for the object.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'The title for the post', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'The content for the post', + }, + { + displayName: 'Slug', + name: 'slug', + type: 'string', + default: '', + description: 'An alphanumeric identifier for the object unique to its type.', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: 'A password to protect access to the content and excerpt.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Publish', + value: 'publish' + }, + { + name: 'Future', + value: 'future' + }, + { + name: 'Draft', + value: 'draft' + }, + { + name: 'Pending', + value: 'pending' + }, + { + name: 'Private', + value: 'private' + }, + ], + default: '', + description: 'A named status for the post.', + }, + { + displayName: 'Comment Status', + name: 'commentStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open' + }, + { + name: 'Close', + value: 'closed' + }, + ], + default: '', + description: 'Whether or not comments are open on the post.', + }, + { + displayName: 'Ping Status', + name: 'pingStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open' + }, + { + name: 'Close', + value: 'closed' + }, + ], + default: '', + description: 'Whether or not comments are open on the post.', + }, + { + displayName: 'Format', + name: 'format', + type: 'options', + options: [ + { + name: 'Standard', + value: 'standard' + }, + { + name: 'Aside', + value: 'aside' + }, + { + name: 'Chat', + value: 'chat' + }, + { + name: 'Gallery', + value: 'gallery' + }, + { + name: 'Link', + value: 'link' + }, + { + name: 'Image', + value: 'image' + }, + { + name: 'Quote', + value: 'quote' + }, + { + name: 'Status', + value: 'status' + }, + { + name: 'Video', + value: 'video' + }, + { + name: 'Audio', + value: 'audio' + }, + ], + default: '', + description: 'Whether or not comments are open on the post.', + }, + { + displayName: 'Sticky', + name: 'sticky', + type: 'boolean', + default: false, + description: 'Whether or not the object should be treated as sticky.', + }, + { + displayName: 'Categories', + name: 'categories', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getCategories', + }, + default: [], + description: 'The terms assigned to the object in the category taxonomy.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + default: [], + description: 'The terms assigned to the object in the post_tag taxonomy.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* post:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Post ID', + name: 'postId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the object.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'get', + ], + }, + }, + options: [ + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: 'The password for the post if it is password protected.', + }, + { + displayName: 'Context', + name: 'context', + type: 'options', + options: [ + { + name: 'View', + value: 'view', + }, + { + name: 'Embed', + value: 'embed', + }, + { + name: 'Edit', + value: 'edit', + }, + ], + default: 'view', + description: 'Scope under which the request is made; determines fields present in response.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* post:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'post', + ], + 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: [ + 'post', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Context', + name: 'context', + type: 'options', + options: [ + { + name: 'View', + value: 'view', + }, + { + name: 'Embed', + value: 'embed', + }, + { + name: 'Edit', + value: 'edit', + }, + ], + default: [], + description: 'Scope under which the request is made; determines fields present in response.', + }, + { + displayName: 'Order By', + name: 'orderBy', + type: 'options', + options: [ + { + name: 'Author', + value: 'author', + }, + { + name: 'Date', + value: 'date', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Include', + value: 'include', + }, + { + name: 'Modified', + value: 'modified', + }, + { + name: 'Parent', + value: 'parent', + }, + { + name: 'Relevance', + value: 'relevance', + }, + { + name: 'Slug', + value: 'slug', + }, + { + name: 'Include Slugs', + value: 'include_slugs', + }, + { + name: 'Title', + value: 'title', + }, + ], + default: [], + description: 'Sort collection by object attribute.', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'Desc', + value: 'desc', + }, + { + name: 'Asc', + value: 'asc', + }, + ], + default: 'desc', + description: 'Order sort attribute ascending or descending.', + }, + { + displayName: 'Search', + name: 'search', + type: 'string', + default: '', + description: 'Limit results to those matching a string.', + }, + { + displayName: 'After', + name: 'after', + type: 'dateTime', + default: '', + description: 'Limit response to posts published after a given ISO8601 compliant date.', + }, + { + displayName: 'Before', + name: 'before', + type: 'dateTime', + default: '', + description: 'Limit response to posts published before a given ISO8601 compliant date.', + }, + { + displayName: 'Author', + name: 'author', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getAuthors', + }, + description: 'Limit result set to posts assigned to specific authors.', + }, + { + displayName: 'Categories', + name: 'categories', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getCategories', + }, + description: 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', + }, + { + displayName: 'Exclude Categories', + name: 'excludedCategories', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getCategories', + }, + description: 'Limit result set to all items except those that have the specified term assigned in the categories taxonomy.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getTags', + }, + description: 'Limit result set to all items that have the specified term assigned in the tags taxonomy.', + }, + { + displayName: 'Exclude Tags', + name: 'excludedTags', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getTags', + }, + description: 'Limit result set to all items except those that have the specified term assigned in the tags taxonomy.', + }, + { + displayName: 'Sticky', + name: 'sticky', + type: 'boolean', + default: false, + description: 'Limit result set to items that are sticky.', + }, + + ] + }, +/* -------------------------------------------------------------------------- */ +/* post:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Post ID', + name: 'postId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'delete', + ] + }, + }, + description: 'Unique identifier for the object.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'post', + ], + operation: [ + 'delete', + ], + }, + }, + options: [ + { + displayName: 'Force', + name: 'force', + type: 'boolean', + default: false, + description: 'Whether to bypass trash and force deletion.', + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Wordpress/PostInterface.ts b/packages/nodes-base/nodes/Wordpress/PostInterface.ts new file mode 100644 index 0000000000..ad4b378a1d --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/PostInterface.ts @@ -0,0 +1,15 @@ + +export interface IPost { + id?: number; + title?: string; + content?: string; + slug?: string; + password?: string; + status?: string; + comment_status?: string; + ping_status?: string; + format?: string; + sticky?: boolean; + categories?: number[]; + tags?: number[]; +} diff --git a/packages/nodes-base/nodes/Wordpress/UserDescription.ts b/packages/nodes-base/nodes/Wordpress/UserDescription.ts new file mode 100644 index 0000000000..62b1f1b2b1 --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/UserDescription.ts @@ -0,0 +1,567 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const userOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'user', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a user', + }, + { + name: 'Update', + value: 'update', + description: 'Update a user', + }, + { + name: 'Get', + value: 'get', + description: 'Get a user', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all users', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a user', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const userFields = [ + +/* -------------------------------------------------------------------------- */ +/* user:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Username', + name: 'username', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + }, + description: 'Login name for the user.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + }, + description: 'Display name for the user.', + }, + { + displayName: 'Fistname', + name: 'firstName', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + }, + description: 'First name for the user.', + }, + { + displayName: 'Lastname', + name: 'lastName', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + }, + description: 'Last name for the user.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + }, + description: 'The email address for the user.', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ] + }, + }, + description: 'Password for the user (never included)', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'URL', + name: 'url', + type: 'string', + default: '', + description: 'URL of the user.', + }, + { + displayName: 'Description', + name: 'description', + typeOptions: { + alwaysOpenEditWindow: true, + }, + type: 'string', + default: '', + description: 'Description of the user.', + }, + { + displayName: 'Nickname', + name: 'nickname', + type: 'string', + default: '', + description: 'The nickname for the user.', + }, + { + displayName: 'Slug', + name: 'slug', + type: 'string', + default: '', + description: 'An alphanumeric identifier for the user.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* user:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'update', + ] + }, + }, + description: 'Unique identifier for the user.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + description: 'Login name for the user.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Display name for the user.', + }, + { + displayName: 'Fistname', + name: 'firstName', + type: 'string', + default: '', + description: 'First name for the user.', + }, + { + displayName: 'Lastname', + name: 'lastName', + type: 'string', + default: '', + description: 'Last name for the user.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'The email address for the user.', + }, + { + displayName: 'Password', + name: 'password', + type: 'password', + default: '', + description: 'Password for the user (never included)', + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + default: '', + description: 'URL of the user.', + }, + { + displayName: 'Description', + name: 'description', + typeOptions: { + alwaysOpenEditWindow: true, + }, + type: 'string', + default: '', + description: 'Description of the user.', + }, + { + displayName: 'Nickname', + name: 'nickname', + type: 'string', + default: '', + description: 'The nickname for the user.', + }, + { + displayName: 'Slug', + name: 'slug', + type: 'string', + default: '', + description: 'An alphanumeric identifier for the user.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* user:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the user.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'get', + ], + }, + }, + options: [ + { + displayName: 'Context', + name: 'context', + type: 'options', + options: [ + { + name: 'View', + value: 'view', + }, + { + name: 'Embed', + value: 'embed', + }, + { + name: 'Edit', + value: 'edit', + }, + ], + default: 'view', + description: 'Scope under which the request is made; determines fields present in response.', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* user:getAll */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'user', + ], + 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: [ + 'user', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', +}, +{ + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Context', + name: 'context', + type: 'options', + options: [ + { + name: 'View', + value: 'view', + }, + { + name: 'Embed', + value: 'embed', + }, + { + name: 'Edit', + value: 'edit', + }, + ], + default: [], + description: 'Scope under which the request is made; determines fields present in response.', + }, + { + displayName: 'Order By', + name: 'orderBy', + type: 'options', + options: [ + { + name: 'Name', + value: 'name', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Include', + value: 'include', + }, + { + name: 'Registered Date', + value: 'registered_date', + }, + { + name: 'Slug', + value: 'slug', + }, + { + name: 'Include Slugs', + value: 'include_slugs', + }, + { + name: 'Email', + value: 'email', + }, + { + name: 'URL', + value: 'url', + }, + ], + default: [], + description: 'Sort collection by object attribute.', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'Desc', + value: 'desc', + }, + { + name: 'Asc', + value: 'asc', + }, + ], + default: 'desc', + description: 'Order sort attribute ascending or descending.', + }, + { + displayName: 'Search', + name: 'search', + type: 'string', + default: '', + description: 'Limit results to those matching a string.', + }, + { + displayName: 'Who', + name: 'who', + type: 'options', + options: [ + { + name: 'Authors', + value: 'authors', + }, + ], + default: 'authors', + description: 'Limit result set to users who are considered authors.', + }, + ] +}, +/* -------------------------------------------------------------------------- */ +/* user:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Reassign', + name: 'reassign', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'delete', + ] + }, + }, + description: `Reassign the deleted user's posts and links to this user ID.`, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Wordpress/UserInterface.ts b/packages/nodes-base/nodes/Wordpress/UserInterface.ts new file mode 100644 index 0000000000..9bcf3cdeac --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/UserInterface.ts @@ -0,0 +1,14 @@ + +export interface IUser { + id?: number; + username?: string; + name?: string; + first_name?: string; + nickname?: string; + slug?: string; + last_name?: string; + email?: string; + url?: string; + description?: string; + password?: string; +} diff --git a/packages/nodes-base/nodes/Wordpress/Wordpress.node.ts b/packages/nodes-base/nodes/Wordpress/Wordpress.node.ts new file mode 100644 index 0000000000..03735a5617 --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/Wordpress.node.ts @@ -0,0 +1,467 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + wordpressApiRequest, + wordpressApiRequestAllItems, +} from './GenericFunctions'; +import { + postOperations, + postFields, +} from './PostDescription'; +import { + userOperations, + userFields, +} from './UserDescription'; +import { + IPost, +} from './PostInterface'; +import { + IUser, +} from './UserInterface'; + +export class Wordpress implements INodeType { + description: INodeTypeDescription = { + displayName: 'Wordpress', + name: 'Wordpress', + icon: 'file:wordpress.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Wordpress API', + defaults: { + name: 'Wordpress', + color: '#c02428', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'wordpressApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Post', + value: 'post', + description: '', + }, + { + name: 'User', + value: 'user', + description: '', + }, + ], + default: 'post', + description: 'Resource to consume.', + }, + ...postOperations, + ...postFields, + ...userOperations, + ...userFields, + ], + }; + + methods = { + loadOptions: { + // Get all the available categories to display them to user so that he can + // select them easily + async getCategories(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let categories; + try { + categories = await wordpressApiRequestAllItems.call(this, 'GET', '/categories', {}); + } catch (err) { + throw new Error(`Wordpress Error: ${err}`); + } + for (const category of categories) { + const categoryName = category.name; + const categoryId = category.id; + + returnData.push({ + name: categoryName, + value: categoryId, + }); + } + return returnData; + }, + // Get all the available tags to display them to user so that he can + // select them easily + async getTags(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let tags; + try { + tags = await wordpressApiRequestAllItems.call(this, 'GET', '/tags', {}); + } catch (err) { + throw new Error(`Wordpress Error: ${err}`); + } + for (const tag of tags) { + const tagName = tag.name; + const tagId = tag.id; + + returnData.push({ + name: tagName, + value: tagId, + }); + } + return returnData; + }, + // Get all the available authors to display them to user so that he can + // select them easily + async getAuthors(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let authors; + try { + authors = await wordpressApiRequestAllItems.call(this, 'GET', '/users', {}, { who: 'authors' }); + } catch (err) { + throw new Error(`Wordpress Error: ${err}`); + } + for (const author of authors) { + const authorName = author.name; + const authorId = author.id; + + returnData.push({ + name: authorName, + value: authorId, + }); + } + return returnData; + }, + }, + }; + + 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; + + for (let i = 0; i < length; i++) { + if (resource === 'post') { + //https://developer.wordpress.org/rest-api/reference/posts/#create-a-post + if (operation === 'create') { + const title = this.getNodeParameter('title', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IPost = { + title, + }; + if (additionalFields.content) { + body.content = additionalFields.content as string; + } + if (additionalFields.slug) { + body.slug = additionalFields.slug as string; + } + if (additionalFields.password) { + body.password = additionalFields.password as string; + } + if (additionalFields.status) { + body.status = additionalFields.status as string; + } + if (additionalFields.commentStatus) { + body.comment_status = additionalFields.commentStatus as string; + } + if (additionalFields.pingStatus) { + body.ping_status = additionalFields.pingStatus as string; + } + if (additionalFields.sticky) { + body.sticky = additionalFields.sticky as boolean; + } + if (additionalFields.categories) { + body.categories = additionalFields.categories as number[]; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as number[]; + } + if (additionalFields.format) { + body.format = additionalFields.format as string; + } + try{ + responseData = await wordpressApiRequest.call(this, 'POST', '/posts', body); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/posts/#update-a-post + if (operation === 'update') { + const postId = this.getNodeParameter('postId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IPost = { + id: parseInt(postId, 10), + }; + if (updateFields.title) { + body.title = updateFields.title as string; + } + if (updateFields.content) { + body.content = updateFields.content as string; + } + if (updateFields.slug) { + body.slug = updateFields.slug as string; + } + if (updateFields.password) { + body.password = updateFields.password as string; + } + if (updateFields.status) { + body.status = updateFields.status as string; + } + if (updateFields.commentStatus) { + body.comment_status = updateFields.commentStatus as string; + } + if (updateFields.pingStatus) { + body.ping_status = updateFields.pingStatus as string; + } + if (updateFields.sticky) { + body.sticky = updateFields.sticky as boolean; + } + if (updateFields.categories) { + body.categories = updateFields.categories as number[]; + } + if (updateFields.tags) { + body.tags = updateFields.tags as number[]; + } + if (updateFields.format) { + body.format = updateFields.format as string; + } + try { + responseData = await wordpressApiRequest.call(this, 'POST', `/posts/${postId}`, body); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/posts/#retrieve-a-post + if (operation === 'get') { + const postId = this.getNodeParameter('postId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.password) { + qs.password = options.password as string; + } + if (options.context) { + qs.context = options.context as string; + } + try { + responseData = await wordpressApiRequest.call(this,'GET', `/posts/${postId}`, {}, qs); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/posts/#list-posts + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.context) { + qs.context = filters.context as string; + } + if (filters.orderBy) { + qs.orderby = filters.orderBy as string; + } + if (filters.order) { + qs.order = filters.order as string; + } + if (filters.search) { + qs.search = filters.search as string; + } + if (filters.after) { + qs.after = filters.after as string; + } + if (filters.author) { + qs.author = filters.author as number[]; + } + if (filters.categories) { + qs.categories = filters.categories as number[]; + } + if (filters.excludedCategories) { + qs.categories_exclude = filters.excludedCategories as number[]; + } + if (filters.tags) { + qs.tags = filters.tags as number[]; + } + if (filters.excludedTags) { + qs.tags_exclude = filters.excludedTags as number[]; + } + if (filters.sticky) { + qs.sticky = filters.sticky as boolean; + } + try { + if (returnAll === true) { + responseData = await wordpressApiRequestAllItems.call(this, 'GET', '/posts', {}, qs); + } else { + qs.per_page = this.getNodeParameter('limit', i) as number; + responseData = await wordpressApiRequest.call(this, 'GET', '/posts', {}, qs); + } + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/posts/#delete-a-post + if (operation === 'delete') { + const postId = this.getNodeParameter('postId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.force) { + qs.force = options.force as boolean; + } + try { + responseData = await wordpressApiRequest.call(this, 'DELETE', `/posts/${postId}`, {}, qs); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + } + if (resource === 'user') { + //https://developer.wordpress.org/rest-api/reference/users/#create-a-user + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + const username = this.getNodeParameter('username', i) as string; + const firstName = this.getNodeParameter('firstName', i) as string; + const lastName = this.getNodeParameter('lastName', i) as string; + const email = this.getNodeParameter('email', i) as string; + const password = this.getNodeParameter('password', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IUser = { + name, + username, + first_name: firstName, + last_name: lastName, + email, + password, + }; + if (additionalFields.url) { + body.url = additionalFields.url as string; + } + if (additionalFields.description) { + body.description = additionalFields.description as string; + } + if (additionalFields.nickname) { + body.nickname = additionalFields.nickname as string; + } + if (additionalFields.slug) { + body.slug = additionalFields.slug as string; + } + try{ + responseData = await wordpressApiRequest.call(this, 'POST', '/users', body); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/users/#update-a-user + if (operation === 'update') { + const userId = this.getNodeParameter('userId', i) as number; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IUser = { + id: userId, + }; + if (updateFields.name) { + body.name = updateFields.name as string; + } + if (updateFields.firstName) { + body.first_name = updateFields.firstName as string; + } + if (updateFields.lastName) { + body.last_name = updateFields.lastName as string; + } + if (updateFields.email) { + body.email = updateFields.email as string; + } + if (updateFields.password) { + body.password = updateFields.password as string; + } + if (updateFields.username) { + body.username = updateFields.username as string; + } + if (updateFields.url) { + body.url = updateFields.url as string; + } + if (updateFields.description) { + body.description = updateFields.description as string; + } + if (updateFields.nickname) { + body.nickname = updateFields.nickname as string; + } + if (updateFields.slug) { + body.slug = updateFields.slug as string; + } + try{ + responseData = await wordpressApiRequest.call(this, 'POST', `/users/${userId}`, body); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/users/#retrieve-a-user + if (operation === 'get') { + const userId = this.getNodeParameter('userId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.context) { + qs.context = options.context as string; + } + try { + responseData = await wordpressApiRequest.call(this,'GET', `/users/${userId}`, {}, qs); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/users/#list-users + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.context) { + qs.context = filters.context as string; + } + if (filters.orderBy) { + qs.orderby = filters.orderBy as string; + } + if (filters.order) { + qs.order = filters.order as string; + } + if (filters.search) { + qs.search = filters.search as string; + } + if (filters.who) { + qs.who = filters.who as string; + } + try { + if (returnAll === true) { + responseData = await wordpressApiRequestAllItems.call(this, 'GET', '/users', {}, qs); + } else { + qs.per_page = this.getNodeParameter('limit', i) as number; + responseData = await wordpressApiRequest.call(this, 'GET', '/users', {}, qs); + } + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + //https://developer.wordpress.org/rest-api/reference/users/#delete-a-user + if (operation === 'delete') { + const reassign = this.getNodeParameter('reassign', i) as string; + qs.reassign = reassign; + qs.force = true; + try { + responseData = await wordpressApiRequest.call(this, 'DELETE', `/users/me`, {}, qs); + } catch (err) { + throw new Error(`Wordpress Error: ${err.message}`); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Wordpress/wordpress.png b/packages/nodes-base/nodes/Wordpress/wordpress.png new file mode 100644 index 0000000000..55005aedff Binary files /dev/null and b/packages/nodes-base/nodes/Wordpress/wordpress.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 4242762f70..dc5747eb36 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -71,10 +71,11 @@ "dist/credentials/TwilioApi.credentials.js", "dist/credentials/TypeformApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/TodoistApi.credentials.js", - "dist/credentials/TypeformApi.credentials.js", - "dist/credentials/TogglApi.credentials.js", - "dist/credentials/VeroApi.credentials.js" + "dist/credentials/TodoistApi.credentials.js", + "dist/credentials/TypeformApi.credentials.js", + "dist/credentials/TogglApi.credentials.js", + "dist/credentials/VeroApi.credentials.js", + "dist/credentials/WordpressApi.credentials.js" ], "nodes": [ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", @@ -127,7 +128,8 @@ "dist/nodes/MoveBinaryData.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/MySql/MySql.node.js", - "dist/nodes/NextCloud/NextCloud.node.js", + "dist/nodes/NextCloud/NextCloud.node.js", + "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/NoOp.node.js", "dist/nodes/OpenWeatherMap.node.js", "dist/nodes/Pipedrive/Pipedrive.node.js", @@ -161,11 +163,9 @@ "dist/nodes/Toggl/TogglTrigger.node.js", "dist/nodes/Vero/Vero.node.js", "dist/nodes/WriteBinaryFile.node.js", - "dist/nodes/Webhook.node.js", - "dist/nodes/Xml.node.js", - "dist/nodes/Mandrill/Mandrill.node.js", - "dist/nodes/Todoist/Todoist.node.js", - "dist/nodes/Xml.node.js" + "dist/nodes/Webhook.node.js", + "dist/nodes/Wordpress/Wordpress.node.js", + "dist/nodes/Xml.node.js" ] }, "devDependencies": {