🔀 Merge branch 'feature/box-node' of https://github.com/RicardoE105/n8n into RicardoE105-feature/box-node

This commit is contained in:
Jan Oberhauser 2020-07-24 10:58:34 +02:00
commit f5013c22a7
14 changed files with 1316 additions and 29 deletions

View file

@ -15,6 +15,7 @@ import {
ITriggerResponse,
IWebhookFunctions as IWebhookFunctionsBase,
IWorkflowSettings as IWorkflowSettingsWorkflow,
IOAuth2Options,
} from 'n8n-workflow';
@ -36,7 +37,7 @@ export interface IExecuteFunctions extends IExecuteFunctionsBase {
helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: requestPromise.RequestPromiseAPI,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
};
@ -47,7 +48,7 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: requestPromise.RequestPromiseAPI,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
};
}
@ -57,7 +58,7 @@ export interface IPollFunctions extends IPollFunctionsBase {
helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: requestPromise.RequestPromiseAPI,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
};
@ -73,7 +74,7 @@ export interface ITriggerFunctions extends ITriggerFunctionsBase {
helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: requestPromise.RequestPromiseAPI,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
};
@ -98,7 +99,7 @@ export interface IUserSettings {
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
helpers: {
request?: requestPromise.RequestPromiseAPI,
requestOAuth2?: (this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions) => Promise<any>, // tslint:disable-line:no-any
requestOAuth2?: (this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options) => Promise<any>, // tslint:disable-line:no-any
requestOAuth1?(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
};
}
@ -107,7 +108,7 @@ export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
export interface IHookFunctions extends IHookFunctionsBase {
helpers: {
request: requestPromise.RequestPromiseAPI,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
};
}
@ -117,7 +118,7 @@ export interface IWebhookFunctions extends IWebhookFunctionsBase {
helpers: {
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
request: requestPromise.RequestPromiseAPI,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
};

View file

@ -34,6 +34,7 @@ import {
Workflow,
WorkflowDataProxy,
WorkflowExecuteMode,
IOAuth2Options,
} from 'n8n-workflow';
import * as clientOAuth1 from 'oauth-1.0a';
@ -124,9 +125,10 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m
* @param {(OptionsWithUri | requestPromise.RequestPromiseOptions)} requestOptions
* @param {INode} node
* @param {IWorkflowExecuteAdditionalData} additionalData
*
* @returns
*/
export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string, property?: string) {
export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, oAuth2Options?: IOAuth2Options) {
const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
if (credentials === undefined) {
@ -145,7 +147,7 @@ export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: strin
const oauthTokenData = credentials.oauthTokenData as clientOAuth2.Data;
const token = oAuthClient.createToken(get(oauthTokenData, property as string) || oauthTokenData.accessToken, oauthTokenData.refreshToken, tokenType || oauthTokenData.tokenType, oauthTokenData);
const token = oAuthClient.createToken(get(oauthTokenData, oAuth2Options?.property as string) || oauthTokenData.accessToken, oauthTokenData.refreshToken, oAuth2Options?.tokenType || oauthTokenData.tokenType, oauthTokenData);
// Signs the request by adding authorization headers or query parameters depending
// on the token-type used.
const newRequestOptions = token.sign(requestOptions as clientOAuth2.RequestObject);
@ -156,7 +158,18 @@ export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: strin
if (error.statusCode === 401) {
// TODO: Whole refresh process is not tested yet
// Token is probably not valid anymore. So try refresh it.
const newToken = await token.refresh();
const tokenRefreshOptions: IDataObject = {};
if (oAuth2Options?.includeCredentialsOnRefreshOnBody) {
const body: IDataObject = {
client_id: credentials.clientId as string,
client_secret: credentials.clientSecret as string,
};
tokenRefreshOptions.body = body;
}
const newToken = await token.refresh(tokenRefreshOptions);
credentials.oauthTokenData = newToken.data;
@ -535,8 +548,8 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
@ -598,8 +611,8 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
@ -694,8 +707,8 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
@ -792,8 +805,8 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
@ -848,8 +861,8 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
},
helpers: {
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
@ -915,8 +928,8 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
},
helpers: {
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
@ -1009,8 +1022,8 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);

View file

@ -0,0 +1,46 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class BoxOAuth2Api implements ICredentialType {
name = 'boxOAuth2Api';
extends = [
'oAuth2Api',
];
displayName = 'Box OAuth2 API';
properties = [
{
displayName: 'Authorization URL',
name: 'authUrl',
type: 'hidden' as NodePropertyTypes,
default: 'https://account.box.com/api/oauth2/authorize',
required: true,
},
{
displayName: 'Access Token URL',
name: 'accessTokenUrl',
type: 'hidden' as NodePropertyTypes,
default: 'https://api.box.com/oauth2/token',
required: true,
},
{
displayName: 'Scope',
name: 'scope',
type: 'hidden' as NodePropertyTypes,
default: '',
},
{
displayName: 'Auth URI Query Parameters',
name: 'authQueryParameters',
type: 'hidden' as NodePropertyTypes,
default: '',
},
{
displayName: 'Authentication',
name: 'authentication',
type: 'hidden' as NodePropertyTypes,
default: 'body',
},
];
}

View file

@ -0,0 +1,281 @@
import {
BINARY_ENCODING,
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeTypeDescription,
INodeExecutionData,
INodeType,
IBinaryKeyData,
} from 'n8n-workflow';
import {
boxApiRequest,
} from './GenericFunctions';
import {
fileFields,
fileOperations,
} from './FileDescription';
import {
folderFields,
folderOperations,
} from './FolderDescription';
export class Box implements INodeType {
description: INodeTypeDescription = {
displayName: 'Box',
name: 'box',
icon: 'file:box.png',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Box API',
defaults: {
name: 'Box',
color: '#00aeef',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'boxOAuth2Api',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'File',
value: 'file',
},
{
name: 'Folder',
value: 'folder',
},
],
default: 'file',
description: 'The resource to operate on.',
},
...fileOperations,
...fileFields,
...folderOperations,
...folderFields,
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length as unknown as number;
const qs: IDataObject = {};
let responseData;
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < length; i++) {
if (resource === 'file') {
//https://developer.box.com/reference/post-files-id-copy
if (operation === 'copy') {
const fileId = this.getNodeParameter('fileId', i) as string;
const parentId = this.getNodeParameter('parentId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const body: IDataObject = {};
if (additionalFields.name) {
body.name = additionalFields.name as string;
}
if (parentId) {
body.parent = { id: parentId };
} else {
body.parent = { id: 0 };
}
if (additionalFields.fields) {
qs.fields = additionalFields.fields as string;
}
if (additionalFields.version) {
body.version = additionalFields.version as string;
}
responseData = await boxApiRequest.call(this, 'POST', `/files/${fileId}/copy`, body, qs);
returnData.push(responseData as IDataObject);
}
//https://developer.box.com/reference/delete-files-id
if (operation === 'delete') {
const fileId = this.getNodeParameter('fileId', i) as string;
responseData = await boxApiRequest.call(this, 'DELETE', `/files/${fileId}`);
responseData = { success: true };
returnData.push(responseData as IDataObject);
}
//https://developer.box.com/reference/get-files-id-content
if (operation === 'download') {
const fileId = this.getNodeParameter('fileId', i) as string;
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
responseData = await boxApiRequest.call(this, 'GET', `/files/${fileId}`);
const fileName = responseData.name;
let mimeType: string | undefined;
responseData = await boxApiRequest.call(this, 'GET', `/files/${fileId}/content`, {}, {}, undefined, { resolveWithFullResponse: true });
const newItem: INodeExecutionData = {
json: items[i].json,
binary: {},
};
if (mimeType === undefined && responseData.headers['content-type']) {
mimeType = responseData.headers['content-type'];
}
if (items[i].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, items[i].binary);
}
items[i] = newItem;
const data = Buffer.from(responseData.body);
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType);
}
//https://developer.box.com/reference/get-files-id
if (operation === 'get') {
const fileId = this.getNodeParameter('fileId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.fields) {
qs.fields = additionalFields.fields as string;
}
responseData = await boxApiRequest.call(this, 'GET', `/files/${fileId}`, {}, qs);
returnData.push(responseData as IDataObject);
}
//https://developer.box.com/reference/post-files-content
if (operation === 'upload') {
const parentId = this.getNodeParameter('parentId', i) as string;
const isBinaryData = this.getNodeParameter('binaryData', i) as boolean;
const fileName = this.getNodeParameter('fileName', i) as string;
const attributes: IDataObject = {};
if (parentId !== '') {
attributes['parent'] = { id: parentId };
} else {
// if not parent defined save it on the root directory
attributes['parent'] = { id: 0 };
}
if (isBinaryData) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
if (items[i].binary === undefined) {
throw new Error('No binary data exists on item!');
}
//@ts-ignore
if (items[i].binary[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
}
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
const body: IDataObject = {};
attributes['name'] = fileName || binaryData.fileName;
body['attributes'] = JSON.stringify(attributes);
body['file'] = {
value: Buffer.from(binaryData.data, BINARY_ENCODING),
options: {
filename: binaryData.fileName,
contentType: binaryData.mimeType,
},
};
responseData = await boxApiRequest.call(this, 'POST', '', {}, {}, 'https://upload.box.com/api/2.0/files/content', { formData: body });
returnData.push.apply(returnData, responseData.entries as IDataObject[]);
} else {
const content = this.getNodeParameter('fileContent', i) as string;
if (fileName === '') {
throw new Error('File name must be set!');
}
attributes['name'] = fileName;
const body: IDataObject = {};
body['attributes'] = JSON.stringify(attributes);
body['file'] = {
value: Buffer.from(content),
options: {
filename: fileName,
contentType: 'text/plain',
},
};
responseData = await boxApiRequest.call(this, 'POST', '', {} , {}, 'https://upload.box.com/api/2.0/files/content', { formData: body });
returnData.push.apply(returnData, responseData.entries as IDataObject[]);
}
}
}
if (resource === 'folder') {
//https://developer.box.com/reference/post-folders
if (operation === 'create') {
const name = this.getNodeParameter('name', i) as string;
const parentId = this.getNodeParameter('parentId', i) as string;
const options = this.getNodeParameter('options', i) as IDataObject;
const body: IDataObject = {
name,
};
if (parentId) {
body.parent = { id: parentId };
} else {
body.parent = { id: 0 };
}
if (options.access) {
body.folder_upload_email = {
access: options.access as string,
};
}
if (options.fields) {
qs.fields = options.fields as string;
}
responseData = await boxApiRequest.call(this, 'POST', '/folders', body, qs);
returnData.push(responseData);
}
//https://developer.box.com/reference/delete-folders-id
if (operation === 'delete') {
const folderId = this.getNodeParameter('folderId', i) as string;
const recursive = this.getNodeParameter('recursive', i) as boolean;
qs.recursive = recursive;
responseData = await boxApiRequest.call(this, 'DELETE', `/folders/${folderId}`, qs);
responseData = { success: true };
returnData.push(responseData as IDataObject);
}
}
}
if (resource === 'file' && operation === 'download') {
// For file downloads the files get attached to the existing items
return this.prepareOutputData(items);
} else {
return [this.helpers.returnJsonArray(returnData)];
}
}
}

