feat(PostBin Node): Add PostBin node (#3236)

* 🚧 Initial progress on PostBin node.

*  Implemented Bin and Request operations for PostBin node.

* 🚧 Reworked the node in the declarative way.

* 🚧 PosBin node refactoring after reworking it.

*  Implemented Bin id parsing in PostBin node. Done some final refactoring and documentation.

*  Improvements

*  Add comments

* 👌Updating the PostBin node based on the product review

* 💄Updating PostBin node Bin ID validation logic

*  Small improvements

*  Transform the bin requests and add additional properties

Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Milorad FIlipović 2022-05-27 18:04:56 +02:00 committed by GitHub
parent 5f3bed3d4e
commit 06c407def8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 455 additions and 1 deletions

View file

@ -708,7 +708,10 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest
};
}
if (n8nRequest.body) {
// if there is a body and it's empty (does not have properties),
// make sure not to send anything in it as some services fail when
// sending GET request with empty body.
if (n8nRequest.body && Object.keys(n8nRequest.body).length) {
axiosRequest.data = n8nRequest.body;
// Let's add some useful header standards here.
const existingContentTypeHeaderKey = searchForHeader(axiosRequest.headers, 'content-type');

View file

@ -0,0 +1,104 @@
import {
INodeProperties
} from 'n8n-workflow';
import {
buildBinAPIURL,
transformBinReponse,
} from './GenericFunctions';
// Operations for the `Bin` resource:
export const binOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'bin',
],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create bin',
routing: {
request: {
method: 'POST',
url: '/developers/postbin/api/bin',
},
output: {
postReceive: [
transformBinReponse,
],
},
},
},
{
name: 'Get',
value: 'get',
description: 'Get a bin',
routing: {
request: {
method: 'GET',
},
output: {
postReceive: [
transformBinReponse,
],
},
send: {
preSend: [
// Parse binId before sending to make sure it's in the right format
buildBinAPIURL,
],
},
},
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a bin',
routing: {
request: {
method: 'DELETE',
},
send: {
preSend: [
// Parse binId before sending to make sure it's in the right format
buildBinAPIURL,
],
},
},
},
],
default: 'create',
},
];
// Properties of the `Bin` resource
export const binFields: INodeProperties[] = [
{
name: 'binId',
displayName: 'Bin ID',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'bin',
],
operation: [
'get',
'delete',
],
},
},
description: 'Unique identifier for each bin',
},
];

View file

