mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
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:
parent
5f3bed3d4e
commit
06c407def8
|
@ -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');
|
||||
|
|
104
packages/nodes-base/nodes/PostBin/BinDescription.ts
Normal file
104
packages/nodes-base/nodes/PostBin/BinDescription.ts
Normal 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',
|
||||
},
|
||||
];
|
113
packages/nodes-base/nodes/PostBin/GenericFunctions.ts
Normal file
113
packages/nodes-base/nodes/PostBin/GenericFunctions.ts
Normal 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;
|
||||
}
|
16
packages/nodes-base/nodes/PostBin/PostBin.node.json
Normal file
16
packages/nodes-base/nodes/PostBin/PostBin.node.json
Normal 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/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
59
packages/nodes-base/nodes/PostBin/PostBin.node.ts
Normal file
59
packages/nodes-base/nodes/PostBin/PostBin.node.ts
Normal 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,
|
||||
],
|
||||
};
|
||||
}
|
155
packages/nodes-base/nodes/PostBin/RequestDescription.ts
Normal file
155
packages/nodes-base/nodes/PostBin/RequestDescription.ts
Normal 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',
|
||||
},
|
||||
];
|
3
packages/nodes-base/nodes/PostBin/postbin.svg
Normal file
3
packages/nodes-base/nodes/PostBin/postbin.svg
Normal 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 |
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue