diff --git a/packages/nodes-base/credentials/ContentfulApi.credentials.ts b/packages/nodes-base/credentials/ContentfulApi.credentials.ts new file mode 100644 index 0000000000..d08b97e647 --- /dev/null +++ b/packages/nodes-base/credentials/ContentfulApi.credentials.ts @@ -0,0 +1,36 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +//https://www.contentful.com/developers/docs/references/authentication/ +export class ContentfulApi implements ICredentialType { + name = 'contentfulApi'; + displayName = 'Contenful API'; + properties = [ + { + displayName: 'Space ID', + name: 'spaceId', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + description: 'The id for the Contentful space.' + }, + { + displayName: 'Content Delivery API Access token', + name: 'ContentDeliveryaccessToken', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + description: 'Access token that has access to the space' + }, + { + displayName: 'Content Preview API Access token', + name: 'ContentPreviewaccessToken', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + description: 'Access token that has access to the space' + }, + ]; +} diff --git a/packages/nodes-base/nodes/Contentful/AssetDescription.ts b/packages/nodes-base/nodes/Contentful/AssetDescription.ts new file mode 100644 index 0000000000..dfd523806e --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/AssetDescription.ts @@ -0,0 +1,197 @@ +import { + INodeProperties, + INodePropertyOptions, +} from 'n8n-workflow'; + +export const resource = { + name: 'Asset', + value: 'asset', +} as INodePropertyOptions; + +export const operations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + resource.value, + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + ], + default: 'getAll', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const fields = [ + { + displayName: 'Environment ID', + name: 'environmentId', + type: 'string', + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'get', + 'getAll', + ], + }, + }, + default: 'master', + description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".' + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + resource.value, + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + resource.value, + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Equal', + name: 'equal', + type: 'string', + default: '', + placeholder: 'fields.title=n8n', + description: 'Search for all data that matches the condition: {attribute}={value}. Attribute can use dot notation.', + }, + { + displayName: 'Exclude', + name: 'exclude', + type: 'string', + default: '', + placeholder: 'fields.tags[nin]=accessories,flowers', + description: 'Search for all data that matches the condition: {attribute}[nin]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Exist', + name: 'exist', + type: 'string', + default: '', + placeholder: 'fields.tags[exists]=true', + description: 'Search for all data that matches the condition: {attribute}[exists]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Fields', + name: 'select', + type: 'string', + placeholder: 'fields.title', + default: '', + description: 'The select operator allows you to choose what fields to return from an entity. You can choose multiple values by combining comma separated operators.', + }, + { + displayName: 'Include', + name: 'include', + type: 'string', + default: '', + placeholder: 'fields.tags[in]=accessories,flowers', + description: 'Search for all data that matches the condition: {attribute}[in]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Not Equal', + name: 'notEqual', + type: 'string', + default: '', + placeholder: 'fields.title[ne]=n8n', + description: 'Search for all data that matches the condition: {attribute}[ne]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Order', + name: 'order', + type: 'string', + default: '', + placeholder: 'sys.createdAt', + description: 'You can order items in the response by specifying the order search parameter. You can use sys properties (such as sys.createdAt) or field values (such as fields.myCustomDateField) for ordering.', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + description: ' Full-text search is case insensitive and might return more results than expected. A query will only take values with more than 1 character.', + }, + ], + }, + { + displayName: 'Asset ID', + name: 'assetId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + resource.value + ], + operation: [ + 'get', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Contentful/ContentTypeDescription.ts b/packages/nodes-base/nodes/Contentful/ContentTypeDescription.ts new file mode 100644 index 0000000000..6b2fa4eb3f --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/ContentTypeDescription.ts @@ -0,0 +1,69 @@ +import { + INodeProperties, + INodePropertyOptions, +} from 'n8n-workflow'; + +export const resource = { + name: 'Content Type', + value: 'contentType', +} as INodePropertyOptions; + +export const operations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + resource.value, + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const fields = [ + { + displayName: 'Environment ID', + name: 'environmentId', + type: 'string', + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'get', + ], + }, + }, + default: 'master', + description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".' + }, + { + displayName: 'Content Type ID', + name: 'contentTypeId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'get', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Contentful/Contentful.node.ts b/packages/nodes-base/nodes/Contentful/Contentful.node.ts new file mode 100644 index 0000000000..820e8b07a9 --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/Contentful.node.ts @@ -0,0 +1,266 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + contentfulApiRequest, + contenfulApiRequestAllItems, +} from './GenericFunctions'; + +import * as SpaceDescription from './SpaceDescription'; +import * as ContentTypeDescription from './ContentTypeDescription'; +import * as EntryDescription from './EntryDescription'; +import * as AssetDescription from './AssetDescription'; +import * as LocaleDescription from './LocaleDescription'; + +export class Contentful implements INodeType { + description: INodeTypeDescription = { + displayName: 'Contentful', + name: 'contentful', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + icon: 'file:contentful.png', + group: ['input'], + version: 1, + description: 'Consume Contenful API', + defaults: { + name: 'Contentful', + color: '#2E75D4' + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'contentfulApi', + required: true + }, + ], + properties: [ + { + displayName: 'Source', + name: 'source', + type: 'options', + default: 'Delivery API', + description: 'Pick where your data comes from, delivery or preview API', + options: [ + { + name: 'Delivery API', + value: 'deliveryApi' + }, + { + name: 'Preview API', + value: 'previewApi' + }, + ], + }, + // Resources: + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + AssetDescription.resource, + ContentTypeDescription.resource, + EntryDescription.resource, + LocaleDescription.resource, + SpaceDescription.resource, + ], + default: '', + description: 'The resource to operate on.' + }, + + // Operations: + ...SpaceDescription.operations, + ...ContentTypeDescription.operations, + ...EntryDescription.operations, + ...AssetDescription.operations, + ...LocaleDescription.operations, + + // Resource specific fields: + ...SpaceDescription.fields, + ...ContentTypeDescription.fields, + ...EntryDescription.fields, + ...AssetDescription.fields, + ...LocaleDescription.fields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + let responseData; + + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const qs: Record = {}; + + for (let i = 0; i < items.length; i++) { + if (resource === 'space') { + if (operation === 'get') { + + const credentials = this.getCredentials('contentfulApi'); + + responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}`); + } + } + if (resource === 'contentType') { + if (operation === 'get') { + + const credentials = this.getCredentials('contentfulApi'); + + const env = this.getNodeParameter('environmentId', 0) as string; + + const id = this.getNodeParameter('contentTypeId', 0) as string; + + responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/content_types/${id}`); + } + } + if (resource === 'entry') { + + if (operation === 'get') { + + const credentials = this.getCredentials('contentfulApi'); + + const env = this.getNodeParameter('environmentId', 0) as string; + + const id = this.getNodeParameter('entryId', 0) as string; + + responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries/${id}`, {}, qs); + + } else if (operation === 'getAll') { + const credentials = this.getCredentials('contentfulApi'); + + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const env = this.getNodeParameter('environmentId', i) as string; + + Object.assign(qs, additionalFields); + + if (qs.equal) { + const [atribute, value] = (qs.equal as string).split('='); + qs[atribute] = value; + delete qs.equal; + } + + if (qs.notEqual) { + const [atribute, value] = (qs.notEqual as string).split('='); + qs[atribute] = value; + delete qs.notEqual; + } + + if (qs.include) { + const [atribute, value] = (qs.include as string).split('='); + qs[atribute] = value; + delete qs.include; + } + + if (qs.exclude) { + const [atribute, value] = (qs.exclude as string).split('='); + qs[atribute] = value; + delete qs.exclude; + } + + if (returnAll) { + responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries`, {}, qs); + } else { + const limit = this.getNodeParameter('limit', 0) as number; + qs.limit = limit; + responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries`, {}, qs); + responseData = responseData.items; + } + } + } + if (resource === 'asset') { + if (operation === 'get') { + + const credentials = this.getCredentials('contentfulApi'); + + const env = this.getNodeParameter('environmentId', 0) as string; + + const id = this.getNodeParameter('assetId', 0) as string; + + responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets/${id}`, {}, qs); + + } else if (operation === 'getAll') { + + const credentials = this.getCredentials('contentfulApi'); + + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const env = this.getNodeParameter('environmentId', i) as string; + + Object.assign(qs, additionalFields); + + if (qs.equal) { + const [atribute, value] = (qs.equal as string).split('='); + qs[atribute] = value; + delete qs.equal; + } + + if (qs.notEqual) { + const [atribute, value] = (qs.notEqual as string).split('='); + qs[atribute] = value; + delete qs.notEqual; + } + + if (qs.include) { + const [atribute, value] = (qs.include as string).split('='); + qs[atribute] = value; + delete qs.include; + } + + if (qs.exclude) { + const [atribute, value] = (qs.exclude as string).split('='); + qs[atribute] = value; + delete qs.exclude; + } + + if (returnAll) { + responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets`, {}, qs); + } else { + const limit = this.getNodeParameter('limit', 0) as number; + qs.limit = limit; + responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets`, {}, qs); + responseData = responseData.items; + } + } + } + if (resource === 'locale') { + + if (operation === 'getAll') { + + const credentials = this.getCredentials('contentfulApi'); + + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + const env = this.getNodeParameter('environmentId', i) as string; + + if (returnAll) { + responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/locales`, {}, qs); + } else { + const limit = this.getNodeParameter('limit', 0) as number; + qs.limit = limit; + responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/locales`, {}, qs); + responseData = responseData.items; + } + } + } + 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/Contentful/EntryDescription.ts b/packages/nodes-base/nodes/Contentful/EntryDescription.ts new file mode 100644 index 0000000000..c22e3ba0d6 --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/EntryDescription.ts @@ -0,0 +1,207 @@ +import { + INodeProperties, + INodePropertyOptions, +} from 'n8n-workflow'; +import { type } from 'os'; +import { notEqual } from 'assert'; +import { exists } from 'fs'; + +export const resource = { + name: 'Entry', + value: 'entry', +} as INodePropertyOptions; + +export const operations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + resource.value, + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + ], + default: 'get', + description: 'The operation to perform.' + } +] as INodeProperties[]; + +export const fields = [ + { + displayName: 'Environment ID', + name: 'environmentId', + type: 'string', + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'get', + 'getAll', + ], + }, + }, + default: 'master', + description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".' + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + resource.value, + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + resource.value, + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Content Type ID', + name: 'content_type', + type: 'string', + default: '', + description: 'To search for entries with a specific content type', + }, + { + displayName: 'Equal', + name: 'equal', + type: 'string', + default: '', + placeholder: 'fields.title=n8n', + description: 'Search for all data that matches the condition: {attribute}={value}. Attribute can use dot notation.', + }, + { + displayName: 'Exclude', + name: 'exclude', + type: 'string', + default: '', + placeholder: 'fields.tags[nin]=accessories,flowers', + description: 'Search for all data that matches the condition: {attribute}[nin]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Exist', + name: 'exist', + type: 'string', + default: '', + placeholder: 'fields.tags[exists]=true', + description: 'Search for all data that matches the condition: {attribute}[exists]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Fields', + name: 'select', + type: 'string', + placeholder: 'fields.title', + default: '', + description: 'The select operator allows you to choose what fields to return from an entity. You can choose multiple values by combining comma separated operators.', + }, + { + displayName: 'Include', + name: 'include', + type: 'string', + default: '', + placeholder: 'fields.tags[in]=accessories,flowers', + description: 'Search for all data that matches the condition: {attribute}[in]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Not Equal', + name: 'notEqual', + type: 'string', + default: '', + placeholder: 'fields.title[ne]=n8n', + description: 'Search for all data that matches the condition: {attribute}[ne]={value}. Attribute can use dot notation.', + }, + { + displayName: 'Order', + name: 'order', + type: 'string', + default: '', + placeholder: 'sys.createdAt', + description: 'You can order items in the response by specifying the order search parameter. You can use sys properties (such as sys.createdAt) or field values (such as fields.myCustomDateField) for ordering.', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + description: ' Full-text search is case insensitive and might return more results than expected. A query will only take values with more than 1 character.', + }, + ], + }, + { + displayName: 'Entry ID', + name: 'entryId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'get', + ], + }, + }, + } +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Contentful/GenericFunctions.ts b/packages/nodes-base/nodes/Contentful/GenericFunctions.ts new file mode 100644 index 0000000000..218a85cc71 --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/GenericFunctions.ts @@ -0,0 +1,75 @@ +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + OptionsWithUri, +} from 'request'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function contentfulApiRequest(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('contentfulApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const source = this.getNodeParameter('source', 0) as string; + const isPreview = source === 'previewApi'; + + const options: OptionsWithUri = { + method, + qs, + body, + uri: uri ||`https://${isPreview ? 'preview' : 'cdn'}.contentful.com${resource}`, + json: true + }; + + if (isPreview) { + qs.access_token = credentials.ContentPreviewaccessToken as string; + } else { + qs.access_token = credentials.ContentDeliveryaccessToken as string; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + + let errorMessage = error; + + if (error.response && error.response.body && error.response.body.details) { + const details = error.response.body.details; + errorMessage = details.errors.map((e: IDataObject) => e.details).join('|'); + } else if (error.response && error.response.body && error.response.body.message) { + errorMessage = error.response.body.message; + } + + throw new Error(`Contentful error response [${error.statusCode}]: ${errorMessage}`); + } + +} + +export async function contenfulApiRequestAllItems(this: ILoadOptionsFunctions | IExecuteFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.limit = 100; + query.skip = 0; + + do { + responseData = await contentfulApiRequest.call(this, method, resource, body, query); + query.skip = (query.skip + 1) * query.limit; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + returnData.length < responseData.total + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Contentful/LocaleDescription.ts b/packages/nodes-base/nodes/Contentful/LocaleDescription.ts new file mode 100644 index 0000000000..45f2872ad2 --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/LocaleDescription.ts @@ -0,0 +1,94 @@ +import { + INodeProperties, + INodePropertyOptions +} from 'n8n-workflow'; + +export const resource = { + name: 'Locale', + value: 'locale', +} as INodePropertyOptions; + +export const operations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + resource.value, + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + }, + ], + default: 'getAll', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const fields = [ + { + displayName: 'Environment ID', + name: 'environmentId', + type: 'string', + displayOptions: { + show: { + resource: [ + resource.value, + ], + operation: [ + 'get', + 'getAll', + ], + }, + }, + default: 'master', + description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".' + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + resource.value, + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + resource.value, + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Contentful/SearchParameterDescription.ts b/packages/nodes-base/nodes/Contentful/SearchParameterDescription.ts new file mode 100644 index 0000000000..7557cbb755 --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/SearchParameterDescription.ts @@ -0,0 +1,37 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const fields = [ + { + displayName: 'Search Parameters', + name: 'search_parameters', + description: 'You can use a variety of query parameters to search and filter items.', + placeholder: 'Add parameter', + type: 'fixedCollection', + typeOptions: { + multipleValues: true + }, + default: {}, + options: [ + { + displayName: 'Parameters', + name: 'parameters', + values: [ + { + displayName: 'Parameter Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the search parameter to set.' + }, + { + displayName: 'Parameter Value', + name: 'value', + type: 'string', + default: '', + description: 'Value of the search parameter to set.' + } + ] + } + ] + } +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Contentful/SpaceDescription.ts b/packages/nodes-base/nodes/Contentful/SpaceDescription.ts new file mode 100644 index 0000000000..035f3c6e57 --- /dev/null +++ b/packages/nodes-base/nodes/Contentful/SpaceDescription.ts @@ -0,0 +1,33 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const resource = { + name: 'Space', + value: 'space', +}; + +export const operations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + resource.value + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const fields = [] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Contentful/contentful.png b/packages/nodes-base/nodes/Contentful/contentful.png new file mode 100644 index 0000000000..4448da65ae Binary files /dev/null and b/packages/nodes-base/nodes/Contentful/contentful.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 691cca25e2..d7186000ca 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -47,6 +47,7 @@ "dist/credentials/ClockifyApi.credentials.js", "dist/credentials/CockpitApi.credentials.js", "dist/credentials/CodaApi.credentials.js", + "dist/credentials/ContentfulApi.credentials.js", "dist/credentials/CopperApi.credentials.js", "dist/credentials/CalendlyApi.credentials.js", "dist/credentials/CustomerIoApi.credentials.js", @@ -205,6 +206,7 @@ "dist/nodes/Clockify/ClockifyTrigger.node.js", "dist/nodes/Cockpit/Cockpit.node.js", "dist/nodes/Coda/Coda.node.js", + "dist/nodes/Contentful/Contentful.node.js", "dist/nodes/Copper/CopperTrigger.node.js", "dist/nodes/CrateDb/CrateDb.node.js", "dist/nodes/Cron.node.js",