@ -0,0 +1,113 @@
import {
IExecuteSingleFunctions,
IHttpRequestOptions,
IN8nHttpFullResponse,
INodeExecutionData,
NodeApiError,
} from 'n8n-workflow';
// Regular expressions used to extract binId from parameter value
const BIN_ID_REGEX = /\b\d{13}-\d{13}\b/g;
/**
* Creates correctly-formatted PostBin API URL based on the entered binId.
* This function makes sure binId is in the expected format by parsing it
* from current node parameter value.
*
* @export
* @param {IExecuteSingleFunctions} this
* @param {IHttpRequestOptions} requestOptions
* @returns {Promise<IHttpRequestOptions>} requestOptions
*/
export async function buildBinAPIURL(this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
const binId = parseBinId(this);
// Assemble the PostBin API URL and put it back to requestOptions
requestOptions.url = `/developers/postbin/api/bin/${binId}`;
return requestOptions;
}
/**
* Creates correctly-formatted PostBin Bin test URL based on the entered binId.
* This function makes sure binId is in the expected format by parsing it
* from current node parameter value.
*
* @export
* @param {IExecuteSingleFunctions} this
* @param {IHttpRequestOptions} requestOptions
* @returns {Promise<IHttpRequestOptions>} requestOptions
*/
export async function buildBinTestURL(this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
const binId = parseBinId(this);
// Assemble the PostBin API URL and put it back to requestOptions
requestOptions.url = `/developers/postbin/${binId}`;
return requestOptions;
}
/**
* Creates correctly-formatted PostBin API URL based on the entered binId and reqId.
* This function makes sure binId is in the expected format by parsing it
* from current node parameter value.
*
* @export
* @param {IExecuteSingleFunctions} this
* @param {IHttpRequestOptions} requestOptions
* @returns {Promise<IHttpRequestOptions>} requestOptions
*/
export async function buildRequestURL(this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
const reqId = this.getNodeParameter('requestId', 'shift') as string;
const binId = parseBinId(this);
requestOptions.url = `/developers/postbin/api/bin/${binId}/req/${reqId}`;
return requestOptions;
}
/**
* Extracts the PostBin Bin Id from the specified string.
* This method should be able to extract bin Id from the
* PostBin URL or from the string in the following format:
* `Bin '<binId>'.`
*
* @param {IExecuteSingleFunctions} this
* @param {IHttpRequestOptions} requestOptions
* @returns {Promise<IHttpRequestOptions>} requestOptions
*/
function parseBinId(context: IExecuteSingleFunctions) {
const binId = context.getNodeParameter('binId') as string;
// Test if the Bin id is in the expected format
const idMatch = BIN_ID_REGEX.exec(binId);
// Return what is matched
if(idMatch) {
return idMatch[0];
}
// If it's not recognized, error out
throw new NodeApiError(context.getNode(), {}, {
message: 'Bin ID format is not valid',
description: 'Please check the provided Bin ID and try again.',
parseXml: false,
});
}
/**
* Converts the bin response data and adds additional properties
*
* @param {IExecuteSingleFunctions} this
* @param {INodeExecutionData} items[]
* @param {IN8nHttpFullResponse} response
* @returns {Promise<INodeExecutionData[]>}
*/
export async function transformBinReponse(this: IExecuteSingleFunctions, items: INodeExecutionData[], response: IN8nHttpFullResponse): Promise<INodeExecutionData[]> {
items.forEach(item => item.json = {
'binId': item.json.binId,
'nowTimestamp': item.json.now,
'nowIso': new Date(item.json.now as string).toISOString(),
'expiresTimestamp': item.json.expires,
'expiresIso': new Date(item.json.expires as string).toISOString(),
'requestUrl': 'https://www.toptal.com/developers/postbin/' + item.json.binId,
'viewUrl': 'https://www.toptal.com/developers/postbin/b/' + item.json.binId,
});
return items;
}

View file

@ -0,0 +1,16 @@
{
"node": "n8n-nodes-base.postbin",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Development",
"Data & Storage"
],
"resources": {
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.postbin/"
}
]
}
}

View file

@ -0,0 +1,59 @@
import {
INodeType,
INodeTypeDescription
} from 'n8n-workflow';
import {
binFields,
binOperations,
} from './BinDescription';
import {
requestFields,
requestOperations,
} from './RequestDescription';
export class PostBin implements INodeType {
description: INodeTypeDescription = {
displayName: 'PostBin',
name: 'postBin',
icon: 'file:postbin.svg',
group: ['transform'],
version: 1,
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Consume PostBin API',
defaults: {
name: 'PostBin',
color: '#4dc0b5',
},
inputs: ['main'],
outputs: ['main'],
credentials: [],
requestDefaults: {
baseURL: 'https://www.toptal.com',
},
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Bin',
value: 'bin',
},
{
name: 'Request',
value: 'request',
},
],
default: 'bin',
required: true,
},
...binOperations,
...binFields,
...requestOperations,
...requestFields,
],
};
}

View file