View file

@ -0,0 +1,354 @@
import {
IHookFunctions,
IWebhookFunctions,
} from 'n8n-core';
import {
INodeTypeDescription,
INodeType,
IWebhookResponseData,
} from 'n8n-workflow';
import {
boxApiRequest,
boxApiRequestAllItems,
} from './GenericFunctions';
export class BoxTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Box Trigger',
name: 'boxTrigger',
icon: 'file:box.png',
group: ['trigger'],
version: 1,
description: 'Starts the workflow when a Github events occurs.',
defaults: {
name: 'Box Trigger',
color: '#00aeef',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'boxOAuth2Api',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Events',
name: 'events',
type: 'multiOptions',
options: [
{
name: 'Collaboration Created',
value: 'COLLABORATION.CREATED',
description: 'A collaboration is created',
},
{
name: 'Collaboration Accepted',
value: 'COLLABORATION.ACCEPTED',
description: 'A collaboration has been accepted',
},
{
name: 'Collaboration Rejected',
value: 'COLLABORATION.REJECTED',
description: 'A collaboration has been rejected',
},
{
name: 'Collaboration Removed',
value: 'COLLABORATION.REMOVED',
description: 'A collaboration has been removed',
},
{
name: 'Collaboration Updated',
value: 'COLLABORATION.UPDATED',
description: 'A collaboration has been updated.',
},
{
name: 'Comment Created',
value: 'COMMENT.CREATED',
description: 'A comment object is created',
},
{
name: 'Comment Updated',
value: 'COMMENT.UPDATED',
description: 'A comment object is edited',
},
{
name: 'Comment Deleted',
value: 'COMMENT.DELETED',
description: 'A comment object is removed',
},
{
name: 'File Uploaded',
value: 'FILE.UPLOADED',
description: 'A file is uploaded to or moved to this folder',
},
{
name: 'File Previewed',
value: 'FILE.PREVIEWED',
description: 'A file is previewed',
},
{
name: 'File Downloaded',
value: 'FILE.DOWNLOADED',
description: 'A file is downloaded',
},
{
name: 'File Trashed',
value: 'FILE.TRASHED',
description: 'A file is moved to the trash',
},
{
name: 'File Deleted',
value: 'FILE.DELETED',
description: 'A file is moved to the trash',
},
{
name: 'File Restored',
value: 'FILE.RESTORED',
description: 'A file is restored from the trash',
},
{
name: 'File Copied',
value: 'FILE.COPIED',
description: 'A file is copied',
},
{
name: 'File Moved',
value: 'FILE.MOVED',
description: 'A file is moved from one folder to another',
},
{
name: 'File Locked',
value: 'FILE.LOCKED',
description: 'A file is locked',
},
{
name: 'File Unlocked',
value: 'FILE.UNLOCKED',
description: 'A file is unlocked',
},
{
name: 'File Renamed',
value: 'FILE.RENAMED',
description: 'A file was renamed.',
},
{
name: 'Folder Created',
value: 'FOLDER.CREATED',
description: 'A folder is created',
},
{
name: 'Folder Renamed',
value: 'FOLDER.RENAMED',
description: 'A folder was renamed.',
},
{
name: 'Folder Downloaded',
value: 'FOLDER.DOWNLOADED',
description: 'A folder is downloaded',
},
{
name: 'Folder Restored',
value: 'FOLDER.RESTORED',
description: 'A folder is restored from the trash',
},
{
name: 'Folder Deleted',
value: 'FOLDER.DELETED',
description: 'A folder is permanently removed',
},
{
name: 'Folder Copied',
value: 'FOLDER.COPIED',
description: 'A copy of a folder is made',
},
{
name: 'Folder Moved',
value: 'FOLDER.MOVED',
description: 'A folder is moved to a different folder',
},
{
name: 'Folder Trashed',
value: 'FOLDER.TRASHED',
description: 'A folder is moved to the trash',
},
{
name: 'Metadata Instance Created',
value: 'METADATA_INSTANCE.CREATED',
description: 'A new metadata template instance is associated with a file or folder',
},
{
name: 'Metadata Instance Updated',
value: 'METADATA_INSTANCE.UPDATED',
description: 'An attribute (value) is updated/deleted for an existing metadata template instance associated with a file or folder',
},
{
name: 'Metadata Instance Deleted',
value: 'METADATA_INSTANCE.DELETED',
description: 'An existing metadata template instance associated with a file or folder is deleted',
},
{
name: 'Sharedlink Deleted',
value: 'SHARED_LINK.DELETED',
description: 'A shared link was deleted',
},
{
name: 'Sharedlink Created',
value: 'SHARED_LINK.CREATED',
description: 'A shared link was created',
},
{
name: 'Sharedlink UPDATED',
value: 'SHARED_LINK.UPDATED',
description: 'A shared link was updated',
},
{
name: 'Task Assignment Created',
value: 'TASK_ASSIGNMENT.CREATED',
description: 'A task is created',
},
{
name: 'Task Assignment Updated',
value: 'TASK_ASSIGNMENT.UPDATED',
description: 'A task is updated',
},
{
name: 'Webhook Deleted',
value: 'WEBHOOK.DELETED',
description: 'When a webhook is deleted',
},
],
required: true,
default: [],
description: 'The events to listen to.',
},
{
displayName: 'Target Type',
name: 'targetType',
type: 'options',
options: [
{
name: 'File',
value: 'file',
},
{
name: 'Folder',
value: 'folder',
},
],
default: '',
description: 'The type of item to trigger a webhook',
},
{
displayName: 'Target ID',
name: 'targetId',
type: 'string',
required: false,
default: '',
description: 'The ID of the item to trigger a webhook',
},
],
};
// @ts-ignore (because of request)
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
const webhookData = this.getWorkflowStaticData('node');
const events = this.getNodeParameter('events') as string;
const targetId = this.getNodeParameter('targetId') as string;
const targetType = this.getNodeParameter('targetType') as string;
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const endpoint = '/webhooks';
const webhooks = await boxApiRequestAllItems.call(this, 'entries', 'GET', endpoint, {});
console.log(webhooks);
for (const webhook of webhooks) {
if (webhook.address === webhookUrl &&
webhook.target.id === targetId &&
webhook.target.type === targetType) {
for (const event of events) {
if (!webhook.triggers.includes(event)) {
return false;
}
}
}
// Set webhook-id to be sure that it can be deleted
webhookData.webhookId = webhook.id as string;
return true;
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default');
const events = this.getNodeParameter('events') as string;
const targetId = this.getNodeParameter('targetId') as string;
const targetType = this.getNodeParameter('targetType') as string;
const endpoint = '/webhooks';
const body = {
address: webhookUrl,
triggers: events,
target: {
id: targetId,
type: targetType,
}
};
const responseData = await boxApiRequest.call(this, 'POST', endpoint, body);
if (responseData.id === undefined) {
// Required data is missing so was not successful
return false;
}
webhookData.webhookId = responseData.id as string;
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const endpoint = `/webhooks/${webhookData.webhookId}`;
try {
await boxApiRequest.call(this, 'DELETE', endpoint);
} catch (e) {
return false;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registred anymore
delete webhookData.webhookId;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const bodyData = this.getBodyData();
return {
workflowData: [
this.helpers.returnJsonArray(bodyData)
],
};
}
}

