Improvements to Contenful-Node

This commit is contained in:
ricardo 2020-08-04 15:07:54 -04:00
parent 6573642fc1
commit bc0b349faa
11 changed files with 810 additions and 463 deletions

View file

@ -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'
},
];
}

View file

@ -1,31 +0,0 @@
import { ICredentialType, NodePropertyTypes } from 'n8n-workflow';
export class ContentfulDeliveryApi implements ICredentialType {
name = 'contentfulDeliveryApi';
displayName = 'Delivery API';
properties = [
{
displayName: 'Space Id',
name: 'space_id',
type: 'string' as NodePropertyTypes,
default: '',
required: true,
description: 'The id for the Contentful space.'
},
{
displayName: 'Content Delivery API - access token',
name: 'access_token',
type: 'string' as NodePropertyTypes,
default: '',
required: true,
description: 'Access token that has access to the space'
},
{
displayName: 'Content Preview API - access token',
name: 'access_token_preview',
type: 'string' as NodePropertyTypes,
default: '',
description: 'Access token that has access to the space'
}
];
}

View file

@ -1,49 +1,197 @@
import { INodeProperties, INodePropertyOptions } from "n8n-workflow";
import {
INodeProperties,
INodePropertyOptions,
} from 'n8n-workflow';
export const resource = {
name: "Asset",
value: "asset",
name: 'Asset',
value: 'asset',
} as INodePropertyOptions;
export const operations = [
{
displayName: "Operation",
name: "operation",
type: "options",
displayOptions: {
show: {
resource: [resource.value],
},
},
options: [
{
name: "Get Assets",
value: "get_assets",
},
{
name: "Get Single Asset",
value: "get_asset",
},
],
default: "get_assets",
description: "The operation to perform.",
},
{
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: "Asset Id",
name: "asset_id",
type: "string",
default: "",
placeholder: "",
description: "",
required: true,
displayOptions: {
show: {
resource: [resource.value],
operation: ["get_asset"],
},
},
},
{
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[];

View file

@ -1,49 +1,69 @@
import { INodeProperties, INodePropertyOptions } from "n8n-workflow";
import {
INodeProperties,
INodePropertyOptions,
} from 'n8n-workflow';
export const resource = {
name: "Content Types",
value: "content_type",
name: 'Content Type',
value: 'contentType',
} as INodePropertyOptions;
export const operations = [
{
displayName: "Operation",
name: "operation",
type: "options",
displayOptions: {
show: {
resource: [resource.value],
},
},
options: [
{
name: "Get Content types",
value: "get_content_types",
},
{
name: "Get Single Content Type",
value: "get_content_type",
},
],
default: "get_content_types",
description: "The operation to perform.",
},
{
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: "Content Type Id",
name: "content_type_id",
type: "string",
default: "",
placeholder: "",
description: "",
required: true,
displayOptions: {
show: {
resource: [resource.value],
operation: ["get_content_type"],
},
},
},
{
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[];

View file

@ -1,24 +1,34 @@
import { IExecuteFunctions } from 'n8n-core';
import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, NodePropertyTypes } from 'n8n-workflow';
import {
IExecuteFunctions,
} from 'n8n-core';
import { contentfulApiRequest } from './GenericFunctions';
import resolveResponse from './resolveResponse';
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';
import * as SearchParameterDescription from './SearchParameterDescription';
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: "Access data through Contentful's Content Delivery API",
description: 'Consume Contenful API',
defaults: {
name: 'Contentful',
color: '#2E75D4'
@ -27,49 +37,39 @@ export class Contentful implements INodeType {
outputs: ['main'],
credentials: [
{
name: 'contentfulDeliveryApi',
name: 'contentfulApi',
required: true
}
},
],
properties: [
// Common fields:
{
displayName: 'Source',
name: 'source',
type: 'options' as NodePropertyTypes,
type: 'options',
default: 'Delivery API',
description: 'Pick where your data comes from, delivery or preview API',
options: [
{
name: 'Delivery API',
value: 'delivery_api'
value: 'deliveryApi'
},
{
name: 'Preview API',
value: 'preview_api'
}
]
value: 'previewApi'
},
],
},
{
displayName: 'Environment Id',
name: 'environment_id',
type: 'string' as NodePropertyTypes,
default: '',
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".'
},
// Resources:
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
SpaceDescription.resource,
AssetDescription.resource,
ContentTypeDescription.resource,
EntryDescription.resource,
AssetDescription.resource,
LocaleDescription.resource
LocaleDescription.resource,
SpaceDescription.resource,
],
default: '',
description: 'The resource to operate on.'
@ -88,16 +88,13 @@ export class Contentful implements INodeType {
...EntryDescription.fields,
...AssetDescription.fields,
...LocaleDescription.fields,
// Options:
...SearchParameterDescription.fields
]
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const environmentId = this.getNodeParameter('environment_id', 0) as string;
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[] = [];
@ -105,54 +102,164 @@ export class Contentful implements INodeType {
for (let i = 0; i < items.length; i++) {
if (resource === 'space') {
if (operation === 'get_space') {
const res = await contentfulApiRequest(this);
returnData.push(res);
if (operation === 'get') {
const credentials = this.getCredentials('contentfulApi');
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}`);
}
} else if (resource === 'content_type') {
if (operation === 'get_content_types') {
const res = await contentfulApiRequest(this, '/content_types', environmentId);
const resolvedData = resolveResponse(res, {});
returnData.push(...resolvedData);
} else if (operation === 'get_content_type') {
const id = this.getNodeParameter('content_type_id', 0) as string;
const res = await contentfulApiRequest(this, `/content_types/${id}`, environmentId);
returnData.push(...res.items);
}
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}`);
}
} else if (resource === 'entry') {
if (operation === 'get_entries') {
const shouldResolve = this.getNodeParameter('resolve', 0) as boolean;
if (shouldResolve) qs.include = this.getNodeParameter('include', 0) as number;
const searchParameters = this.getNodeParameter('search_parameters', 0) as IDataObject;
if (searchParameters.parameters && Array.isArray(searchParameters.parameters)) {
searchParameters.parameters.forEach(parameter => {
const { name, value } = parameter as { name: string; value: string };
qs[name] = value;
});
}
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;
}
const res = await contentfulApiRequest(this, '/entries', environmentId, qs);
const resolvedData = shouldResolve ? resolveResponse(res, {}) : res.items;
returnData.push(...resolvedData);
} else if (operation === 'get_entry') {
const id = this.getNodeParameter('entry_id', 0) as string;
const res = await contentfulApiRequest(this, `/entries/${id}`, environmentId);
returnData.push(res);
}
} else if (resource === 'asset') {
if (operation === 'get_assets') {
const res = await contentfulApiRequest(this, '/assets', environmentId);
returnData.push(...res.items);
} else if (operation === 'get_asset') {
const id = this.getNodeParameter('asset_id', 0) as string;
const res = await contentfulApiRequest(this, `/assets/${id}`, environmentId);
returnData.push(res);
}
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;
}
}
} else if (resource === 'locale') {
if (operation === 'get_locales') {
const res = await contentfulApiRequest(this, '/locales', environmentId);
returnData.push(res);
}
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)];
}

