Add APITemplate.io node (#1497)

* Add support for APITemplate.io

*  Refactor node

* 👕 Fix lint issues

*  Improvements

*  Fix ApiTemplateIo Node

*  Add placeholder

Co-authored-by: Jacky Tan Boon Kiat <bktan@apitemplate.io>
Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Iván Ovejero 2021-03-01 09:12:37 -03:00 committed by GitHub
parent 14329b2eb4
commit 307860b21a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 775 additions and 0 deletions

View file

@ -0,0 +1,20 @@
{
"node": "n8n-nodes-base.apiTemplateIo",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Marketing & Content"
],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/apiTemplateIo"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.apiTemplateIo/"
}
]
}
}

View file

@ -0,0 +1,560 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import {
apiTemplateIoApiRequest,
downloadImage,
loadResource,
validateJSON,
} from './GenericFunctions';
export class ApiTemplateIo implements INodeType {
description: INodeTypeDescription = {
displayName: 'APITemplate.io',
name: 'ApiTemplateIo',
icon: 'file:apiTemplateIo.svg',
group: ['transform'],
version: 1,
description: 'Consume the APITemplate.io API',
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
defaults: {
name: 'APITemplate.io',
color: '#c0c0c0',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'apiTemplateIoApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Account',
value: 'account',
},
{
name: 'Image',
value: 'image',
},
{
name: 'PDF',
value: 'pdf',
},
],
default: 'image',
description: 'Resource to consume',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
default: 'create',
required: true,
description: 'Operation to perform',
options: [
{
name: 'Create',
value: 'create',
},
],
displayOptions: {
show: {
resource: [
'image',
'pdf',
],
},
},
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
default: 'get',
required: true,
description: 'Operation to perform',
options: [
{
name: 'Get',
value: 'get',
},
],
displayOptions: {
show: {
resource: [
'account',
],
},
},
},
{
displayName: 'Template ID',
name: 'imageTemplateId',
type: 'options',
required: true,
default: '',
description: 'ID of the image template to use.',
typeOptions: {
loadOptionsMethod: 'getImageTemplates',
},
displayOptions: {
show: {
resource: [
'image',
],
operation: [
'create',
],
},
},
},
{
displayName: 'Template ID',
name: 'pdfTemplateId',
type: 'options',
required: true,
default: '',
description: 'ID of the PDF template to use.',
typeOptions: {
loadOptionsMethod: 'getPdfTemplates',
},
displayOptions: {
show: {
resource: [
'pdf',
],
operation: [
'create',
],
},
},
},
{
displayName: 'JSON Parameters',
name: 'jsonParameters',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'pdf',
'image',
],
operation: [
'create',
],
},
},
},
{
displayName: 'Download',
name: 'download',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'pdf',
'image',
],
operation: [
'create',
],
},
},
description: 'Name of the binary property to which to<br />write the data of the read file.',
},
{
displayName: 'Binary Property',
name: 'binaryProperty',
type: 'string',
required: true,
default: 'data',
description: 'Name of the binary property to which to write to.',
displayOptions: {
show: {
resource: [
'pdf',
'image',
],
operation: [
'create',
],
download: [
true,
],
},
},
},
{
displayName: 'Overrides (JSON)',
name: 'overridesJson',
type: 'json',
default: '',
displayOptions: {
show: {
resource: [
'image',
],
operation: [
'create',
],
jsonParameters: [
true,
],
},
},
placeholder: `[ {"name": "text_1", "text": "hello world", "textBackgroundColor": "rgba(246, 243, 243, 0)" } ]`,
},
{
displayName: 'Properties (JSON)',
name: 'propertiesJson',
type: 'json',
default: '',
displayOptions: {
show: {
resource: [
'pdf',
],
operation: [
'create',
],
jsonParameters: [
true,
],
},
},
placeholder: `{ "name": "text_1" }`,
},
{
displayName: 'Overrides',
name: 'overridesUi',
placeholder: 'Add Override',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
resource: [
'image',
],
operation: [
'create',
],
jsonParameters: [
false,
],
},
},
default: {},
options: [
{
name: 'overrideValues',
displayName: 'Override',
values: [
{
displayName: 'Properties',
name: 'propertiesUi',
placeholder: 'Add Property',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'propertyValues',
displayName: 'Property',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
description: 'Name of the property',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value to the property.',
},
],
},
],
},
],
},
],
},
{
displayName: 'Properties',
name: 'propertiesUi',
placeholder: 'Add Property',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
displayOptions: {
show: {
resource: [
'pdf',
],
operation: [
'create',
],
jsonParameters: [
false,
],
},
},
options: [
{
name: 'propertyValues',
displayName: 'Property',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
description: 'Name of the property',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value to the property.',
},
],
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'pdf',
'image',
],
'download': [
true,
],
},
},
default: {},
options: [
{
displayName: 'File Name',
name: 'fileName',
type: 'string',
default: '',
description: 'The name of the downloaded image/pdf. It has to include the extension. For example: report.pdf',
},
],
},
],
};
methods = {
loadOptions: {
async getImageTemplates(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
return await loadResource.call(this, 'image');
},
async getPdfTemplates(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
return await loadResource.call(this, 'pdf');
},
},
};
async execute(this: IExecuteFunctions) {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length;
let responseData;
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
if (resource === 'account') {
// *********************************************************************
// account
// *********************************************************************
if (operation === 'get') {
// ----------------------------------
// account: get
// ----------------------------------
for (let i = 0; i < length; i++) {
responseData = await apiTemplateIoApiRequest.call(this, 'GET', '/account-information');
returnData.push(responseData);
}
}
} else if (resource === 'image') {
// *********************************************************************
// image
// *********************************************************************
if (operation === 'create') {
// ----------------------------------
// image: create
// ----------------------------------
const download = this.getNodeParameter('download', 0) as boolean;
// https://docs.apitemplate.io/reference/api-reference.html#create-an-image-jpeg-and-png
for (let i = 0; i < length; i++) {
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
const options = this.getNodeParameter('options', i) as IDataObject;
const qs = {
template_id: this.getNodeParameter('imageTemplateId', i),
};
const body = { overrides: [] } as IDataObject;
if (jsonParameters === false) {
const overrides = (this.getNodeParameter('overridesUi', i) as IDataObject || {}).overrideValues as IDataObject[] || [];
if (overrides.length !== 0) {
const data: IDataObject[] = [];
for (const override of overrides) {
const properties = (override.propertiesUi as IDataObject || {}).propertyValues as IDataObject[] || [];
data.push(properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {}));
}
body.overrides = data;
}
} else {
const overrideJson = this.getNodeParameter('overridesJson', i) as string;
if (overrideJson !== '') {
const data = validateJSON(overrideJson);
if (data === undefined) {
throw new Error('A valid JSON must be provided.');
}
body.overrides = data;
}
}
responseData = await apiTemplateIoApiRequest.call(this, 'POST', '/create', qs, body);
if (download === true) {
const binaryProperty = this.getNodeParameter('binaryProperty', i) as string;
const data = await downloadImage.call(this, responseData.download_url);
const fileName = responseData.download_url.split('/').pop();
const binaryData = await this.helpers.prepareBinaryData(data, options.fileName || fileName);
responseData = {
json: responseData,
binary: {
[binaryProperty]: binaryData,
},
};
}
returnData.push(responseData);
}
if (download === true) {
return this.prepareOutputData(returnData as unknown as INodeExecutionData[]);
}
}
} else if (resource === 'pdf') {
// *********************************************************************
// pdf
// *********************************************************************
if (operation === 'create') {
// ----------------------------------
// pdf: create
// ----------------------------------
// https://docs.apitemplate.io/reference/api-reference.html#create-a-pdf
const download = this.getNodeParameter('download', 0) as boolean;
for (let i = 0; i < length; i++) {
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
const options = this.getNodeParameter('options', i) as IDataObject;
const qs = {
template_id: this.getNodeParameter('pdfTemplateId', i),
};
let data;
if (jsonParameters === false) {
const properties = (this.getNodeParameter('propertiesUi', i) as IDataObject || {}).propertyValues as IDataObject[] || [];
if (properties.length === 0) {
throw new Error('The parameter properties cannot be empty');
}
data = properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {});
} else {
const propertiesJson = this.getNodeParameter('propertiesJson', i) as string;
data = validateJSON(propertiesJson);
if (data === undefined) {
throw new Error('A valid JSON must be provided.');
}
}
responseData = await apiTemplateIoApiRequest.call(this, 'POST', '/create', qs, data);
if (download === true) {
const binaryProperty = this.getNodeParameter('binaryProperty', i) as string;
const data = await downloadImage.call(this, responseData.download_url);
const fileName = responseData.download_url.split('/').pop();
const binaryData = await this.helpers.prepareBinaryData(data, options.fileName || fileName);
responseData = {
json: responseData,
binary: {
[binaryProperty]: binaryData,
},
};
}
returnData.push(responseData);
}
if (download === true) {
return this.prepareOutputData(returnData as unknown as INodeExecutionData[]);
}
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

View file

@ -0,0 +1,91 @@
import {
OptionsWithUri,
} from 'request';
import {
IExecuteFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
export async function apiTemplateIoApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: string,
endpoint: string,
qs = {},
body = {},
) {
const { apiKey } = this.getCredentials('apiTemplateIoApi') as { apiKey: string };
const options: OptionsWithUri = {
headers: {
'user-agent': 'n8n',
Accept: 'application/json',
'X-API-KEY': `${apiKey}`,
},
uri: `https://api.apitemplate.io/v1${endpoint}`,
method,
qs,
body,
followRedirect: true,
followAllRedirects: true,
json: true,
};
if (!Object.keys(body).length) {
delete options.body;
}
if (!Object.keys(qs).length) {
delete options.qs;
}
try {
const response = await this.helpers.request!(options);
if (response.status === 'error') {
throw new Error(response.message);
}
return response;
} catch (error) {
if (error?.response?.body?.message) {
throw new Error(`APITemplate.io error response [${error.statusCode}]: ${error.response.body.message}`);
}
throw error;
}
}
export async function loadResource(
this: ILoadOptionsFunctions,
resource: 'image' | 'pdf',
) {
const target = resource === 'image' ? ['JPEG', 'PNG'] : ['PDF'];
const templates = await apiTemplateIoApiRequest.call(this, 'GET', '/list-templates');
const filtered = templates.filter(({ format }: { format: 'PDF' | 'JPEG' | 'PNG' }) => target.includes(format));
return filtered.map(({ format, name, id }: { format: string, name: string, id: string }) => ({
name: `${name} (${format})`,
value: id,
}));
}
export function validateJSON(json: string | object | undefined): any { // tslint:disable-line:no-any
let result;
if (typeof json === 'object') {
return json;
}
try {
result = JSON.parse(json!);
} catch (exception) {
result = undefined;
}
return result;
}
export function downloadImage(this: IExecuteFunctions, url: string) {
return this.helpers.request({
uri: url,
method: 'GET',
json: false,
encoding: null,
});
}

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="80"
height="80"
viewBox="0 0 23.166659 21.166671"
version="1.1"
id="svg8"
inkscape:version="1.0.1 (1.0.1+r75)"
sodipodi:docname="favicon.svg"
inkscape:export-filename="/mnt/shared/bktan81@gmail.com/Companies/AlphaCloud/APITemplate.io/export/favicon-color.png"
inkscape:export-xdpi="614.40002"
inkscape:export-ydpi="614.40002">
<defs
id="defs2">
<rect
x="5.5194178"
y="6.7918406"
width="82.495544"
height="30.566183"
id="rect835" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.6962788"
inkscape:cx="84.04094"
inkscape:cy="31.651389"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1014"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
style="fill:#000000"
id="g944"
transform="matrix(0.26072249,-0.0450346,0.0450346,0.26072249,-4.4090493,0.80238047)">
<path
fill="#91bce5"
d="m 85.926965,13.698648 -63.285045,30.243807 15.697805,11.196666 1.472218,15.148608 13.242212,-6.250661 9.822444,5.345054 z"
id="path918" />
<path
fill="#1f212b"
d="m 39.778008,71.288154 c -0.158903,-0.0056 -0.31648,-0.04907 -0.45874,-0.130089 -0.283589,-0.159995 -0.470626,-0.448698 -0.501329,-0.772967 L 37.38898,55.688275 22.06215,44.756717 c -0.288015,-0.205176 -0.446151,-0.547904 -0.415808,-0.901061 0.03035,-0.353155 0.245283,-0.662843 0.564816,-0.815784 L 85.495203,12.796029 c 0.373477,-0.179075 0.817321,-0.109542 1.119534,0.176179 0.301247,0.284686 0.393931,0.72519 0.235443,1.108892 L 63.800813,69.764609 c -0.108224,0.262384 -0.32346,0.465996 -0.589929,0.560754 -0.268434,0.09369 -0.562801,0.0704 -0.811249,-0.06435 l -9.376677,-5.101349 -12.782391,6.034577 c -0.147425,0.0669 -0.305655,0.09939 -0.462559,0.09391 z M 24.604162,44.11304 38.920495,54.324893 c 0.235269,0.168314 0.386237,0.429742 0.414198,0.717894 l 1.33547,13.734974 11.957261,-5.644136 c 0.287923,-0.136035 0.624788,-0.126272 0.904658,0.02559 l 8.84643,4.812668 21.627569,-52.247035 z"
id="path920" />
<path
fill="#3a84c1"
d="M 40.73124,69.46431 46.129963,59.222484 85.897126,14.553126 39.059778,54.433821 Z"
id="path922" />
<path
fill="#1f212b"
d="m 40.71279,69.963971 c -0.07296,-0.0026 -0.146351,-0.02112 -0.215122,-0.05754 -0.244661,-0.128616 -0.338162,-0.431064 -0.208546,-0.675689 l 5.398723,-10.241827 c 0.01925,-0.03535 0.04242,-0.06856 0.06852,-0.09967 L 80.940068,19.368061 38.684781,54.856992 c -0.211297,0.176733 -0.526545,0.149715 -0.705278,-0.06165 -0.177698,-0.212332 -0.149715,-0.526547 0.06165,-0.705279 L 85.574314,14.169627 c 0.201987,-0.168053 0.497667,-0.153725 0.680201,0.03476 0.182533,0.188486 0.189157,0.485899 0.01525,0.680947 L 46.544181,59.510112 41.173361,69.69788 c -0.09216,0.17489 -0.27468,0.272579 -0.460568,0.266088 z"
id="path924" />
<path
fill="#1f212b"
d="m 28.066921,71.78593 c -4.37019,-0.64391 -8.339996,-2.867809 -10.598292,-6.766997 -1.432549,-2.473502 -1.528128,-6.298167 2.122716,-6.172679 3.274075,0.112327 4.26409,2.596776 4.740567,5.406801 1.694711,8.06304 -1.290339,11.045322 -9.002137,14.829352 -0.594821,0.242389 -0.134492,1.113471 0.457295,0.871978 5.181377,-2.944492 7.754842,-4.282847 9.399081,-7.76557 1.370916,-4.981188 0.489139,-11.773963 -2.947089,-13.689368 -3.921594,-2.143168 -8.01567,0.59562 -6.601411,4.939623 1.751362,5.378399 7.074496,8.562112 12.394405,9.345251 0.635099,0.09422 0.665001,-0.905343 0.03486,-0.998391 z"
id="path926"
sodipodi:nodetypes="cccccccccccc" />
<path
fill="#1f212b"
d="m 32.211855,72.060754 c -0.960938,-0.01857 -1.924304,-0.08221 -2.882497,-0.15069 -0.64277,-0.04646 -0.676671,0.952965 -0.0349,0.999392 0.958193,0.06848 1.921524,0.133135 2.882497,0.150689 0.642958,0.01242 0.679856,-0.986875 0.0349,-0.999391 z m 4.771615,-1.109149 c -1.135251,0.302564 -2.280575,0.549744 -3.442445,0.726303 -0.635768,0.09687 -0.401574,1.069637 0.232195,0.972696 1.16187,-0.176559 2.307194,-0.423738 3.442444,-0.726302 0.620118,-0.164459 0.391991,-1.139015 -0.232194,-0.972697 z"
id="path928" />
<path
fill="#1f212b"
d="m 54.069217,64.636113 c -0.08695,-0.003 -0.175091,-0.02913 -0.253366,-0.07989 l -7.481853,-4.834056 c -0.231899,-0.150184 -0.298138,-0.459685 -0.148952,-0.69162 0.150185,-0.231899 0.460683,-0.298103 0.691619,-0.148953 l 7.481852,4.834058 c 0.231899,0.150184 0.298139,0.459686 0.148953,0.69162 -0.09943,0.153622 -0.268357,0.234778 -0.438253,0.228845 z"
id="path926-0"
sodipodi:nodetypes="sccccccs"
style="fill:#000000" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -35,6 +35,7 @@
"dist/credentials/Amqp.credentials.js",
"dist/credentials/AsanaApi.credentials.js",
"dist/credentials/AsanaOAuth2Api.credentials.js",
"dist/credentials/ApiTemplateIoApi.credentials.js",
"dist/credentials/AutomizyApi.credentials.js",
"dist/credentials/Aws.credentials.js",
"dist/credentials/AffinityApi.credentials.js",
@ -268,6 +269,7 @@
"dist/nodes/Amqp/AmqpTrigger.node.js",
"dist/nodes/Asana/Asana.node.js",
"dist/nodes/Asana/AsanaTrigger.node.js",
"dist/nodes/ApiTemplateIo/ApiTemplateIo.node.js",
"dist/nodes/Affinity/Affinity.node.js",
"dist/nodes/Affinity/AffinityTrigger.node.js",
"dist/nodes/Automizy/Automizy.node.js",