View file

@ -0,0 +1,335 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const fileOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'file',
],
},
},
options: [
{
name: 'Copy',
value: 'copy',
description: 'Copy a file',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a file',
},
{
name: 'Download',
value: 'download',
description: 'Download a file',
},
{
name: 'Get',
value: 'get',
description: 'Get a file',
},
{
name: 'Upload',
value: 'upload',
description: 'Upload a file',
},
],
default: 'upload',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const fileFields = [
/* -------------------------------------------------------------------------- */
/* file:copy */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'copy',
],
resource: [
'file',
],
},
},
default: '',
description: 'File ID',
},
{
displayName: 'Parent ID',
name: 'parentId',
type: 'string',
default: '',
displayOptions: {
show: {
operation: [
'copy',
],
resource: [
'file',
],
},
},
description: 'The ID of folder to copy the file to. If not defined will be copied to the root folder',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
operation: [
'copy',
],
resource: [
'file',
],
},
},
default: {},
options: [
{
displayName: 'Fields',
name: 'fields',
type: 'string',
default: '',
description: 'A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response.',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'An optional new name for the copied file.',
},
{
displayName: 'Version',
name: 'version',
type: 'string',
default: '',
description: 'An optional ID of the specific file version to copy.',
},
],
},
/* -------------------------------------------------------------------------- */
/* file:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: [
'delete',
],
resource: [
'file',
],
},
},
default: '',
description: 'Field ID',
},
/* -------------------------------------------------------------------------- */
/* file:download */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: [
'download',
],
resource: [
'file',
],
},
},
default: '',
description: 'File ID',
},
{
displayName: 'Binary Property',
name: 'binaryPropertyName',
type: 'string',
required: true,
default: 'data',
displayOptions: {
show: {
operation: [
'download'
],
resource: [
'file',
],
},
},
description: 'Name of the binary property to which to<br />write the data of the read file.',
},
/* -------------------------------------------------------------------------- */
/* file:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'file',
],
},
},
default: '',
description: 'Field ID',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'file',
],
},
},
default: {},
options: [
{
displayName: 'Fields',
name: 'fields',
type: 'string',
default: '',
description: 'A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response.',
},
],
},
/* -------------------------------------------------------------------------- */
/* file:upload */
/* -------------------------------------------------------------------------- */
{
displayName: 'File Name',
name: 'fileName',
type: 'string',
placeholder: 'photo.png',
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'file',
],
},
},
default: '',
description: 'The name the file should be saved as.',
},
{
displayName: 'Binary Data',
name: 'binaryData',
type: 'boolean',
default: false,
required: true,
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'file',
],
},
},
description: 'If the data to upload should be taken from binary field.',
},
{
displayName: 'File Content',
name: 'fileContent',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
binaryData: [
false,
],
operation: [
'upload',
],
resource: [
'file',
],
},
},
placeholder: '',
description: 'The text content of the file.',
},
{
displayName: 'Binary Property',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
displayOptions: {
show: {
binaryData: [
true,
],
operation: [
'upload',
],
resource: [
'file',
],
},
},
placeholder: '',
description: 'Name of the binary property which contains<br />the data for the file.',
},
{
displayName: 'Parent ID',
name: 'parentId',
type: 'string',
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'file',
],
},
},
default: '',
description: 'ID of the parent folder that will contain the file. If not it will be uploaded to the root folder',
},
] as INodeProperties[];