View file

@ -1,8 +1,14 @@
import { INodeProperties, INodePropertyOptions } from 'n8n-workflow';
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'
value: 'entry',
} as INodePropertyOptions;
export const operations = [
@ -12,72 +18,190 @@ export const operations = [
type: 'options',
displayOptions: {
show: {
resource: [resource.value]
}
resource: [
resource.value,
],
},
},
options: [
{
name: 'Get Entries',
value: 'get_entries'
name: 'Get',
value: 'get',
},
{
name: 'Get Single Entry',
value: 'get_entry'
}
name: 'Get All',
value: 'getAll',
},
],
default: 'get_entries',
default: 'get',
description: 'The operation to perform.'
}
] as INodeProperties[];
export const fields = [
{
displayName: 'Resolve',
name: 'resolve',
type: 'boolean',
default: false,
description: 'Linked entries can be automatically resolved in the results if you click activate this feature.',
displayName: 'Environment ID',
name: 'environmentId',
type: 'string',
displayOptions: {
show: {
resource: [resource.value],
operation: ['get_entries']
}
}
},
{
displayName: 'Include',
name: 'include',
type: 'number',
default: 1,
placeholder: '',
description:
"When you have related content (e.g. entries with links to image assets) it's possible to include them in the results. Using the include parameter, you can specify the number of levels of entries to include in the results. A lower number might improve performance.",
typeOptions: {
minValue: 0,
maxValue: 10
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: {
resource: [resource.value],
operation: ['get_entries'],
resolve: [true]
}
}
operation: [
'getAll',
],
resource: [
resource.value,
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit.',
},
{
displayName: 'Entry Id',
name: 'entry_id',
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: '',
placeholder: '',
description: '',
required: true,
displayOptions: {
show: {
resource: [resource.value],
operation: ['get_entry']
}
}
resource: [
resource.value,
],
operation: [
'get',
],
},
},
}
] as INodeProperties[];