@ -0,0 +1,155 @@
import {
INodeProperties
} from 'n8n-workflow';
import {
buildBinTestURL,
buildRequestURL
} from './GenericFunctions';
// Operations for the `Request` resource
export const requestOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'request',
],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a request',
routing: {
request: {
method: 'GET',
url: '=/developers/postbin/api/bin/{{$parameter["binId"]}}/req/{{$parameter["requestId"]}}',
},
send: {
preSend: [
// Parse binId before sending to make sure it's in the right format
buildRequestURL,
],
},
},
},
{
name: 'Remove First',
value: 'removeFirst',
description: 'Remove the first request from bin',
routing: {
request: {
method: 'GET',
url: '=/developers/postbin/api/bin/{{$parameter["binId"]}}/req/shift',
},
send: {
preSend: [
// Parse binId before sending to make sure it's in the right format
buildRequestURL,
],
},
},
},
{
name: 'Send',
value: 'send',
description: 'Send a test request to the bin',
routing: {
request: {
method: 'POST',
},
send: {
preSend: [
// Parse binId before sending to make sure it's in the right format
buildBinTestURL,
],
},
output: {
postReceive: [
{
type: 'set',
properties: {
value: '={{ { "requestId": $response.body } }}',
},
},
],
},
},
},
],
default: 'get',
},
];
// Properties of the `Request` resource
export const requestFields: INodeProperties[] = [
{
name: 'binId',
displayName: 'Bin ID',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'request',
],
operation: [
'get',
'removeFirst',
'send',
],
},
},
description: 'Unique identifier for each bin',
},
{
name: 'binContent',
displayName: 'Bin Content',
type: 'string',
default: '',
typeOptions: {
rows: 5,
},
displayOptions: {
show: {
resource: [
'request',
],
operation: [
'send',
],
},
},
// Content is sent in the body of POST requests
routing: {
send: {
property: 'content',
type: 'body',
},
},
},
{
name: 'requestId',
displayName: 'Request ID',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'request',
],
operation: [
'get',
],
},
},
description: 'Unique identifier for each request',
},
];

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path fill="#4dc0b5" d="M352 256C352 278.2 350.8 299.6 348.7 320H163.3C161.2 299.6 159.1 278.2 159.1 256C159.1 233.8 161.2 212.4 163.3 192H348.7C350.8 212.4 352 233.8 352 256zM503.9 192C509.2 212.5 512 233.9 512 256C512 278.1 509.2 299.5 503.9 320H380.8C382.9 299.4 384 277.1 384 256C384 234 382.9 212.6 380.8 192H503.9zM493.4 160H376.7C366.7 96.14 346.9 42.62 321.4 8.442C399.8 29.09 463.4 85.94 493.4 160zM344.3 160H167.7C173.8 123.6 183.2 91.38 194.7 65.35C205.2 41.74 216.9 24.61 228.2 13.81C239.4 3.178 248.7 0 256 0C263.3 0 272.6 3.178 283.8 13.81C295.1 24.61 306.8 41.74 317.3 65.35C328.8 91.38 338.2 123.6 344.3 160H344.3zM18.61 160C48.59 85.94 112.2 29.09 190.6 8.442C165.1 42.62 145.3 96.14 135.3 160H18.61zM131.2 192C129.1 212.6 127.1 234 127.1 256C127.1 277.1 129.1 299.4 131.2 320H8.065C2.8 299.5 0 278.1 0 256C0 233.9 2.8 212.5 8.065 192H131.2zM194.7 446.6C183.2 420.6 173.8 388.4 167.7 352H344.3C338.2 388.4 328.8 420.6 317.3 446.6C306.8 470.3 295.1 487.4 283.8 498.2C272.6 508.8 263.3 512 255.1 512C248.7 512 239.4 508.8 228.2 498.2C216.9 487.4 205.2 470.3 194.7 446.6H194.7zM190.6 503.6C112.2 482.9 48.59 426.1 18.61 352H135.3C145.3 415.9 165.1 469.4 190.6 503.6V503.6zM321.4 503.6C346.9 469.4 366.7 415.9 376.7 352H493.4C463.4 426.1 399.8 482.9 321.4 503.6V503.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -570,6 +570,7 @@
"dist/nodes/Pipedrive/Pipedrive.node.js",
"dist/nodes/Pipedrive/PipedriveTrigger.node.js",
"dist/nodes/Plivo/Plivo.node.js",
"dist/nodes/PostBin/PostBin.node.js",
"dist/nodes/Postgres/Postgres.node.js",
"dist/nodes/PostHog/PostHog.node.js",
"dist/nodes/Postmark/PostmarkTrigger.node.js",