View file

@ -0,0 +1,156 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const folderOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'folder',
],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a folder',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a folder',
},
],
default: 'create',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const folderFields = [
/* -------------------------------------------------------------------------- */
/* folder:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Name',
name: 'name',
required: true,
type: 'string',
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'folder',
],
},
},
default: '',
description: `Folder's name`,
},
{
displayName: 'Parent ID',
name: 'parentId',
type: 'string',
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'folder',
],
},
},
default: '',
description: 'ID of the folder you want to create the new folder in. if not defined it will be created on the root folder',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'folder',
],
},
},
default: {},
placeholder: 'Add Field',
options: [
{
displayName: 'Access',
name: 'access',
type: 'options',
options: [
{
name: 'Collaborators',
value: 'collaborators',
description: 'Only emails from registered email addresses for collaborators will be accepted.',
},
{
name: 'Open',
value: 'open',
description: 'It will accept emails from any email addres',
},
],
default: '',
description: 'ID of the folder you want to create the new folder in',
},
{
displayName: 'Fields',
name: 'fields',
type: 'string',
default: '',
description: 'A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response.',
},
],
},
/* -------------------------------------------------------------------------- */
/* folder:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Folder ID',
name: 'folderId',
type: 'string',
displayOptions: {
show: {
operation: [
'delete',
],
resource: [
'folder',
],
},
},
default: '',
description: 'Folder ID',
},
{
displayName: 'Recursive',
name: 'recursive',
type: 'boolean',
displayOptions: {
show: {
operation: [
'delete',
],
resource: [
'folder',
],
},
},
default: false,
description: 'Delete a folder that is not empty by recursively deleting the folder and all of its content.',
},
] as INodeProperties[];

View file

@ -0,0 +1,87 @@
import {
OptionsWithUri,
} from 'request';
import {
IExecuteFunctions,
IExecuteSingleFunctions,
ILoadOptionsFunctions,
IHookFunctions,
} from 'n8n-core';
import {
IDataObject,
IOAuth2Options,
} from 'n8n-workflow';
export async function boxApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
let options: OptionsWithUri = {
headers: {
'Content-Type': 'application/json',
},
method,
body,
qs,
uri: uri || `https://api.box.com/2.0${resource}`,
json: true,
};
options = Object.assign({}, options, option);
try {
if (Object.keys(body).length === 0) {
delete options.body;
}
const oAuth2Options: IOAuth2Options = {
includeCredentialsOnRefreshOnBody: true,
};
//@ts-ignore
return await this.helpers.requestOAuth2.call(this, 'boxOAuth2Api', options, oAuth2Options);
} catch (error) {
console.log(error);
let errorMessage;
if (error.response && error.response.body) {
if (error.response.body.context_info && error.response.body.context_info.errors) {
const errors = error.response.body.context_info.errors;
errorMessage = errors.map((e: IDataObject) => e.message);
errorMessage = errorMessage.join('|');
} else if (error.response.body.message) {
errorMessage = error.response.body.message;
}
throw new Error(`Box error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
}
}
export async function boxApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
let responseData;
query.limit = 100;
do {
responseData = await boxApiRequest.call(this, method, endpoint, body, query);
query.marker = responseData['next_marker'];
returnData.push.apply(returnData, responseData[propertyName]);
} while (
responseData['next_marker'] !== undefined &&
responseData['next_marker'] !== ''
);
return returnData;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -45,7 +45,7 @@ export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions
return await this.helpers.request!(options);
} else {
// @ts-ignore
return await this.helpers.requestOAuth2!.call(this, 'hubspotOAuth2Api', options, 'Bearer');
return await this.helpers.requestOAuth2!.call(this, 'hubspotOAuth2Api', options, { tokenType: 'Bearer' });
}
} catch (error) {
let errorMessages;

View file

@ -57,7 +57,7 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio
options.url = `${api_endpoint}/3.0${endpoint}`;
//@ts-ignore
return await this.helpers.requestOAuth2!.call(this, 'mailchimpOAuth2Api', options, 'Bearer');
return await this.helpers.requestOAuth2!.call(this, 'mailchimpOAuth2Api', options, { tokenType: 'Bearer' });
}
} catch (error) {
if (error.respose && error.response.body && error.response.body.detail) {

View file

@ -9,8 +9,10 @@ import {
} from 'n8n-core';
import {
IDataObject
IDataObject,
IOAuth2Options,
} from 'n8n-workflow';
import * as _ from 'lodash';
export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: object = {}, query: object = {}, headers: {} | undefined = undefined, option: {} = {}): Promise<any> { // tslint:disable-line:no-any
@ -42,8 +44,13 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu
//@ts-ignore
return await this.helpers.request(options);
} else {
const oAuth2Options: IOAuth2Options = {
tokenType: 'Bearer',
property: 'authed_user.access_token',
};
//@ts-ignore
return await this.helpers.requestOAuth2.call(this, 'slackOAuth2Api', options, 'bearer', 'authed_user.access_token');
return await this.helpers.requestOAuth2.call(this, 'slackOAuth2Api', options, oAuth2Options);
}
} catch (error) {
if (error.statusCode === 401) {

View file

@ -38,6 +38,7 @@
"dist/credentials/BannerbearApi.credentials.js",
"dist/credentials/BitbucketApi.credentials.js",
"dist/credentials/BitlyApi.credentials.js",
"dist/credentials/BoxOAuth2Api.credentials.js",
"dist/credentials/ChargebeeApi.credentials.js",
"dist/credentials/CircleCiApi.credentials.js",
"dist/credentials/ClearbitApi.credentials.js",
@ -177,6 +178,8 @@
"dist/nodes/Bannerbear/Bannerbear.node.js",
"dist/nodes/Bitbucket/BitbucketTrigger.node.js",
"dist/nodes/Bitly/Bitly.node.js",
"dist/nodes/Box/Box.node.js",
"dist/nodes/Box/BoxTrigger.node.js",
"dist/nodes/Calendly/CalendlyTrigger.node.js",
"dist/nodes/Chargebee/Chargebee.node.js",
"dist/nodes/Chargebee/ChargebeeTrigger.node.js",

View file

@ -12,6 +12,11 @@ export interface IBinaryData {
fileExtension?: string;
}
export interface IOAuth2Options {
includeCredentialsOnRefreshOnBody?: boolean;
property?: string;
tokenType?: string;
}
export interface IConnection {
// The node the connection is to
@ -180,7 +185,6 @@ export interface IExecuteData {
node: INode;
}
export type IContextObject = {
[key: string]: any; // tslint:disable-line:no-any
};