View file

@ -1,45 +1,75 @@
import { IExecuteFunctions } from 'n8n-core';
import { OptionsWithUrl } from 'request';
import {
IExecuteFunctions,
IExecuteSingleFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
/**
* @param {IExecuteFunctions} that Reference to the system's execute functions
* @param {string} endpoint? Endpoint of api call
* @param {string} environmentId? Id of contentful environment (eg. master, staging, etc.)
* @param {Record<string|number>} qs? Query string, can be used for search parameters
*/
export const contentfulApiRequest = async (
that: IExecuteFunctions,
endpoint?: string,
environmentId?: string,
qs?: Record<string, string | number | undefined>
) => {
const credentials = that.getCredentials('contentfulDeliveryApi');
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<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('contentfulApi');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const source = that.getNodeParameter('source', 0) as string;
const isPreview = source === 'preview_api';
let accessToken = credentials.access_token as string;
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) {
accessToken = credentials.access_token_preview as string;
console.log('accessToken', accessToken);
if (!accessToken) {
throw new Error('No access token for preview API set in credentials!');
}
qs.access_token = credentials.ContentPreviewaccessToken as string;
} else {
qs.access_token = credentials.ContentDeliveryaccessToken as string;
}
let url = `https://${isPreview ? 'preview' : 'cdn'}.contentful.com/spaces/${credentials.space_id}`;
if (environmentId) url = `${url}/environments/${environmentId}`;
if (endpoint) url = `${url}${endpoint}`;
qs = qs || {};
qs.access_token = accessToken;
try {
return await this.helpers.request!(options);
} catch (error) {
const res = await that.helpers.request!({
url,
method: 'GET',
qs
} as OptionsWithUrl);
let errorMessage = error;
return JSON.parse(res);
};
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<any> { // 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;
}

View file

@ -1,29 +1,94 @@
import { INodeProperties, INodePropertyOptions } from "n8n-workflow";
import {
INodeProperties,
INodePropertyOptions
} from 'n8n-workflow';
export const resource = {
name: "Locale",
value: "locale",
name: 'Locale',
value: 'locale',
} as INodePropertyOptions;
export const operations = [
{
displayName: "Operation",
name: "operation",
type: "options",
displayOptions: {
show: {
resource: [resource.value],
},
},
options: [
{
name: "Get Locales",
value: "get_locales",
},
],
default: "get_locales",
description: "The operation to perform.",
},
{
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 = [] 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[];

View file

@ -1,29 +1,33 @@
import { INodeProperties } from "n8n-workflow";
import {
INodeProperties,
} from 'n8n-workflow';
export const resource = {
name: "Space",
value: "space",
name: 'Space',
value: 'space',
};
export const operations = [
{
displayName: "Operation",
name: "operation",
type: "options",
displayOptions: {
show: {
resource: [resource.value],
},
},
options: [
{
name: "Get Space",
value: "get_space",
},
],
default: "get_space",
description: "The operation to perform.",
},
{
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[];

View file

@ -1,156 +0,0 @@
// @ts-nocheck
// Code from https://github.com/contentful/contentful-resolve-response/blob/master/index.js
import { cloneDeep } from 'lodash';
const UNRESOLVED_LINK = {}; // unique object to avoid polyfill bloat using Symbol()
/**
* isLink Function
* Checks if the object has sys.type "Link"
* @param object
*/
const isLink = (object: { sys: { type: string } }) =>
object && object.sys && object.sys.type === 'Link';
/**
* findNormalizableLinkInArray
*
* @param array
* @param predicate
* @return {*}
*/
const findNormalizableLinkInArray = (array, predicate) => {
for (let i = 0, len = array.length; i < len; i++) {
if (predicate(array[i])) {
return array[i];
}
}
return UNRESOLVED_LINK;
};
/**
* getLink Function
*
* @param response
* @param link
* @return {undefined}
*/
const getLink = (allEntries, link) => {
const { linkType: type, id } = link.sys;
const predicate = ({ sys }) => sys.type === type && sys.id === id;
return findNormalizableLinkInArray(allEntries, predicate);
};
/**
* cleanUpLinks Function
* - Removes unresolvable links from Arrays and Objects
*
* @param {Object[]|Object} input
*/
const cleanUpLinks = input => {
if (Array.isArray(input)) {
return input.filter(val => val !== UNRESOLVED_LINK);
}
for (const key in input) {
if (input[key] === UNRESOLVED_LINK) {
delete input[key];
}
}
return input;
};
/**
* walkMutate Function
* @param input
* @param predicate
* @param mutator
* @return {*}
*/
const walkMutate = (input, predicate, mutator, removeUnresolved) => {
if (predicate(input)) {
return mutator(input);
}
if (input && typeof input === 'object') {
for (const key in input) {
if (input.hasOwnProperty(key)) {
input[key] = walkMutate(
input[key],
predicate,
mutator,
removeUnresolved
);
}
}
if (removeUnresolved) {
input = cleanUpLinks(input);
}
}
return input;
};
const normalizeLink = (allEntries, link, removeUnresolved) => {
const resolvedLink = getLink(allEntries, link);
if (resolvedLink === UNRESOLVED_LINK) {
return removeUnresolved ? resolvedLink : link;
}
return resolvedLink;
};
const makeEntryObject = (item, itemEntryPoints) => {
if (!Array.isArray(itemEntryPoints)) {
return item;
}
const entryPoints = Object.keys(item).filter(
ownKey => itemEntryPoints.indexOf(ownKey) !== -1
);
return entryPoints.reduce((entryObj, entryPoint) => {
entryObj[entryPoint] = item[entryPoint];
return entryObj;
}, {});
};
/**
* resolveResponse Function
* Resolves contentful response to normalized form.
* @param {Object} response Contentful response
* @param {Object} options
* @param {Boolean} options.removeUnresolved - Remove unresolved links default:false
* @param {Array<String>} options.itemEntryPoints - Resolve links only in those item properties
* @return {Object}
*/
const resolveResponse = (response, options) => {
options = options || {};
if (!response.items) {
return [];
}
const responseClone = cloneDeep(response);
const allIncludes = Object.keys(responseClone.includes || {}).reduce(
(all, type) => [...all, ...response.includes[type]],
[]
);
const allEntries = [...responseClone.items, ...allIncludes];
allEntries.forEach(item => {
const entryObject = makeEntryObject(item, options.itemEntryPoints);
Object.assign(
item,
walkMutate(
entryObject,
isLink,
link => normalizeLink(allEntries, link, options.removeUnresolved),
options.removeUnresolved
)
);
});
return responseClone.items;
};
export default resolveResponse;

View file

@ -43,7 +43,7 @@
"dist/credentials/ClockifyApi.credentials.js",
"dist/credentials/CockpitApi.credentials.js",
"dist/credentials/CodaApi.credentials.js",
"dist/credentials/ContentfulDeliveryApi.credentials.js",
"dist/credentials/ContentfulApi.credentials.js",
"dist/credentials/CopperApi.credentials.js",
"dist/credentials/CalendlyApi.credentials.js",
"dist/credentials/DisqusApi.credentials.js",