diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 790e0600ea70c..6517ef75fe5cd 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -57,13 +57,20 @@ import { createHmac, randomBytes } from 'crypto'; // tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ... import { compare } from 'bcryptjs'; -import { BinaryDataManager, Credentials, LoadNodeParameterOptions, UserSettings } from 'n8n-core'; +import { + BinaryDataManager, + Credentials, + LoadNodeParameterOptions, + LoadNodeListSearch, + UserSettings, +} from 'n8n-core'; import { ICredentialType, IDataObject, INodeCredentials, INodeCredentialsDetails, + INodeListSearchResult, INodeParameters, INodePropertyOptions, INodeType, @@ -142,6 +149,7 @@ import { resolveJwt } from './UserManagement/auth/jwt'; import { User } from './databases/entities/User'; import type { ExecutionRequest, + NodeListSearchRequest, NodeParameterOptionsRequest, OAuthRequest, TagsRequest, @@ -162,6 +170,7 @@ import { } from './UserManagement/UserManagementHelper'; import { loadPublicApiVersions } from './PublicApi'; import * as telemetryScripts from './telemetry/scripts'; +import { ResponseError } from './ResponseHelper'; require('body-parser-xml')(bodyParser); @@ -932,6 +941,62 @@ class App { ), ); + // Returns parameter values which normally get loaded from an external API or + // get generated dynamically + this.app.get( + `/${this.restEndpoint}/nodes-list-search`, + ResponseHelper.send( + async ( + req: NodeListSearchRequest, + res: express.Response, + ): Promise => { + const nodeTypeAndVersion = JSON.parse( + req.query.nodeTypeAndVersion, + ) as INodeTypeNameVersion; + + const { path, methodName } = req.query; + + if (!req.query.currentNodeParameters) { + throw new ResponseError('Parameter currentNodeParameters is required.', undefined, 400); + } + + const currentNodeParameters = JSON.parse( + req.query.currentNodeParameters, + ) as INodeParameters; + + let credentials: INodeCredentials | undefined; + + if (req.query.credentials) { + credentials = JSON.parse(req.query.credentials); + } + + const listSearchInstance = new LoadNodeListSearch( + nodeTypeAndVersion, + NodeTypes(), + path, + currentNodeParameters, + credentials, + ); + + const additionalData = await WorkflowExecuteAdditionalData.getBase( + req.user.id, + currentNodeParameters, + ); + + if (methodName) { + return listSearchInstance.getOptionsViaMethodName( + methodName, + additionalData, + req.query.filter, + req.query.paginationToken, + ); + } + + throw new ResponseError('Parameter methodName is required.', undefined, 400); + }, + ), + ); + // Returns all the node-types this.app.get( `/${this.restEndpoint}/node-types`, diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index e6e615d27e88e..ee3eb9643d7aa 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -285,6 +285,25 @@ export type NodeParameterOptionsRequest = AuthenticatedRequest< } >; +// ---------------------------------- +// /node-list-search +// ---------------------------------- + +export type NodeListSearchRequest = AuthenticatedRequest< + {}, + {}, + {}, + { + nodeTypeAndVersion: string; + methodName: string; + path: string; + currentNodeParameters: string; + credentials: string; + filter?: string; + paginationToken?: string; + } +>; + // ---------------------------------- // /tags // ---------------------------------- diff --git a/packages/core/src/LoadNodeListSearch.ts b/packages/core/src/LoadNodeListSearch.ts new file mode 100644 index 0000000000000..f1f0313dcc434 --- /dev/null +++ b/packages/core/src/LoadNodeListSearch.ts @@ -0,0 +1,133 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { + INode, + INodeCredentials, + INodeListSearchResult, + INodeParameters, + INodeTypeNameVersion, + INodeTypes, + IWorkflowExecuteAdditionalData, + Workflow, +} from 'n8n-workflow'; + +// eslint-disable-next-line import/no-cycle +import { NodeExecuteFunctions } from '.'; + +const TEMP_NODE_NAME = 'Temp-Node'; +const TEMP_WORKFLOW_NAME = 'Temp-Workflow'; + +export class LoadNodeListSearch { + currentNodeParameters: INodeParameters; + + path: string; + + workflow: Workflow; + + constructor( + nodeTypeNameAndVersion: INodeTypeNameVersion, + nodeTypes: INodeTypes, + path: string, + currentNodeParameters: INodeParameters, + credentials?: INodeCredentials, + ) { + const nodeType = nodeTypes.getByNameAndVersion( + nodeTypeNameAndVersion.name, + nodeTypeNameAndVersion.version, + ); + this.currentNodeParameters = currentNodeParameters; + this.path = path; + if (nodeType === undefined) { + throw new Error( + `The node-type "${nodeTypeNameAndVersion.name} v${nodeTypeNameAndVersion.version}" is not known!`, + ); + } + + const nodeData: INode = { + parameters: currentNodeParameters, + id: 'uuid-1234', + name: TEMP_NODE_NAME, + type: nodeTypeNameAndVersion.name, + typeVersion: nodeTypeNameAndVersion.version, + position: [0, 0], + }; + if (credentials) { + nodeData.credentials = credentials; + } + + const workflowData = { + nodes: [nodeData], + connections: {}, + }; + + this.workflow = new Workflow({ + nodes: workflowData.nodes, + connections: workflowData.connections, + active: false, + nodeTypes, + }); + } + + /** + * Returns data of a fake workflow + * + * @returns + * @memberof LoadNodeParameterOptions + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + getWorkflowData() { + return { + name: TEMP_WORKFLOW_NAME, + active: false, + connections: {}, + nodes: Object.values(this.workflow.nodes), + createdAt: new Date(), + updatedAt: new Date(), + }; + } + + /** + * Returns the available options via a predefined method + * + * @param {string} methodName The name of the method of which to get the data from + * @param {IWorkflowExecuteAdditionalData} additionalData + * @returns {Promise} + * @memberof LoadNodeParameterOptions + */ + async getOptionsViaMethodName( + methodName: string, + additionalData: IWorkflowExecuteAdditionalData, + filter?: string, + paginationToken?: string, + ): Promise { + const node = this.workflow.getNode(TEMP_NODE_NAME); + + const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node!.type, node?.typeVersion); + + if ( + !nodeType || + nodeType.methods === undefined || + nodeType.methods.listSearch === undefined || + nodeType.methods.listSearch[methodName] === undefined + ) { + throw new Error( + `The node-type "${node!.type}" does not have the method "${methodName}" defined!`, + ); + } + + const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions( + this.workflow, + node!, + this.path, + additionalData, + ); + + return nodeType.methods.listSearch[methodName].call(thisArgs, filter, paginationToken); + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 11d130d02155d..6a327177ccc5c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -15,6 +15,7 @@ export * from './Constants'; export * from './Credentials'; export * from './Interfaces'; export * from './LoadNodeParameterOptions'; +export * from './LoadNodeListSearch'; export * from './NodeExecuteFunctions'; export * from './WorkflowExecute'; export { NodeExecuteFunctions, UserSettings }; diff --git a/packages/nodes-base/nodes/Airtable/Airtable.node.ts b/packages/nodes-base/nodes/Airtable/Airtable.node.ts index 0dc96e74ca167..5536870d55cd7 100644 --- a/packages/nodes-base/nodes/Airtable/Airtable.node.ts +++ b/packages/nodes-base/nodes/Airtable/Airtable.node.ts @@ -2,7 +2,9 @@ import { IExecuteFunctions } from 'n8n-core'; import { IDataObject, + ILoadOptionsFunctions, INodeExecutionData, + INodeListSearchResult, INodeType, INodeTypeDescription, NodeOperationError, @@ -10,13 +12,25 @@ import { import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from './GenericFunctions'; +interface AirtableBase { + id: string; + name: string; +} + +interface AirtableTable { + id: string; + name: string; + description: string; +} + export class Airtable implements INodeType { description: INodeTypeDescription = { displayName: 'Airtable', name: 'airtable', icon: 'file:airtable.svg', group: ['input'], - version: 1, + version: [1, 2], + defaultVersion: 2, description: 'Read, update, write and delete data from Airtable', defaults: { name: 'Airtable', @@ -80,6 +94,11 @@ export class Airtable implements INodeType { default: '', required: true, description: 'The ID of the base to access', + displayOptions: { + show: { + '@version': [1], + }, + }, }, { displayName: 'Table ID', @@ -89,6 +108,121 @@ export class Airtable implements INodeType { placeholder: 'Stories', required: true, description: 'The ID of the table to access', + displayOptions: { + show: { + '@version': [1], + }, + }, + }, + + { + displayName: 'Base ID', + name: 'applicationRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + displayOptions: { + show: { + '@version': [2], + }, + }, + description: 'The ID of the base to access', + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter base Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'appD3dfaeidke', + url: '=https://airtable.com/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter base URL', + placeholder: 'https://airtable.com/app12DiScdfes/tblAAAAAAAAAAAAA/viwHdfasdfeieg5p', + validation: [ + { + type: 'regex', + properties: { + regex: 'https://airtable.com/([a-zA-Z0-9]+)/[a-zA-Z0-9/]+', + errorMessage: + 'URL has to be in the format: https://airtable.com///', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://airtable.com/([a-zA-Z0-9]+)', + }, + }, + ], + }, + { + displayName: 'Table ID', + name: 'tableRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + displayOptions: { + show: { + '@version': [2], + }, + }, + description: 'The ID of the table', + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter table Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'tbl3dirwqeidke', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter table URL', + placeholder: 'https://airtable.com/app12DiScdfes/tblAAAAAAAAAAAAA/viwHdfasdfeieg5p', + validation: [ + { + type: 'regex', + properties: { + regex: 'https://airtable.com/[a-zA-Z0-9]+/([a-zA-Z0-9]+)', + errorMessage: + 'URL has to be in the format: https://airtable.com//
/', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://airtable.com/[a-zA-Z0-9]+/([a-zA-Z0-9]+)', + }, + }, + ], }, // ---------------------------------- @@ -421,10 +555,27 @@ export class Airtable implements INodeType { const returnData: INodeExecutionData[] = []; let responseData; + const version = this.getNode().typeVersion; + const operation = this.getNodeParameter('operation', 0) as string; - const application = this.getNodeParameter('application', 0) as string; - const table = encodeURI(this.getNodeParameter('table', 0) as string); + let application: string; + if (version === 2) { + application = this.getNodeParameter('applicationRLC', 0, undefined, { + extractValue: true, + }) as string; + } else { + application = this.getNodeParameter('application', 0) as string; + } + + let table: string; + if (version === 2) { + table = this.getNodeParameter('tableRLC', 0, undefined, { + extractValue: true, + }) as string; + } else { + table = this.getNodeParameter('table', 0) as string; + } let returnAll = false; let endpoint = ''; @@ -493,7 +644,7 @@ export class Airtable implements INodeType { } } catch (error) { if (this.continueOnFail()) { - returnData.push({json: { error: error.message }}); + returnData.push({ json: { error: error.message } }); continue; } throw error; @@ -538,7 +689,7 @@ export class Airtable implements INodeType { } } catch (error) { if (this.continueOnFail()) { - returnData.push({json:{ error: error.message }}); + returnData.push({ json: { error: error.message } }); continue; } throw error; @@ -589,14 +740,13 @@ export class Airtable implements INodeType { // We can return from here return [ - this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(returnData), - { itemData: { item: 0 } }, - ), + this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(returnData), { + itemData: { item: 0 }, + }), ]; } catch (error) { if (this.continueOnFail()) { - returnData.push({json:{ error: error.message }}); + returnData.push({ json: { error: error.message } }); } else { throw error; } @@ -631,7 +781,7 @@ export class Airtable implements INodeType { returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { - returnData.push({json:{ error: error.message }}); + returnData.push({ json: { error: error.message } }); continue; } throw error; @@ -718,7 +868,7 @@ export class Airtable implements INodeType { } } catch (error) { if (this.continueOnFail()) { - returnData.push({json:{ error: error.message }}); + returnData.push({ json: { error: error.message } }); continue; } throw error; diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts index 3159d1fe11171..9012adf96a86a 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts @@ -1,2594 +1,30 @@ -import { IExecuteFunctions } from 'n8n-core'; - -import { - IDataObject, - INodeExecutionData, - INodeType, - INodeTypeDescription, - NodeOperationError, -} from 'n8n-workflow'; - -import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions'; - -import { v4 as uuid } from 'uuid'; - -export class GoogleDrive implements INodeType { - description: INodeTypeDescription = { - displayName: 'Google Drive', - name: 'googleDrive', - icon: 'file:googleDrive.svg', - group: ['input'], - version: [1, 2], - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Access data on Google Drive', - defaults: { - name: 'Google Drive', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'googleApi', - required: true, - displayOptions: { - show: { - authentication: ['serviceAccount'], - }, - }, - }, - { - name: 'googleDriveOAuth2Api', - required: true, - displayOptions: { - show: { - authentication: ['oAuth2'], - }, - }, - }, - ], - properties: [ - { - displayName: 'Authentication', - name: 'authentication', - type: 'options', - options: [ - { - name: 'Service Account', - value: 'serviceAccount', - }, - { - name: 'OAuth2', - value: 'oAuth2', - }, - ], - default: 'serviceAccount', - displayOptions: { - show: { - '@version': [1], - }, - }, - }, - { - displayName: 'Authentication', - name: 'authentication', - type: 'options', - options: [ - { - name: 'OAuth2 (Recommended)', - value: 'oAuth2', - }, - { - name: 'Service Account', - value: 'serviceAccount', - }, - ], - default: 'oAuth2', - displayOptions: { - show: { - '@version': [2], - }, - }, - }, - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Drive', - value: 'drive', - }, - { - name: 'File', - value: 'file', - }, - { - name: 'Folder', - value: 'folder', - }, - ], - default: 'file', - }, - - // ---------------------------------- - // operations - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['file'], - }, - }, - options: [ - { - name: 'Copy', - value: 'copy', - description: 'Copy a file', - action: 'Copy a file', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a file', - action: 'Delete a file', - }, - { - name: 'Download', - value: 'download', - description: 'Download a file', - action: 'Download a file', - }, - { - name: 'List', - value: 'list', - description: 'List files and folders', - action: 'List a file', - }, - { - name: 'Share', - value: 'share', - description: 'Share a file', - action: 'Share a file', - }, - { - name: 'Update', - value: 'update', - description: 'Update a file', - action: 'Update a file', - }, - { - name: 'Upload', - value: 'upload', - description: 'Upload a file', - action: 'Upload a file', - }, - ], - default: 'upload', - }, - - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['folder'], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a folder', - action: 'Create a folder', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a folder', - action: 'Delete a folder', - }, - { - name: 'Share', - value: 'share', - description: 'Share a folder', - action: 'Share a folder', - }, - ], - default: 'create', - }, - - // ---------------------------------- - // file - // ---------------------------------- - - // ---------------------------------- - // file:copy - // ---------------------------------- - { - displayName: 'ID', - name: 'fileId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: ['copy'], - resource: ['file'], - }, - }, - description: 'The ID of the file to copy', - }, - - // ---------------------------------- - // file/folder:delete - // ---------------------------------- - { - displayName: 'ID', - name: 'fileId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: ['delete'], - resource: ['file', 'folder'], - }, - }, - description: 'The ID of the file/folder to delete', - }, - - // ---------------------------------- - // file:download - // ---------------------------------- - { - displayName: 'File ID', - name: 'fileId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: ['download'], - resource: ['file'], - }, - }, - description: 'The ID of the file to download', - }, - { - 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 write the data of the read file', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['download'], - resource: ['file'], - }, - }, - options: [ - { - displayName: 'Google File Conversion', - name: 'googleFileConversion', - type: 'fixedCollection', - typeOptions: { - multipleValues: false, - }, - default: {}, - placeholder: 'Add Conversion', - options: [ - { - displayName: 'Conversion', - name: 'conversion', - values: [ - { - displayName: 'Google Docs', - name: 'docsToFormat', - type: 'options', - options: [ - { - name: 'To HTML', - value: 'text/html', - }, - { - name: 'To MS Word', - value: - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - }, - { - name: 'To OpenOffice Doc', - value: 'application/vnd.oasis.opendocument.text', - }, - { - name: 'To PDF', - value: 'application/pdf', - }, - { - name: 'To Rich Text', - value: 'application/rtf', - }, - ], - default: - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - description: 'Format used to export when downloading Google Docs files', - }, - { - displayName: 'Google Drawings', - name: 'drawingsToFormat', - type: 'options', - options: [ - { - name: 'To JPEG', - value: 'image/jpeg', - }, - { - name: 'To PNG', - value: 'image/png', - }, - { - name: 'To SVG', - value: 'image/svg+xml', - }, - { - name: 'To PDF', - value: 'application/pdf', - }, - ], - default: 'image/jpeg', - description: 'Format used to export when downloading Google Drawings files', - }, - { - displayName: 'Google Slides', - name: 'slidesToFormat', - type: 'options', - options: [ - { - name: 'To MS PowerPoint', - value: - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - }, - { - name: 'To PDF', - value: 'application/pdf', - }, - { - name: 'To OpenOffice Presentation', - value: 'application/vnd.oasis.opendocument.presentation', - }, - { - name: 'To Plain Text', - value: 'text/plain', - }, - ], - default: - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - description: 'Format used to export when downloading Google Slides files', - }, - { - displayName: 'Google Sheets', - name: 'sheetsToFormat', - type: 'options', - options: [ - { - name: 'To MS Excel', - value: 'application/x-vnd.oasis.opendocument.spreadsheet', - }, - { - name: 'To PDF', - value: 'application/pdf', - }, - { - name: 'To CSV', - value: 'text/csv', - }, - ], - default: 'application/x-vnd.oasis.opendocument.spreadsheet', - description: 'Format used to export when downloading Google Spreadsheets files', - }, - ], - }, - ], - }, - { - displayName: 'File Name', - name: 'fileName', - type: 'string', - default: '', - description: 'File name. Ex: data.pdf.', - }, - ], - }, - - // ---------------------------------- - // file:list - // ---------------------------------- - { - displayName: 'Use Query String', - name: 'useQueryString', - type: 'boolean', - default: false, - displayOptions: { - show: { - operation: ['list'], - resource: ['file'], - }, - }, - description: 'Whether a query string should be used to filter results', - }, - { - displayName: 'Query String', - name: 'queryString', - type: 'string', - default: '', - displayOptions: { - show: { - operation: ['list'], - useQueryString: [true], - resource: ['file'], - }, - }, - placeholder: "name contains 'invoice'", - description: 'Query to use to return only specific files', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - displayOptions: { - show: { - operation: ['list'], - resource: ['file'], - }, - }, - typeOptions: { - minValue: 1, - maxValue: 1000, - }, - default: 50, - description: 'Max number of results to return', - }, - { - displayName: 'Filters', - name: 'queryFilters', - placeholder: 'Add Filter', - description: 'Filters to use to return only specific files', - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, - default: {}, - displayOptions: { - show: { - operation: ['list'], - useQueryString: [false], - resource: ['file'], - }, - }, - options: [ - { - name: 'name', - displayName: 'Name', - values: [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Contains', - value: 'contains', - }, - { - name: 'Is', - value: 'is', - }, - { - name: 'Is Not', - value: 'isNot', - }, - ], - default: 'contains', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'The value for operation', - }, - ], - }, - { - name: 'mimeType', - displayName: 'Mime Type', - values: [ - { - displayName: 'Mime Type', - name: 'mimeType', - type: 'options', - options: [ - { - name: '3rd Party Shortcut', - value: 'application/vnd.google-apps.drive-sdk', - }, - { - name: 'Audio', - value: 'application/vnd.google-apps.audio', - }, - { - name: 'Custom Mime Type', - value: 'custom', - }, - { - name: 'Google Apps Scripts', - value: 'application/vnd.google-apps.script', - }, - { - name: 'Google Docs', - value: 'application/vnd.google-apps.document', - }, - { - name: 'Google Drawing', - value: 'application/vnd.google-apps.drawing', - }, - { - name: 'Google Drive File', - value: 'application/vnd.google-apps.file', - }, - { - name: 'Google Drive Folder', - value: 'application/vnd.google-apps.folder', - }, - { - name: 'Google Forms', - value: 'application/vnd.google-apps.form', - }, - { - name: 'Google Fusion Tables', - value: 'application/vnd.google-apps.fusiontable', - }, - { - name: 'Google My Maps', - value: 'application/vnd.google-apps.map', - }, - { - name: 'Google Sheets', - value: 'application/vnd.google-apps.spreadsheet', - }, - { - name: 'Google Sites', - value: 'application/vnd.google-apps.site', - }, - { - name: 'Google Slides', - value: 'application/vnd.google-apps.presentation', - }, - { - name: 'Photo', - value: 'application/vnd.google-apps.photo', - }, - { - name: 'Unknown', - value: 'application/vnd.google-apps.unknown', - }, - { - name: 'Video', - value: 'application/vnd.google-apps.video', - }, - ], - default: 'application/vnd.google-apps.file', - description: 'The Mime-Type of the files to return', - }, - { - displayName: 'Custom Mime Type', - name: 'customMimeType', - type: 'string', - default: '', - displayOptions: { - show: { - mimeType: ['custom'], - }, - }, - }, - ], - }, - ], - }, - - // ---------------------------------- - // file:share - // ---------------------------------- - { - displayName: 'File ID', - name: 'fileId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: ['share'], - resource: ['file', 'folder'], - }, - }, - description: 'The ID of the file or shared drive', - }, - { - displayName: 'Permissions', - name: 'permissionsUi', - placeholder: 'Add Permission', - type: 'fixedCollection', - default: {}, - typeOptions: { - multipleValues: false, - }, - displayOptions: { - show: { - resource: ['file', 'folder'], - operation: ['share'], - }, - }, - options: [ - { - displayName: 'Permission', - name: 'permissionsValues', - values: [ - { - displayName: 'Role', - name: 'role', - type: 'options', - options: [ - { - name: 'Commenter', - value: 'commenter', - }, - { - name: 'File Organizer', - value: 'fileOrganizer', - }, - { - name: 'Organizer', - value: 'organizer', - }, - { - name: 'Owner', - value: 'owner', - }, - { - name: 'Reader', - value: 'reader', - }, - { - name: 'Writer', - value: 'writer', - }, - ], - default: '', - }, - { - displayName: 'Type', - name: 'type', - type: 'options', - options: [ - { - name: 'User', - value: 'user', - }, - { - name: 'Group', - value: 'group', - }, - { - name: 'Domain', - value: 'domain', - }, - { - name: 'Anyone', - value: 'anyone', - }, - ], - default: '', - description: - 'Information about the different types can be found here', - }, - { - displayName: 'Email Address', - name: 'emailAddress', - type: 'string', - displayOptions: { - show: { - type: ['user', 'group'], - }, - }, - default: '', - description: - 'The email address of the user or group to which this permission refers', - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - displayOptions: { - show: { - type: ['domain'], - }, - }, - default: '', - description: 'The domain to which this permission refers', - }, - { - displayName: 'Allow File Discovery', - name: 'allowFileDiscovery', - type: 'boolean', - displayOptions: { - show: { - type: ['domain', 'anyone'], - }, - }, - default: false, - description: - 'Whether the permission allows the file to be discovered through search', - }, - ], - }, - ], - }, - - { - displayName: 'Binary Data', - name: 'binaryData', - type: 'boolean', - default: false, - displayOptions: { - show: { - operation: ['upload'], - resource: ['file'], - }, - }, - description: 'Whether the data to upload should be taken from binary field', - }, - { - displayName: 'File Content', - name: 'fileContent', - type: 'string', - default: '', - displayOptions: { - show: { - operation: ['upload'], - resource: ['file'], - binaryData: [false], - }, - }, - placeholder: '', - description: 'The text content of the file to upload', - }, - { - displayName: 'Binary Property', - name: 'binaryPropertyName', - type: 'string', - default: 'data', - required: true, - displayOptions: { - show: { - operation: ['upload'], - resource: ['file'], - binaryData: [true], - }, - }, - placeholder: '', - description: - 'Name of the binary property which contains the data for the file to be uploaded', - }, - - // ---------------------------------- - // file:update - // ---------------------------------- - { - displayName: 'ID', - name: 'fileId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: ['update'], - resource: ['file'], - }, - }, - description: 'The ID of the file to update', - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['update'], - resource: ['file'], - }, - }, - options: [ - { - displayName: 'File Name', - name: 'fileName', - type: 'string', - default: '', - description: 'The name of the file', - }, - { - displayName: 'Keep Revision Forever', - name: 'keepRevisionForever', - type: 'boolean', - default: false, - description: - "Whether to set the 'keepForever' field in the new head revision. This is only applicable to files with binary content in Google Drive. Only 200 revisions for the file can be kept forever. If the limit is reached, try deleting pinned revisions.", - }, - { - displayName: 'Move to Trash', - name: 'trashed', - type: 'boolean', - default: false, - description: 'Whether to move a file to the trash. Only the owner may trash a file.', - }, - { - displayName: 'OCR Language', - name: 'ocrLanguage', - type: 'string', - default: '', - description: 'A language hint for OCR processing during image import (ISO 639-1 code)', - }, - { - displayName: 'Parent ID', - name: 'parentId', - type: 'string', - default: '', - description: 'The ID of the parent to set', - }, - { - displayName: 'Use Content As Indexable Text', - name: 'useContentAsIndexableText', - type: 'boolean', - default: false, - description: 'Whether to use the uploaded content as indexable text', - }, - ], - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['update'], - resource: ['file'], - }, - }, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'multiOptions', - options: [ - { - name: '[All]', - value: '*', - description: 'All fields', - }, - { - name: 'explicitlyTrashed', - value: 'explicitlyTrashed', - }, - { - name: 'exportLinks', - value: 'exportLinks', - }, - { - name: 'hasThumbnail', - value: 'hasThumbnail', - }, - { - name: 'iconLink', - value: 'iconLink', - }, - { - name: 'ID', - value: 'id', - }, - { - name: 'Kind', - value: 'kind', - }, - { - name: 'mimeType', - value: 'mimeType', - }, - { - name: 'Name', - value: 'name', - }, - { - name: 'Permissions', - value: 'permissions', - }, - { - name: 'Shared', - value: 'shared', - }, - { - name: 'Spaces', - value: 'spaces', - }, - { - name: 'Starred', - value: 'starred', - }, - { - name: 'thumbnailLink', - value: 'thumbnailLink', - }, - { - name: 'Trashed', - value: 'trashed', - }, - { - name: 'Version', - value: 'version', - }, - { - name: 'webViewLink', - value: 'webViewLink', - }, - ], - default: [], - description: 'The fields to return', - }, - ], - }, - // ---------------------------------- - // file:upload - // ---------------------------------- - { - displayName: 'File Name', - name: 'name', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: ['upload'], - resource: ['file'], - }, - }, - placeholder: 'invoice_1.pdf', - description: 'The name the file should be saved as', - }, - // ---------------------------------- - { - displayName: 'Resolve Data', - name: 'resolveData', - type: 'boolean', - default: false, - displayOptions: { - show: { - operation: ['upload'], - resource: ['file'], - }, - }, - // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether - description: - 'By default the response only contain the ID of the file. If this option gets activated, it will resolve the data automatically.', - }, - { - displayName: 'Parents', - name: 'parents', - type: 'string', - typeOptions: { - multipleValues: true, - }, - default: [], - displayOptions: { - show: { - operation: ['upload'], - resource: ['file'], - }, - }, - description: 'The IDs of the parent folders which contain the file', - }, - - // ---------------------------------- - // folder - // ---------------------------------- - - // ---------------------------------- - // folder:create - // ---------------------------------- - { - displayName: 'Folder', - name: 'name', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: ['create'], - resource: ['folder'], - }, - }, - placeholder: 'invoices', - description: 'The name of folder to create', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - '/operation': ['copy', 'list', 'share', 'create'], - '/resource': ['file', 'folder'], - }, - }, - options: [ - { - displayName: 'Email Message', - name: 'emailMessage', - type: 'string', - displayOptions: { - show: { - '/operation': ['share'], - '/resource': ['file', 'folder'], - }, - }, - default: '', - description: 'A plain text custom message to include in the notification email', - }, - { - displayName: 'Enforce Single Parent', - name: 'enforceSingleParent', - type: 'boolean', - displayOptions: { - show: { - '/operation': ['share'], - '/resource': ['file', 'folder'], - }, - }, - default: false, - description: - 'Whether to opt in to API behavior that aims for all items to have exactly one parent. This parameter only takes effect if the item is not in a shared drive.', - }, - { - displayName: 'Fields', - name: 'fields', - type: 'multiOptions', - displayOptions: { - show: { - '/operation': ['list', 'copy'], - }, - }, - options: [ - { - name: '*', - value: '*', - description: 'All fields', - }, - { - name: 'explicitlyTrashed', - value: 'explicitlyTrashed', - }, - { - name: 'exportLinks', - value: 'exportLinks', - }, - { - name: 'hasThumbnail', - value: 'hasThumbnail', - }, - { - name: 'iconLink', - value: 'iconLink', - }, - { - name: 'ID', - value: 'id', - }, - { - name: 'Kind', - value: 'kind', - }, - { - name: 'mimeType', - value: 'mimeType', - }, - { - name: 'Name', - value: 'name', - }, - { - name: 'Permissions', - value: 'permissions', - }, - { - name: 'Shared', - value: 'shared', - }, - { - name: 'Spaces', - value: 'spaces', - }, - { - name: 'Starred', - value: 'starred', - }, - { - name: 'thumbnailLink', - value: 'thumbnailLink', - }, - { - name: 'Trashed', - value: 'trashed', - }, - { - name: 'Version', - value: 'version', - }, - { - name: 'webViewLink', - value: 'webViewLink', - }, - ], - default: [], - description: 'The fields to return', - }, - { - displayName: 'Move To New Owners Root', - name: 'moveToNewOwnersRoot', - type: 'boolean', - displayOptions: { - show: { - '/operation': ['share'], - '/resource': ['file', 'folder'], - }, - }, - default: false, - // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether - description: - "

This parameter only takes effect if the item is not in a shared drive and the request is attempting to transfer the ownership of the item.

When set to true, the item is moved to the new owner's My Drive root folder and all prior parents removed.

", - }, - { - displayName: 'Send Notification Email', - name: 'sendNotificationEmail', - type: 'boolean', - displayOptions: { - show: { - '/operation': ['share'], - '/resource': ['file', 'folder'], - }, - }, - default: false, - description: 'Whether to send a notification email when sharing to users or groups', - }, - { - displayName: 'Supports All Drives', - name: 'supportsAllDrives', - type: 'boolean', - displayOptions: { - show: { - '/operation': ['share'], - '/resource': ['file', 'folder'], - }, - }, - default: false, - description: - 'Whether the requesting application supports both My Drives and shared drives', - }, - { - displayName: 'Transfer Ownership', - name: 'transferOwnership', - type: 'boolean', - displayOptions: { - show: { - '/operation': ['share'], - '/resource': ['file', 'folder'], - }, - }, - default: false, - description: - 'Whether to transfer ownership to the specified user and downgrade the current owner to a writer', - }, - { - displayName: 'Use Domain Admin Access', - name: 'useDomainAdminAccess', - type: 'boolean', - displayOptions: { - show: { - '/operation': ['share'], - '/resource': ['file', 'folder'], - }, - }, - default: false, - description: - 'Whether to perform the operation as domain administrator, i.e. if you are an administrator of the domain to which the shared drive belongs, you will be granted access automatically.', - }, - - { - displayName: 'File Name', - name: 'name', - type: 'string', - displayOptions: { - show: { - '/operation': ['copy'], - '/resource': ['file'], - }, - }, - default: '', - placeholder: 'invoice_1.pdf', - description: 'The name the file should be saved as', - }, - { - displayName: 'Parents', - name: 'parents', - type: 'string', - displayOptions: { - show: { - '/operation': ['copy', 'create'], - '/resource': ['file', 'folder'], - }, - }, - typeOptions: { - multipleValues: true, - }, - default: [], - description: 'The IDs of the parent folders the file/folder should be saved in', - }, - { - displayName: 'Spaces', - name: 'spaces', - type: 'multiOptions', - displayOptions: { - show: { - '/operation': ['list'], - '/resource': ['file'], - }, - }, - options: [ - { - name: '[All]', - value: '*', - description: 'All spaces', - }, - { - name: 'appDataFolder', - value: 'appDataFolder', - }, - { - name: 'Drive', - value: 'drive', - }, - { - name: 'Photos', - value: 'photos', - }, - ], - default: [], - description: 'The spaces to operate on', - }, - { - displayName: 'Corpora', - name: 'corpora', - type: 'options', - displayOptions: { - show: { - '/operation': ['list'], - '/resource': ['file'], - }, - }, - options: [ - { - name: 'User', - value: 'user', - description: 'All files in "My Drive" and "Shared with me"', - }, - { - name: 'Domain', - value: 'domain', - description: "All files shared to the user's domain that are searchable", - }, - { - name: 'Drive', - value: 'drive', - description: 'All files contained in a single shared drive', - }, - { - name: 'allDrives', - value: 'allDrives', - description: 'All drives', - }, - ], - default: '', - description: 'The corpora to operate on', - }, - { - displayName: 'Drive ID', - name: 'driveId', - type: 'string', - default: '', - displayOptions: { - show: { - '/operation': ['list'], - '/resource': ['file'], - corpora: ['drive'], - }, - }, - description: - 'ID of the shared drive to search. The driveId parameter must be specified if and only if corpora is set to drive.', - }, - ], - }, - // ---------------------------------- - // drive - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['drive'], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a drive', - action: 'Create a drive', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a drive', - action: 'Delete a drive', - }, - { - name: 'Get', - value: 'get', - description: 'Get a drive', - action: 'Get a drive', - }, - { - name: 'List', - value: 'list', - description: 'List all drives', - action: 'List all drives', - }, - { - name: 'Update', - value: 'update', - description: 'Update a drive', - action: 'Update a drive', - }, - ], - default: 'create', - }, - // ---------------------------------- - // drive:create - // ---------------------------------- - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - displayOptions: { - show: { - operation: ['create'], - resource: ['drive'], - }, - }, - description: 'The name of this shared drive', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['create'], - resource: ['drive'], - }, - }, - options: [ - { - displayName: 'Capabilities', - name: 'capabilities', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Can Add Children', - name: 'canAddChildren', - type: 'boolean', - default: false, - description: - 'Whether the current user can add children to folders in this shared drive', - }, - { - displayName: 'Can Change Copy Requires Writer Permission Restriction', - name: 'canChangeCopyRequiresWriterPermissionRestriction', - type: 'boolean', - default: false, - description: - 'Whether the current user can change the copyRequiresWriterPermission restriction of this shared drive', - }, - { - displayName: 'Can Change Domain Users Only Restriction', - name: 'canChangeDomainUsersOnlyRestriction', - type: 'boolean', - default: false, - description: - 'Whether the current user can change the domainUsersOnly restriction of this shared drive', - }, - { - displayName: 'Can Change Drive Background', - name: 'canChangeDriveBackground', - type: 'boolean', - default: false, - description: - 'Whether the current user can change the background of this shared drive', - }, - { - displayName: 'Can Change Drive Members Only Restriction', - name: 'canChangeDriveMembersOnlyRestriction', - type: 'boolean', - default: false, - description: - 'Whether the current user can change the driveMembersOnly restriction of this shared drive', - }, - { - displayName: 'Can Comment', - name: 'canComment', - type: 'boolean', - default: false, - description: 'Whether the current user can comment on files in this shared drive', - }, - { - displayName: 'Can Copy', - name: 'canCopy', - type: 'boolean', - default: false, - description: 'Whether the current user can copy files in this shared drive', - }, - { - displayName: 'Can Delete Children', - name: 'canDeleteChildren', - type: 'boolean', - default: false, - description: - 'Whether the current user can delete children from folders in this shared drive', - }, - { - displayName: 'Can Delete Drive', - name: 'canDeleteDrive', - type: 'boolean', - default: false, - description: - 'Whether the current user can delete this shared drive. Attempting to delete the shared drive may still fail if there are untrashed items inside the shared drive.', - }, - { - displayName: 'Can Download', - name: 'canDownload', - type: 'boolean', - default: false, - description: 'Whether the current user can download files in this shared drive', - }, - { - displayName: 'Can Edit', - name: 'canEdit', - type: 'boolean', - default: false, - description: 'Whether the current user can edit files in this shared drive', - }, - { - displayName: 'Can List Children', - name: 'canListChildren', - type: 'boolean', - default: false, - description: - 'Whether the current user can list the children of folders in this shared drive', - }, - { - displayName: 'Can Manage Members', - name: 'canManageMembers', - type: 'boolean', - default: false, - description: - 'Whether the current user can add members to this shared drive or remove them or change their role', - }, - { - displayName: 'Can Read Revisions', - name: 'canReadRevisions', - type: 'boolean', - default: false, - description: - 'Whether the current user can read the revisions resource of files in this shared drive', - }, - { - displayName: 'Can Rename', - name: 'canRename', - type: 'boolean', - default: false, - description: - 'Whether the current user can rename files or folders in this shared drive', - }, - { - displayName: 'Can Rename Drive', - name: 'canRenameDrive', - type: 'boolean', - default: false, - description: 'Whether the current user can rename this shared drive', - }, - { - displayName: 'Can Share', - name: 'canShare', - type: 'boolean', - default: false, - description: 'Whether the current user can rename this shared drive', - }, - { - displayName: 'Can Trash Children', - name: 'canTrashChildren', - type: 'boolean', - default: false, - description: - 'Whether the current user can trash children from folders in this shared drive', - }, - ], - }, - { - displayName: 'Color RGB', - name: 'colorRgb', - type: 'color', - default: '', - description: 'The color of this shared drive as an RGB hex string', - }, - { - displayName: 'Created Time', - name: 'createdTime', - type: 'dateTime', - default: '', - description: 'The time at which the shared drive was created (RFC 3339 date-time)', - }, - { - displayName: 'Hidden', - name: 'hidden', - type: 'boolean', - default: false, - description: 'Whether the shared drive is hidden from default view', - }, - { - displayName: 'Restrictions', - name: 'restrictions', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Admin Managed Restrictions', - name: 'adminManagedRestrictions', - type: 'boolean', - default: false, - description: - 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', - }, - { - displayName: 'Copy Requires Writer Permission', - name: 'copyRequiresWriterPermission', - type: 'boolean', - default: false, - description: - 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', - }, - { - displayName: 'Domain Users Only', - name: 'domainUsersOnly', - type: 'boolean', - default: false, - description: - 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.', - }, - { - displayName: 'Drive Members Only', - name: 'driveMembersOnly', - type: 'boolean', - default: false, - description: - 'Whether access to items inside this shared drive is restricted to its members', - }, - ], - }, - ], - }, - // ---------------------------------- - // drive:delete - // ---------------------------------- - { - displayName: 'Drive ID', - name: 'driveId', - type: 'string', - default: '', - displayOptions: { - show: { - operation: ['delete'], - resource: ['drive'], - }, - }, - description: 'The ID of the shared drive', - }, - // ---------------------------------- - // drive:get - // ---------------------------------- - { - displayName: 'Drive ID', - name: 'driveId', - type: 'string', - default: '', - displayOptions: { - show: { - operation: ['get'], - resource: ['drive'], - }, - }, - description: 'The ID of the shared drive', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['get'], - resource: ['drive'], - }, - }, - options: [ - { - displayName: 'Use Domain Admin Access', - name: 'useDomainAdminAccess', - type: 'boolean', - default: false, - description: - 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).', - }, - ], - }, - // ---------------------------------- - // drive:list - // ---------------------------------- - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - displayOptions: { - show: { - operation: ['list'], - resource: ['drive'], - }, - }, - default: false, - description: 'Whether to return all results or only up to a given limit', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - displayOptions: { - show: { - operation: ['list'], - resource: ['drive'], - returnAll: [false], - }, - }, - typeOptions: { - minValue: 1, - maxValue: 200, - }, - default: 100, - description: 'Max number of results to return', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['list'], - resource: ['drive'], - }, - }, - options: [ - { - displayName: 'Query', - name: 'q', - type: 'string', - default: '', - description: - 'Query string for searching shared drives. See the "Search for shared drives" guide for supported syntax.', - }, - { - displayName: 'Use Domain Admin Access', - name: 'useDomainAdminAccess', - type: 'boolean', - default: false, - description: - 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).', - }, - ], - }, - // ---------------------------------- - // drive:update - // ---------------------------------- - { - displayName: 'Drive ID', - name: 'driveId', - type: 'string', - default: '', - displayOptions: { - show: { - operation: ['update'], - resource: ['drive'], - }, - }, - description: 'The ID of the shared drive', - }, - { - displayName: 'Update Fields', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['update'], - resource: ['drive'], - }, - }, - options: [ - { - displayName: 'Color RGB', - name: 'colorRgb', - type: 'color', - default: '', - description: 'The color of this shared drive as an RGB hex string', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'The name of this shared drive', - }, - { - displayName: 'Restrictions', - name: 'restrictions', - type: 'collection', - placeholder: 'Add Field', - default: {}, - options: [ - { - displayName: 'Admin Managed Restrictions', - name: 'adminManagedRestrictions', - type: 'boolean', - default: false, - description: - 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', - }, - { - displayName: 'Copy Requires Writer Permission', - name: 'copyRequiresWriterPermission', - type: 'boolean', - default: false, - description: - 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', - }, - { - displayName: 'Domain Users Only', - name: 'domainUsersOnly', - type: 'boolean', - default: false, - description: - 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.', - }, - { - displayName: 'Drive Members Only', - name: 'driveMembersOnly', - type: 'boolean', - default: false, - description: - 'Whether access to items inside this shared drive is restricted to its members', - }, - ], - }, - ], - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Add Option', - default: {}, - displayOptions: { - show: { - operation: ['upload'], - resource: ['file'], - }, - }, - options: [ - { - displayName: 'APP Properties', - name: 'appPropertiesUi', - placeholder: 'Add Property', - type: 'fixedCollection', - default: {}, - typeOptions: { - multipleValues: true, - }, - description: - 'A collection of arbitrary key-value pairs which are private to the requesting app', - options: [ - { - name: 'appPropertyValues', - displayName: 'APP Property', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'string', - default: '', - description: 'Name of the key to add', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value to set for the key', - }, - ], - }, - ], - }, - { - displayName: 'Properties', - name: 'propertiesUi', - placeholder: 'Add Property', - type: 'fixedCollection', - default: {}, - typeOptions: { - multipleValues: true, - }, - description: 'A collection of arbitrary key-value pairs which are visible to all apps', - options: [ - { - name: 'propertyValues', - displayName: 'Property', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'string', - default: '', - description: 'Name of the key to add', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'Value to set for the key', - }, - ], - }, - ], - }, - ], - }, - ], - }; - - async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - - const resource = this.getNodeParameter('resource', 0) as string; - const operation = this.getNodeParameter('operation', 0) as string; - - for (let i = 0; i < items.length; i++) { - try { - const options = this.getNodeParameter('options', i, {}) as IDataObject; - - let queryFields = 'id, name'; - if (options && options.fields) { - const fields = options.fields as string[]; - if (fields.includes('*')) { - queryFields = '*'; - } else { - queryFields = fields.join(', '); - } - } - - if (resource === 'drive') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - const name = this.getNodeParameter('name', i) as string; - - const body: IDataObject = { - name, - }; - - Object.assign(body, options); - - const response = await googleApiRequest.call(this, 'POST', `/drive/v3/drives`, body, { requestId: uuid() }); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - - returnData.push(...executionData); - } - if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - const driveId = this.getNodeParameter('driveId', i) as string; - - await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray({ success: true }), - { itemData: { item: i } }, - ); - - returnData.push(...executionData); - } - if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - const driveId = this.getNodeParameter('driveId', i) as string; - - const qs: IDataObject = {}; - - Object.assign(qs, options); - - const response = await googleApiRequest.call(this, 'GET', `/drive/v3/drives/${driveId}`, {}, qs); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - - returnData.push(...executionData); - } - if (operation === 'list') { - // ---------------------------------- - // list - // ---------------------------------- - const returnAll = this.getNodeParameter('returnAll', i) as boolean; - - const qs: IDataObject = {}; - - let response: IDataObject[] = []; - - Object.assign(qs, options); - - if (returnAll === true) { - response = await googleApiRequestAllItems.call( - this, - 'drives', - 'GET', - `/drive/v3/drives`, - {}, - qs, - ); - } else { - qs.pageSize = this.getNodeParameter('limit', i) as number; - const data = await googleApiRequest.call(this, 'GET', `/drive/v3/drives`, {}, qs); - response = data.drives as IDataObject[]; - } - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - - returnData.push(...executionData); - } - if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - const driveId = this.getNodeParameter('driveId', i) as string; - - const body: IDataObject = {}; - - Object.assign(body, options); - - const response = await googleApiRequest.call(this, 'PATCH', `/drive/v3/drives/${driveId}`, body); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - - returnData.push(...executionData); - } - } - if (resource === 'file') { - if (operation === 'copy') { - // ---------------------------------- - // copy - // ---------------------------------- - - const fileId = this.getNodeParameter('fileId', i) as string; - - const body: IDataObject = { - fields: queryFields, - }; - - const optionProperties = ['name', 'parents']; - for (const propertyName of optionProperties) { - if (options[propertyName] !== undefined) { - body[propertyName] = options[propertyName]; - } - } - - const qs = { - supportsAllDrives: true, - }; - - const response = await googleApiRequest.call(this, 'POST', `/drive/v3/files/${fileId}/copy`, body, qs); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - - returnData.push(...executionData); - } else if (operation === 'download') { - // ---------------------------------- - // download - // ---------------------------------- - - const fileId = this.getNodeParameter('fileId', i) as string; - const options = this.getNodeParameter('options', i) as IDataObject; - - const requestOptions = { - resolveWithFullResponse: true, - encoding: null, - json: false, - }; - - const file = await googleApiRequest.call( - this, - 'GET', - `/drive/v3/files/${fileId}`, - {}, - { fields: 'mimeType', supportsTeamDrives: true }, - ); - let response; - - if (file.mimeType.includes('vnd.google-apps')) { - const parameterKey = 'options.googleFileConversion.conversion'; - const type = file.mimeType.split('.')[2]; - let mime; - if (type === 'document') { - mime = this.getNodeParameter( - `${parameterKey}.docsToFormat`, - i, - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - ) as string; - } else if (type === 'presentation') { - mime = this.getNodeParameter( - `${parameterKey}.slidesToFormat`, - i, - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - ) as string; - } else if (type === 'spreadsheet') { - mime = this.getNodeParameter( - `${parameterKey}.sheetsToFormat`, - i, - 'application/x-vnd.oasis.opendocument.spreadsheet', - ) as string; - } else { - mime = this.getNodeParameter( - `${parameterKey}.drawingsToFormat`, - i, - 'image/jpeg', - ) as string; - } - response = await googleApiRequest.call( - this, - 'GET', - `/drive/v3/files/${fileId}/export`, - {}, - { mimeType: mime }, - undefined, - requestOptions, - ); - } else { - response = await googleApiRequest.call( - this, - 'GET', - `/drive/v3/files/${fileId}`, - {}, - { alt: 'media' }, - undefined, - requestOptions, - ); - } - - let mimeType: string | undefined; - let fileName: string | undefined = undefined; - if (response.headers['content-type']) { - mimeType = response.headers['content-type']; - } - - if (options.fileName) { - fileName = options.fileName as string; - } - - const newItem: INodeExecutionData = { - json: items[i].json, - binary: {}, - }; - - 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. - // @ts-ignore - Object.assign(newItem.binary, items[i].binary); - } - - items[i] = newItem; - - const dataPropertyNameDownload = this.getNodeParameter( - 'binaryPropertyName', - i, - ) as string; - - const data = Buffer.from(response.body as string); - - items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData( - data as unknown as Buffer, - fileName, - mimeType, - ); - } else if (operation === 'list') { - // ---------------------------------- - // list - // ---------------------------------- - - let querySpaces = ''; - if (options.spaces) { - const spaces = options.spaces as string[]; - if (spaces.includes('*')) { - querySpaces = 'appDataFolder, drive, photos'; - } else { - querySpaces = spaces.join(', '); - } - } - - let queryCorpora = ''; - if (options.corpora) { - queryCorpora = options.corpora as string; - } - - let driveId: string | undefined; - driveId = options.driveId as string; - if (driveId === '') { - driveId = undefined; - } - - let queryString = ''; - const useQueryString = this.getNodeParameter('useQueryString', i) as boolean; - if (useQueryString === true) { - // Use the user defined query string - queryString = this.getNodeParameter('queryString', i) as string; - } else { - // Build query string out of parameters set by user - const queryFilters = this.getNodeParameter('queryFilters', i) as IDataObject; - - const queryFilterFields: string[] = []; - if (queryFilters.name) { - (queryFilters.name as IDataObject[]).forEach((nameFilter) => { - let operation = nameFilter.operation; - if (operation === 'is') { - operation = '='; - } else if (operation === 'isNot') { - operation = '!='; - } - queryFilterFields.push(`name ${operation} '${nameFilter.value}'`); - }); - - queryString += queryFilterFields.join(' or '); - } - - queryFilterFields.length = 0; - if (queryFilters.mimeType) { - (queryFilters.mimeType as IDataObject[]).forEach((mimeTypeFilter) => { - let mimeType = mimeTypeFilter.mimeType; - if (mimeTypeFilter.mimeType === 'custom') { - mimeType = mimeTypeFilter.customMimeType; - } - queryFilterFields.push(`mimeType = '${mimeType}'`); - }); - - if (queryFilterFields.length) { - if (queryString !== '') { - queryString += ' and '; - } - - queryString += queryFilterFields.join(' or '); - } - } - } - - const pageSize = this.getNodeParameter('limit', i) as number; - - const qs = { - pageSize, - orderBy: 'modifiedTime', - fields: `nextPageToken, files(${queryFields})`, - spaces: querySpaces, - q: queryString, - includeItemsFromAllDrives: queryCorpora !== '' || driveId !== '', - supportsAllDrives: queryCorpora !== '' || driveId !== '', - }; - - const response = await googleApiRequest.call(this, 'GET', `/drive/v3/files`, {}, qs); - - const files = response!.files; - - const version = this.getNode().typeVersion; - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(files), - { itemData: { item: i } }, - ); - - if (version === 1) { - return [executionData]; - } - - returnData.push(...executionData); - } else if (operation === 'upload') { - // ---------------------------------- - // upload - // ---------------------------------- - const resolveData = this.getNodeParameter('resolveData', 0) as boolean; - - let mimeType = 'text/plain'; - let body; - let originalFilename: string | undefined; - if (this.getNodeParameter('binaryData', i) === true) { - // Is binary file to upload - const item = items[i]; - - if (item.binary === undefined) { - throw new NodeOperationError(this.getNode(), 'No binary data exists on item!', { - itemIndex: i, - }); - } - - const propertyNameUpload = this.getNodeParameter('binaryPropertyName', i) as string; - - if (item.binary[propertyNameUpload] === undefined) { - throw new NodeOperationError( - this.getNode(), - `No binary data property "${propertyNameUpload}" does not exists on item!`, - { itemIndex: i }, - ); - } - - if (item.binary[propertyNameUpload].mimeType) { - mimeType = item.binary[propertyNameUpload].mimeType; - } - - if (item.binary[propertyNameUpload].fileName) { - originalFilename = item.binary[propertyNameUpload].fileName; - } - - body = await this.helpers.getBinaryDataBuffer(i, propertyNameUpload); - } else { - // Is text file - body = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8'); - } - - const name = this.getNodeParameter('name', i) as string; - const parents = this.getNodeParameter('parents', i) as string[]; - - let qs: IDataObject = { - fields: queryFields, - uploadType: 'media', - }; - - const requestOptions = { - headers: { - 'Content-Type': mimeType, - 'Content-Length': body.byteLength, - }, - encoding: null, - json: false, - }; - - let response = await googleApiRequest.call( - this, - 'POST', - `/upload/drive/v3/files`, - body, - qs, - undefined, - requestOptions, - ); - - body = { - mimeType, - name, - originalFilename, - }; - - const properties = this.getNodeParameter( - 'options.propertiesUi.propertyValues', - i, - [], - ) as IDataObject[]; - - if (properties.length) { - Object.assign(body, { - properties: properties.reduce( - (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), - {}, - ), - }); - } - - const appProperties = this.getNodeParameter( - 'options.appPropertiesUi.appPropertyValues', - i, - [], - ) as IDataObject[]; - - if (properties.length) { - Object.assign(body, { - appProperties: appProperties.reduce( - (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), - {}, - ), - }); - } - - qs = { - addParents: parents.join(','), - // When set to true shared drives can be used. - supportsAllDrives: true, - }; - - response = await googleApiRequest.call( - this, - 'PATCH', - `/drive/v3/files/${JSON.parse(response).id}`, - body, - qs, - ); - - if (resolveData === true) { - response = await googleApiRequest.call( - this, - 'GET', - `/drive/v3/files/${response.id}`, - {}, - { fields: '*' }, - ); - } - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - } else if (operation === 'update') { - // ---------------------------------- - // file:update - // ---------------------------------- - - const id = this.getNodeParameter('fileId', i) as string; - const updateFields = this.getNodeParameter('updateFields', i, {}) as IDataObject; - - const qs: IDataObject = { - supportsAllDrives: true, - }; - - Object.assign(qs, options); - - qs.fields = queryFields; - - const body: IDataObject = {}; - - if (updateFields.fileName) { - body.name = updateFields.fileName; - } - - if (updateFields.hasOwnProperty('trashed')) { - body.trashed = updateFields.trashed; - } - - if (updateFields.parentId && updateFields.parentId !== '') { - qs.addParents = updateFields.parentId; - } - - const responseData = await googleApiRequest.call( - this, - 'PATCH', - `/drive/v3/files/${id}`, - body, - qs, - ); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(responseData), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - } - } - if (resource === 'folder') { - if (operation === 'create') { - // ---------------------------------- - // folder:create - // ---------------------------------- - - const name = this.getNodeParameter('name', i) as string; - - const body = { - name, - mimeType: 'application/vnd.google-apps.folder', - parents: options.parents || [], - }; - - const qs = { - fields: queryFields, - supportsAllDrives: true, - }; - - const response = await googleApiRequest.call(this, 'POST', '/drive/v3/files', body, qs); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - } - } - if (['file', 'folder'].includes(resource)) { - if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - const fileId = this.getNodeParameter('fileId', i) as string; - - await googleApiRequest.call( - this, - 'DELETE', - `/drive/v3/files/${fileId}`, - {}, - { supportsTeamDrives: true }, - ); - - // If we are still here it did succeed - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray({ - fileId, - success: true, - }), - { itemData: { item: i } }, - ); - - returnData.push(...executionData); - } - if (operation === 'share') { - const fileId = this.getNodeParameter('fileId', i) as string; - - const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject; - - const options = this.getNodeParameter('options', i) as IDataObject; - - const body: IDataObject = {}; - - const qs: IDataObject = { - supportsTeamDrives: true, - }; - - if (permissions.permissionsValues) { - Object.assign(body, permissions.permissionsValues); - } - - Object.assign(qs, options); - - const response = await googleApiRequest.call( - this, - 'POST', - `/drive/v3/files/${fileId}/permissions`, - body, - qs, - ); - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - } - } - } catch (error) { - if (this.continueOnFail()) { - if (resource === 'file' && operation === 'download') { - items[i].json = { error: error.message }; - } else { - returnData.push({ json: {error: error.message} }); - } - continue; - } - throw error; - } - } - if (resource === 'file' && operation === 'download') { - // For file downloads the files get attached to the existing items - return this.prepareOutputData(items); - } else { - // For all other ones does the output items get replaced - return this.prepareOutputData(returnData); - } +import { INodeTypeBaseDescription, INodeVersionedType } from 'n8n-workflow'; + +import { GoogleDriveV1 } from './v1/GoogleDriveV1.node'; + +import { GoogleDriveV3 } from './v3/GoogleDriveV3.node'; + +import { NodeVersionedType } from '../../../src/NodeVersionedType'; + +export class GoogleDrive extends NodeVersionedType { + constructor() { + const baseDescription: INodeTypeBaseDescription = { + displayName: 'Google Drive', + name: 'googleDrive', + icon: 'file:googleDrive.svg', + group: ['input'], + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Access data on Google Drive', + defaultVersion: 3, + }; + + const nodeVersions: INodeVersionedType['nodeVersions'] = { + // The V1 node is actually both V1 and V2 + 1: new GoogleDriveV1(baseDescription), + 2: new GoogleDriveV1(baseDescription), + 3: new GoogleDriveV3(baseDescription), + }; + + super(nodeVersions, baseDescription); } } diff --git a/packages/nodes-base/nodes/Google/Drive/v1/GoogleDriveV1.node.ts b/packages/nodes-base/nodes/Google/Drive/v1/GoogleDriveV1.node.ts new file mode 100644 index 0000000000000..fdb47e6b302c8 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Drive/v1/GoogleDriveV1.node.ts @@ -0,0 +1,709 @@ +import { IExecuteFunctions } from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeBaseDescription, + INodeTypeDescription, + NodeOperationError, +} from 'n8n-workflow'; + +import { googleApiRequest, googleApiRequestAllItems } from '../GenericFunctions'; + +import { v4 as uuid } from 'uuid'; +import { versionDescription } from './VersionDescription'; + +export class GoogleDriveV1 implements INodeType { + description: INodeTypeDescription; + + constructor(baseDescription: INodeTypeBaseDescription) { + this.description = { + ...baseDescription, + ...versionDescription, + }; + } + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < items.length; i++) { + try { + const options = this.getNodeParameter('options', i, {}) as IDataObject; + + let queryFields = 'id, name'; + if (options && options.fields) { + const fields = options.fields as string[]; + if (fields.includes('*')) { + queryFields = '*'; + } else { + queryFields = fields.join(', '); + } + } + + if (resource === 'drive') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + const name = this.getNodeParameter('name', i) as string; + + const body: IDataObject = { + name, + }; + + Object.assign(body, options); + + const response = await googleApiRequest.call(this, 'POST', `/drive/v3/drives`, body, { + requestId: uuid(), + }); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + const driveId = this.getNodeParameter('driveId', i) as string; + + await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ success: true }), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + const driveId = this.getNodeParameter('driveId', i) as string; + + const qs: IDataObject = {}; + + Object.assign(qs, options); + + const response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/drives/${driveId}`, + {}, + qs, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'list') { + // ---------------------------------- + // list + // ---------------------------------- + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const qs: IDataObject = {}; + + let response: IDataObject[] = []; + + Object.assign(qs, options); + + if (returnAll === true) { + response = await googleApiRequestAllItems.call( + this, + 'drives', + 'GET', + `/drive/v3/drives`, + {}, + qs, + ); + } else { + qs.pageSize = this.getNodeParameter('limit', i) as number; + const data = await googleApiRequest.call(this, 'GET', `/drive/v3/drives`, {}, qs); + response = data.drives as IDataObject[]; + } + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + const driveId = this.getNodeParameter('driveId', i) as string; + + const body: IDataObject = {}; + + Object.assign(body, options); + + const response = await googleApiRequest.call( + this, + 'PATCH', + `/drive/v3/drives/${driveId}`, + body, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + } + if (resource === 'file') { + if (operation === 'copy') { + // ---------------------------------- + // copy + // ---------------------------------- + + const fileId = this.getNodeParameter('fileId', i) as string; + + const body: IDataObject = { + fields: queryFields, + }; + + const optionProperties = ['name', 'parents']; + for (const propertyName of optionProperties) { + if (options[propertyName] !== undefined) { + body[propertyName] = options[propertyName]; + } + } + + const qs = { + supportsAllDrives: true, + }; + + const response = await googleApiRequest.call( + this, + 'POST', + `/drive/v3/files/${fileId}/copy`, + body, + qs, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } else if (operation === 'download') { + // ---------------------------------- + // download + // ---------------------------------- + + const fileId = this.getNodeParameter('fileId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + const requestOptions = { + resolveWithFullResponse: true, + encoding: null, + json: false, + }; + + const file = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${fileId}`, + {}, + { fields: 'mimeType', supportsTeamDrives: true }, + ); + let response; + + if (file.mimeType.includes('vnd.google-apps')) { + const parameterKey = 'options.googleFileConversion.conversion'; + const type = file.mimeType.split('.')[2]; + let mime; + if (type === 'document') { + mime = this.getNodeParameter( + `${parameterKey}.docsToFormat`, + i, + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ) as string; + } else if (type === 'presentation') { + mime = this.getNodeParameter( + `${parameterKey}.slidesToFormat`, + i, + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ) as string; + } else if (type === 'spreadsheet') { + mime = this.getNodeParameter( + `${parameterKey}.sheetsToFormat`, + i, + 'application/x-vnd.oasis.opendocument.spreadsheet', + ) as string; + } else { + mime = this.getNodeParameter( + `${parameterKey}.drawingsToFormat`, + i, + 'image/jpeg', + ) as string; + } + response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${fileId}/export`, + {}, + { mimeType: mime }, + undefined, + requestOptions, + ); + } else { + response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${fileId}`, + {}, + { alt: 'media' }, + undefined, + requestOptions, + ); + } + + let mimeType: string | undefined; + let fileName: string | undefined = undefined; + if (response.headers['content-type']) { + mimeType = response.headers['content-type']; + } + + if (options.fileName) { + fileName = options.fileName as string; + } + + const newItem: INodeExecutionData = { + json: items[i].json, + binary: {}, + }; + + 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. + // @ts-ignore + Object.assign(newItem.binary, items[i].binary); + } + + items[i] = newItem; + + const dataPropertyNameDownload = this.getNodeParameter( + 'binaryPropertyName', + i, + ) as string; + + const data = Buffer.from(response.body as string); + + items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData( + data as unknown as Buffer, + fileName, + mimeType, + ); + } else if (operation === 'list') { + // ---------------------------------- + // list + // ---------------------------------- + + let querySpaces = ''; + if (options.spaces) { + const spaces = options.spaces as string[]; + if (spaces.includes('*')) { + querySpaces = 'appDataFolder, drive, photos'; + } else { + querySpaces = spaces.join(', '); + } + } + + let queryCorpora = ''; + if (options.corpora) { + queryCorpora = options.corpora as string; + } + + let driveId: string | undefined; + driveId = options.driveId as string; + if (driveId === '') { + driveId = undefined; + } + + let queryString = ''; + const useQueryString = this.getNodeParameter('useQueryString', i) as boolean; + if (useQueryString === true) { + // Use the user defined query string + queryString = this.getNodeParameter('queryString', i) as string; + } else { + // Build query string out of parameters set by user + const queryFilters = this.getNodeParameter('queryFilters', i) as IDataObject; + + const queryFilterFields: string[] = []; + if (queryFilters.name) { + (queryFilters.name as IDataObject[]).forEach((nameFilter) => { + let operation = nameFilter.operation; + if (operation === 'is') { + operation = '='; + } else if (operation === 'isNot') { + operation = '!='; + } + queryFilterFields.push(`name ${operation} '${nameFilter.value}'`); + }); + + queryString += queryFilterFields.join(' or '); + } + + queryFilterFields.length = 0; + if (queryFilters.mimeType) { + (queryFilters.mimeType as IDataObject[]).forEach((mimeTypeFilter) => { + let mimeType = mimeTypeFilter.mimeType; + if (mimeTypeFilter.mimeType === 'custom') { + mimeType = mimeTypeFilter.customMimeType; + } + queryFilterFields.push(`mimeType = '${mimeType}'`); + }); + + if (queryFilterFields.length) { + if (queryString !== '') { + queryString += ' and '; + } + + queryString += queryFilterFields.join(' or '); + } + } + } + + const pageSize = this.getNodeParameter('limit', i) as number; + + const qs = { + pageSize, + orderBy: 'modifiedTime', + fields: `nextPageToken, files(${queryFields})`, + spaces: querySpaces, + q: queryString, + includeItemsFromAllDrives: queryCorpora !== '' || driveId !== '', + supportsAllDrives: queryCorpora !== '' || driveId !== '', + }; + + const response = await googleApiRequest.call(this, 'GET', `/drive/v3/files`, {}, qs); + + const files = response!.files; + + const version = this.getNode().typeVersion; + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(files), + { itemData: { item: i } }, + ); + + if (version === 1) { + return [executionData]; + } + + returnData.push(...executionData); + } else if (operation === 'upload') { + // ---------------------------------- + // upload + // ---------------------------------- + const resolveData = this.getNodeParameter('resolveData', 0) as boolean; + + let mimeType = 'text/plain'; + let body; + let originalFilename: string | undefined; + if (this.getNodeParameter('binaryData', i) === true) { + // Is binary file to upload + const item = items[i]; + + if (item.binary === undefined) { + throw new NodeOperationError(this.getNode(), 'No binary data exists on item!', { + itemIndex: i, + }); + } + + const propertyNameUpload = this.getNodeParameter('binaryPropertyName', i) as string; + + if (item.binary[propertyNameUpload] === undefined) { + throw new NodeOperationError( + this.getNode(), + `No binary data property "${propertyNameUpload}" does not exists on item!`, + { itemIndex: i }, + ); + } + + if (item.binary[propertyNameUpload].mimeType) { + mimeType = item.binary[propertyNameUpload].mimeType; + } + + if (item.binary[propertyNameUpload].fileName) { + originalFilename = item.binary[propertyNameUpload].fileName; + } + + body = await this.helpers.getBinaryDataBuffer(i, propertyNameUpload); + } else { + // Is text file + body = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8'); + } + + const name = this.getNodeParameter('name', i) as string; + const parents = this.getNodeParameter('parents', i) as string[]; + + let qs: IDataObject = { + fields: queryFields, + uploadType: 'media', + }; + + const requestOptions = { + headers: { + 'Content-Type': mimeType, + 'Content-Length': body.byteLength, + }, + encoding: null, + json: false, + }; + + let response = await googleApiRequest.call( + this, + 'POST', + `/upload/drive/v3/files`, + body, + qs, + undefined, + requestOptions, + ); + + body = { + mimeType, + name, + originalFilename, + }; + + const properties = this.getNodeParameter( + 'options.propertiesUi.propertyValues', + i, + [], + ) as IDataObject[]; + + if (properties.length) { + Object.assign(body, { + properties: properties.reduce( + (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), + {}, + ), + }); + } + + const appProperties = this.getNodeParameter( + 'options.appPropertiesUi.appPropertyValues', + i, + [], + ) as IDataObject[]; + + if (properties.length) { + Object.assign(body, { + appProperties: appProperties.reduce( + (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), + {}, + ), + }); + } + + qs = { + addParents: parents.join(','), + // When set to true shared drives can be used. + supportsAllDrives: true, + }; + + response = await googleApiRequest.call( + this, + 'PATCH', + `/drive/v3/files/${JSON.parse(response).id}`, + body, + qs, + ); + + if (resolveData === true) { + response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${response.id}`, + {}, + { fields: '*' }, + ); + } + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } else if (operation === 'update') { + // ---------------------------------- + // file:update + // ---------------------------------- + + const id = this.getNodeParameter('fileId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i, {}) as IDataObject; + + const qs: IDataObject = { + supportsAllDrives: true, + }; + + Object.assign(qs, options); + + qs.fields = queryFields; + + const body: IDataObject = {}; + + if (updateFields.fileName) { + body.name = updateFields.fileName; + } + + if (updateFields.hasOwnProperty('trashed')) { + body.trashed = updateFields.trashed; + } + + if (updateFields.parentId && updateFields.parentId !== '') { + qs.addParents = updateFields.parentId; + } + + const responseData = await googleApiRequest.call( + this, + 'PATCH', + `/drive/v3/files/${id}`, + body, + qs, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } + } + if (resource === 'folder') { + if (operation === 'create') { + // ---------------------------------- + // folder:create + // ---------------------------------- + + const name = this.getNodeParameter('name', i) as string; + + const body = { + name, + mimeType: 'application/vnd.google-apps.folder', + parents: options.parents || [], + }; + + const qs = { + fields: queryFields, + supportsAllDrives: true, + }; + + const response = await googleApiRequest.call(this, 'POST', '/drive/v3/files', body, qs); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } + } + if (['file', 'folder'].includes(resource)) { + if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + const fileId = this.getNodeParameter('fileId', i) as string; + + await googleApiRequest.call( + this, + 'DELETE', + `/drive/v3/files/${fileId}`, + {}, + { supportsTeamDrives: true }, + ); + + // If we are still here it did succeed + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ + fileId, + success: true, + }), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'share') { + const fileId = this.getNodeParameter('fileId', i) as string; + + const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject; + + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IDataObject = {}; + + const qs: IDataObject = { + supportsTeamDrives: true, + }; + + if (permissions.permissionsValues) { + Object.assign(body, permissions.permissionsValues); + } + + Object.assign(qs, options); + + const response = await googleApiRequest.call( + this, + 'POST', + `/drive/v3/files/${fileId}/permissions`, + body, + qs, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } + } + } catch (error) { + if (this.continueOnFail()) { + if (resource === 'file' && operation === 'download') { + items[i].json = { error: error.message }; + } else { + returnData.push({ json: { error: error.message } }); + } + continue; + } + throw error; + } + } + if (resource === 'file' && operation === 'download') { + // For file downloads the files get attached to the existing items + return this.prepareOutputData(items); + } else { + // For all other ones does the output items get replaced + return this.prepareOutputData(returnData); + } + } +} diff --git a/packages/nodes-base/nodes/Google/Drive/v1/VersionDescription.ts b/packages/nodes-base/nodes/Google/Drive/v1/VersionDescription.ts new file mode 100644 index 0000000000000..2bd0922d58641 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Drive/v1/VersionDescription.ts @@ -0,0 +1,1915 @@ +/* eslint-disable n8n-nodes-base/node-filename-against-convention */ +import type { INodeTypeDescription } from 'n8n-workflow'; + +export const versionDescription: INodeTypeDescription = { + displayName: 'Google Drive', + name: 'googleDrive', + icon: 'file:googleDrive.svg', + group: ['input'], + version: [1, 2], + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Access data on Google Drive', + defaults: { + name: 'Google Drive', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'googleApi', + required: true, + displayOptions: { + show: { + authentication: ['serviceAccount'], + }, + }, + }, + { + name: 'googleDriveOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: ['oAuth2'], + }, + }, + }, + ], + properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Service Account', + value: 'serviceAccount', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'serviceAccount', + displayOptions: { + show: { + '@version': [1], + }, + }, + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'OAuth2 (Recommended)', + value: 'oAuth2', + }, + { + name: 'Service Account', + value: 'serviceAccount', + }, + ], + default: 'oAuth2', + displayOptions: { + show: { + '@version': [2], + }, + }, + }, + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Drive', + value: 'drive', + }, + { + name: 'File', + value: 'file', + }, + { + name: 'Folder', + value: 'folder', + }, + ], + default: 'file', + }, + + // ---------------------------------- + // operations + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['file'], + }, + }, + options: [ + { + name: 'Copy', + value: 'copy', + description: 'Copy a file', + action: 'Copy a file', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a file', + action: 'Delete a file', + }, + { + name: 'Download', + value: 'download', + description: 'Download a file', + action: 'Download a file', + }, + { + name: 'List', + value: 'list', + description: 'List files and folders', + action: 'List a file', + }, + { + name: 'Share', + value: 'share', + description: 'Share a file', + action: 'Share a file', + }, + { + name: 'Update', + value: 'update', + description: 'Update a file', + action: 'Update a file', + }, + { + name: 'Upload', + value: 'upload', + description: 'Upload a file', + action: 'Upload a file', + }, + ], + default: 'upload', + }, + + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['folder'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a folder', + action: 'Create a folder', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a folder', + action: 'Delete a folder', + }, + { + name: 'Share', + value: 'share', + description: 'Share a folder', + action: 'Share a folder', + }, + ], + default: 'create', + }, + + // ---------------------------------- + // file + // ---------------------------------- + + // ---------------------------------- + // file:copy + // ---------------------------------- + { + displayName: 'ID', + name: 'fileId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['copy'], + resource: ['file'], + }, + }, + description: 'The ID of the file to copy', + }, + + // ---------------------------------- + // file/folder:delete + // ---------------------------------- + { + displayName: 'ID', + name: 'fileId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['delete'], + resource: ['file', 'folder'], + }, + }, + description: 'The ID of the file/folder to delete', + }, + + // ---------------------------------- + // file:download + // ---------------------------------- + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['download'], + resource: ['file'], + }, + }, + description: 'The ID of the file to download', + }, + { + 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 write the data of the read file', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['download'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'Google File Conversion', + name: 'googleFileConversion', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + placeholder: 'Add Conversion', + options: [ + { + displayName: 'Conversion', + name: 'conversion', + values: [ + { + displayName: 'Google Docs', + name: 'docsToFormat', + type: 'options', + options: [ + { + name: 'To HTML', + value: 'text/html', + }, + { + name: 'To MS Word', + value: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }, + { + name: 'To OpenOffice Doc', + value: 'application/vnd.oasis.opendocument.text', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + { + name: 'To Rich Text', + value: 'application/rtf', + }, + ], + default: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + description: 'Format used to export when downloading Google Docs files', + }, + { + displayName: 'Google Drawings', + name: 'drawingsToFormat', + type: 'options', + options: [ + { + name: 'To JPEG', + value: 'image/jpeg', + }, + { + name: 'To PNG', + value: 'image/png', + }, + { + name: 'To SVG', + value: 'image/svg+xml', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + ], + default: 'image/jpeg', + description: 'Format used to export when downloading Google Drawings files', + }, + { + displayName: 'Google Slides', + name: 'slidesToFormat', + type: 'options', + options: [ + { + name: 'To MS PowerPoint', + value: + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + { + name: 'To OpenOffice Presentation', + value: 'application/vnd.oasis.opendocument.presentation', + }, + { + name: 'To Plain Text', + value: 'text/plain', + }, + ], + default: + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + description: 'Format used to export when downloading Google Slides files', + }, + { + displayName: 'Google Sheets', + name: 'sheetsToFormat', + type: 'options', + options: [ + { + name: 'To MS Excel', + value: 'application/x-vnd.oasis.opendocument.spreadsheet', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + { + name: 'To CSV', + value: 'text/csv', + }, + ], + default: 'application/x-vnd.oasis.opendocument.spreadsheet', + description: 'Format used to export when downloading Google Spreadsheets files', + }, + ], + }, + ], + }, + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + default: '', + description: 'File name. Ex: data.pdf.', + }, + ], + }, + + // ---------------------------------- + // file:list + // ---------------------------------- + { + displayName: 'Use Query String', + name: 'useQueryString', + type: 'boolean', + default: false, + displayOptions: { + show: { + operation: ['list'], + resource: ['file'], + }, + }, + description: 'Whether a query string should be used to filter results', + }, + { + displayName: 'Query String', + name: 'queryString', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['list'], + useQueryString: [true], + resource: ['file'], + }, + }, + placeholder: "name contains 'invoice'", + description: 'Query to use to return only specific files', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: ['list'], + resource: ['file'], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + default: 50, + description: 'Max number of results to return', + }, + { + displayName: 'Filters', + name: 'queryFilters', + placeholder: 'Add Filter', + description: 'Filters to use to return only specific files', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + operation: ['list'], + useQueryString: [false], + resource: ['file'], + }, + }, + options: [ + { + name: 'name', + displayName: 'Name', + values: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Contains', + value: 'contains', + }, + { + name: 'Is', + value: 'is', + }, + { + name: 'Is Not', + value: 'isNot', + }, + ], + default: 'contains', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'The value for operation', + }, + ], + }, + { + name: 'mimeType', + displayName: 'Mime Type', + values: [ + { + displayName: 'Mime Type', + name: 'mimeType', + type: 'options', + options: [ + { + name: '3rd Party Shortcut', + value: 'application/vnd.google-apps.drive-sdk', + }, + { + name: 'Audio', + value: 'application/vnd.google-apps.audio', + }, + { + name: 'Custom Mime Type', + value: 'custom', + }, + { + name: 'Google Apps Scripts', + value: 'application/vnd.google-apps.script', + }, + { + name: 'Google Docs', + value: 'application/vnd.google-apps.document', + }, + { + name: 'Google Drawing', + value: 'application/vnd.google-apps.drawing', + }, + { + name: 'Google Drive File', + value: 'application/vnd.google-apps.file', + }, + { + name: 'Google Drive Folder', + value: 'application/vnd.google-apps.folder', + }, + { + name: 'Google Forms', + value: 'application/vnd.google-apps.form', + }, + { + name: 'Google Fusion Tables', + value: 'application/vnd.google-apps.fusiontable', + }, + { + name: 'Google My Maps', + value: 'application/vnd.google-apps.map', + }, + { + name: 'Google Sheets', + value: 'application/vnd.google-apps.spreadsheet', + }, + { + name: 'Google Sites', + value: 'application/vnd.google-apps.site', + }, + { + name: 'Google Slides', + value: 'application/vnd.google-apps.presentation', + }, + { + name: 'Photo', + value: 'application/vnd.google-apps.photo', + }, + { + name: 'Unknown', + value: 'application/vnd.google-apps.unknown', + }, + { + name: 'Video', + value: 'application/vnd.google-apps.video', + }, + ], + default: 'application/vnd.google-apps.file', + description: 'The Mime-Type of the files to return', + }, + { + displayName: 'Custom Mime Type', + name: 'customMimeType', + type: 'string', + default: '', + displayOptions: { + show: { + mimeType: ['custom'], + }, + }, + }, + ], + }, + ], + }, + + // ---------------------------------- + // file:share + // ---------------------------------- + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['share'], + resource: ['file', 'folder'], + }, + }, + description: 'The ID of the file or shared drive', + }, + { + displayName: 'Permissions', + name: 'permissionsUi', + placeholder: 'Add Permission', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: ['file', 'folder'], + operation: ['share'], + }, + }, + options: [ + { + displayName: 'Permission', + name: 'permissionsValues', + values: [ + { + displayName: 'Role', + name: 'role', + type: 'options', + options: [ + { + name: 'Commenter', + value: 'commenter', + }, + { + name: 'File Organizer', + value: 'fileOrganizer', + }, + { + name: 'Organizer', + value: 'organizer', + }, + { + name: 'Owner', + value: 'owner', + }, + { + name: 'Reader', + value: 'reader', + }, + { + name: 'Writer', + value: 'writer', + }, + ], + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'User', + value: 'user', + }, + { + name: 'Group', + value: 'group', + }, + { + name: 'Domain', + value: 'domain', + }, + { + name: 'Anyone', + value: 'anyone', + }, + ], + default: '', + description: + 'Information about the different types can be found here', + }, + { + displayName: 'Email Address', + name: 'emailAddress', + type: 'string', + displayOptions: { + show: { + type: ['user', 'group'], + }, + }, + default: '', + description: 'The email address of the user or group to which this permission refers', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { + show: { + type: ['domain'], + }, + }, + default: '', + description: 'The domain to which this permission refers', + }, + { + displayName: 'Allow File Discovery', + name: 'allowFileDiscovery', + type: 'boolean', + displayOptions: { + show: { + type: ['domain', 'anyone'], + }, + }, + default: false, + description: 'Whether the permission allows the file to be discovered through search', + }, + ], + }, + ], + }, + + { + displayName: 'Binary Data', + name: 'binaryData', + type: 'boolean', + default: false, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + description: 'Whether the data to upload should be taken from binary field', + }, + { + displayName: 'File Content', + name: 'fileContent', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + binaryData: [false], + }, + }, + placeholder: '', + description: 'The text content of the file to upload', + }, + { + displayName: 'Binary Property', + name: 'binaryPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + binaryData: [true], + }, + }, + placeholder: '', + description: + 'Name of the binary property which contains the data for the file to be uploaded', + }, + + // ---------------------------------- + // file:update + // ---------------------------------- + { + displayName: 'ID', + name: 'fileId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['update'], + resource: ['file'], + }, + }, + description: 'The ID of the file to update', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['update'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + default: '', + description: 'The name of the file', + }, + { + displayName: 'Keep Revision Forever', + name: 'keepRevisionForever', + type: 'boolean', + default: false, + description: + "Whether to set the 'keepForever' field in the new head revision. This is only applicable to files with binary content in Google Drive. Only 200 revisions for the file can be kept forever. If the limit is reached, try deleting pinned revisions.", + }, + { + displayName: 'Move to Trash', + name: 'trashed', + type: 'boolean', + default: false, + description: 'Whether to move a file to the trash. Only the owner may trash a file.', + }, + { + displayName: 'OCR Language', + name: 'ocrLanguage', + type: 'string', + default: '', + description: 'A language hint for OCR processing during image import (ISO 639-1 code)', + }, + { + displayName: 'Parent ID', + name: 'parentId', + type: 'string', + default: '', + description: 'The ID of the parent to set', + }, + { + displayName: 'Use Content As Indexable Text', + name: 'useContentAsIndexableText', + type: 'boolean', + default: false, + description: 'Whether to use the uploaded content as indexable text', + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['update'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'multiOptions', + options: [ + { + name: '[All]', + value: '*', + description: 'All fields', + }, + { + name: 'explicitlyTrashed', + value: 'explicitlyTrashed', + }, + { + name: 'exportLinks', + value: 'exportLinks', + }, + { + name: 'hasThumbnail', + value: 'hasThumbnail', + }, + { + name: 'iconLink', + value: 'iconLink', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Kind', + value: 'kind', + }, + { + name: 'mimeType', + value: 'mimeType', + }, + { + name: 'Name', + value: 'name', + }, + { + name: 'Permissions', + value: 'permissions', + }, + { + name: 'Shared', + value: 'shared', + }, + { + name: 'Spaces', + value: 'spaces', + }, + { + name: 'Starred', + value: 'starred', + }, + { + name: 'thumbnailLink', + value: 'thumbnailLink', + }, + { + name: 'Trashed', + value: 'trashed', + }, + { + name: 'Version', + value: 'version', + }, + { + name: 'webViewLink', + value: 'webViewLink', + }, + ], + default: [], + description: 'The fields to return', + }, + ], + }, + // ---------------------------------- + // file:upload + // ---------------------------------- + { + displayName: 'File Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + placeholder: 'invoice_1.pdf', + description: 'The name the file should be saved as', + }, + // ---------------------------------- + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: false, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether + description: + 'By default the response only contain the ID of the file. If this option gets activated, it will resolve the data automatically.', + }, + { + displayName: 'Parents', + name: 'parents', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: [], + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + description: 'The IDs of the parent folders which contain the file', + }, + + // ---------------------------------- + // folder + // ---------------------------------- + + // ---------------------------------- + // folder:create + // ---------------------------------- + { + displayName: 'Folder', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['create'], + resource: ['folder'], + }, + }, + placeholder: 'invoices', + description: 'The name of folder to create', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + '/operation': ['copy', 'list', 'share', 'create'], + '/resource': ['file', 'folder'], + }, + }, + options: [ + { + displayName: 'Email Message', + name: 'emailMessage', + type: 'string', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: '', + description: 'A plain text custom message to include in the notification email', + }, + { + displayName: 'Enforce Single Parent', + name: 'enforceSingleParent', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether to opt in to API behavior that aims for all items to have exactly one parent. This parameter only takes effect if the item is not in a shared drive.', + }, + { + displayName: 'Fields', + name: 'fields', + type: 'multiOptions', + displayOptions: { + show: { + '/operation': ['list', 'copy'], + }, + }, + options: [ + { + name: '*', + value: '*', + description: 'All fields', + }, + { + name: 'explicitlyTrashed', + value: 'explicitlyTrashed', + }, + { + name: 'exportLinks', + value: 'exportLinks', + }, + { + name: 'hasThumbnail', + value: 'hasThumbnail', + }, + { + name: 'iconLink', + value: 'iconLink', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Kind', + value: 'kind', + }, + { + name: 'mimeType', + value: 'mimeType', + }, + { + name: 'Name', + value: 'name', + }, + { + name: 'Permissions', + value: 'permissions', + }, + { + name: 'Shared', + value: 'shared', + }, + { + name: 'Spaces', + value: 'spaces', + }, + { + name: 'Starred', + value: 'starred', + }, + { + name: 'thumbnailLink', + value: 'thumbnailLink', + }, + { + name: 'Trashed', + value: 'trashed', + }, + { + name: 'Version', + value: 'version', + }, + { + name: 'webViewLink', + value: 'webViewLink', + }, + ], + default: [], + description: 'The fields to return', + }, + { + displayName: 'Move To New Owners Root', + name: 'moveToNewOwnersRoot', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether + description: + "

This parameter only takes effect if the item is not in a shared drive and the request is attempting to transfer the ownership of the item.

When set to true, the item is moved to the new owner's My Drive root folder and all prior parents removed.

", + }, + { + displayName: 'Send Notification Email', + name: 'sendNotificationEmail', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: 'Whether to send a notification email when sharing to users or groups', + }, + { + displayName: 'Supports All Drives', + name: 'supportsAllDrives', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether the requesting application supports both My Drives and shared drives', + }, + { + displayName: 'Transfer Ownership', + name: 'transferOwnership', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether to transfer ownership to the specified user and downgrade the current owner to a writer', + }, + { + displayName: 'Use Domain Admin Access', + name: 'useDomainAdminAccess', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether to perform the operation as domain administrator, i.e. if you are an administrator of the domain to which the shared drive belongs, you will be granted access automatically.', + }, + + { + displayName: 'File Name', + name: 'name', + type: 'string', + displayOptions: { + show: { + '/operation': ['copy'], + '/resource': ['file'], + }, + }, + default: '', + placeholder: 'invoice_1.pdf', + description: 'The name the file should be saved as', + }, + { + displayName: 'Parents', + name: 'parents', + type: 'string', + displayOptions: { + show: { + '/operation': ['copy', 'create'], + '/resource': ['file', 'folder'], + }, + }, + typeOptions: { + multipleValues: true, + }, + default: [], + description: 'The IDs of the parent folders the file/folder should be saved in', + }, + { + displayName: 'Spaces', + name: 'spaces', + type: 'multiOptions', + displayOptions: { + show: { + '/operation': ['list'], + '/resource': ['file'], + }, + }, + options: [ + { + name: '[All]', + value: '*', + description: 'All spaces', + }, + { + name: 'appDataFolder', + value: 'appDataFolder', + }, + { + name: 'Drive', + value: 'drive', + }, + { + name: 'Photos', + value: 'photos', + }, + ], + default: [], + description: 'The spaces to operate on', + }, + { + displayName: 'Corpora', + name: 'corpora', + type: 'options', + displayOptions: { + show: { + '/operation': ['list'], + '/resource': ['file'], + }, + }, + options: [ + { + name: 'User', + value: 'user', + description: 'All files in "My Drive" and "Shared with me"', + }, + { + name: 'Domain', + value: 'domain', + description: "All files shared to the user's domain that are searchable", + }, + { + name: 'Drive', + value: 'drive', + description: 'All files contained in a single shared drive', + }, + { + name: 'allDrives', + value: 'allDrives', + description: 'All drives', + }, + ], + default: '', + description: 'The corpora to operate on', + }, + { + displayName: 'Drive ID', + name: 'driveId', + type: 'string', + default: '', + displayOptions: { + show: { + '/operation': ['list'], + '/resource': ['file'], + corpora: ['drive'], + }, + }, + description: + 'ID of the shared drive to search. The driveId parameter must be specified if and only if corpora is set to drive.', + }, + ], + }, + // ---------------------------------- + // drive + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['drive'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a drive', + action: 'Create a drive', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a drive', + action: 'Delete a drive', + }, + { + name: 'Get', + value: 'get', + description: 'Get a drive', + action: 'Get a drive', + }, + { + name: 'List', + value: 'list', + description: 'List all drives', + action: 'List all drives', + }, + { + name: 'Update', + value: 'update', + description: 'Update a drive', + action: 'Update a drive', + }, + ], + default: 'create', + }, + // ---------------------------------- + // drive:create + // ---------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['create'], + resource: ['drive'], + }, + }, + description: 'The name of this shared drive', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['create'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Capabilities', + name: 'capabilities', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Can Add Children', + name: 'canAddChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can add children to folders in this shared drive', + }, + { + displayName: 'Can Change Copy Requires Writer Permission Restriction', + name: 'canChangeCopyRequiresWriterPermissionRestriction', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the copyRequiresWriterPermission restriction of this shared drive', + }, + { + displayName: 'Can Change Domain Users Only Restriction', + name: 'canChangeDomainUsersOnlyRestriction', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the domainUsersOnly restriction of this shared drive', + }, + { + displayName: 'Can Change Drive Background', + name: 'canChangeDriveBackground', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the background of this shared drive', + }, + { + displayName: 'Can Change Drive Members Only Restriction', + name: 'canChangeDriveMembersOnlyRestriction', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the driveMembersOnly restriction of this shared drive', + }, + { + displayName: 'Can Comment', + name: 'canComment', + type: 'boolean', + default: false, + description: 'Whether the current user can comment on files in this shared drive', + }, + { + displayName: 'Can Copy', + name: 'canCopy', + type: 'boolean', + default: false, + description: 'Whether the current user can copy files in this shared drive', + }, + { + displayName: 'Can Delete Children', + name: 'canDeleteChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can delete children from folders in this shared drive', + }, + { + displayName: 'Can Delete Drive', + name: 'canDeleteDrive', + type: 'boolean', + default: false, + description: + 'Whether the current user can delete this shared drive. Attempting to delete the shared drive may still fail if there are untrashed items inside the shared drive.', + }, + { + displayName: 'Can Download', + name: 'canDownload', + type: 'boolean', + default: false, + description: 'Whether the current user can download files in this shared drive', + }, + { + displayName: 'Can Edit', + name: 'canEdit', + type: 'boolean', + default: false, + description: 'Whether the current user can edit files in this shared drive', + }, + { + displayName: 'Can List Children', + name: 'canListChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can list the children of folders in this shared drive', + }, + { + displayName: 'Can Manage Members', + name: 'canManageMembers', + type: 'boolean', + default: false, + description: + 'Whether the current user can add members to this shared drive or remove them or change their role', + }, + { + displayName: 'Can Read Revisions', + name: 'canReadRevisions', + type: 'boolean', + default: false, + description: + 'Whether the current user can read the revisions resource of files in this shared drive', + }, + { + displayName: 'Can Rename', + name: 'canRename', + type: 'boolean', + default: false, + description: + 'Whether the current user can rename files or folders in this shared drive', + }, + { + displayName: 'Can Rename Drive', + name: 'canRenameDrive', + type: 'boolean', + default: false, + description: 'Whether the current user can rename this shared drive', + }, + { + displayName: 'Can Share', + name: 'canShare', + type: 'boolean', + default: false, + description: 'Whether the current user can rename this shared drive', + }, + { + displayName: 'Can Trash Children', + name: 'canTrashChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can trash children from folders in this shared drive', + }, + ], + }, + { + displayName: 'Color RGB', + name: 'colorRgb', + type: 'color', + default: '', + description: 'The color of this shared drive as an RGB hex string', + }, + { + displayName: 'Created Time', + name: 'createdTime', + type: 'dateTime', + default: '', + description: 'The time at which the shared drive was created (RFC 3339 date-time)', + }, + { + displayName: 'Hidden', + name: 'hidden', + type: 'boolean', + default: false, + description: 'Whether the shared drive is hidden from default view', + }, + { + displayName: 'Restrictions', + name: 'restrictions', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Admin Managed Restrictions', + name: 'adminManagedRestrictions', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Copy Requires Writer Permission', + name: 'copyRequiresWriterPermission', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Domain Users Only', + name: 'domainUsersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.', + }, + { + displayName: 'Drive Members Only', + name: 'driveMembersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to items inside this shared drive is restricted to its members', + }, + ], + }, + ], + }, + // ---------------------------------- + // drive:delete + // ---------------------------------- + { + displayName: 'Drive ID', + name: 'driveId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['delete'], + resource: ['drive'], + }, + }, + description: 'The ID of the shared drive', + }, + // ---------------------------------- + // drive:get + // ---------------------------------- + { + displayName: 'Drive ID', + name: 'driveId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['get'], + resource: ['drive'], + }, + }, + description: 'The ID of the shared drive', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['get'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Use Domain Admin Access', + name: 'useDomainAdminAccess', + type: 'boolean', + default: false, + description: + 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).', + }, + ], + }, + // ---------------------------------- + // drive:list + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: ['list'], + resource: ['drive'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: ['list'], + resource: ['drive'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 200, + }, + default: 100, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['list'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Query', + name: 'q', + type: 'string', + default: '', + description: + 'Query string for searching shared drives. See the "Search for shared drives" guide for supported syntax.', + }, + { + displayName: 'Use Domain Admin Access', + name: 'useDomainAdminAccess', + type: 'boolean', + default: false, + description: + 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).', + }, + ], + }, + // ---------------------------------- + // drive:update + // ---------------------------------- + { + displayName: 'Drive ID', + name: 'driveId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['update'], + resource: ['drive'], + }, + }, + description: 'The ID of the shared drive', + }, + { + displayName: 'Update Fields', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['update'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Color RGB', + name: 'colorRgb', + type: 'color', + default: '', + description: 'The color of this shared drive as an RGB hex string', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of this shared drive', + }, + { + displayName: 'Restrictions', + name: 'restrictions', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Admin Managed Restrictions', + name: 'adminManagedRestrictions', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Copy Requires Writer Permission', + name: 'copyRequiresWriterPermission', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Domain Users Only', + name: 'domainUsersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.', + }, + { + displayName: 'Drive Members Only', + name: 'driveMembersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to items inside this shared drive is restricted to its members', + }, + ], + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'APP Properties', + name: 'appPropertiesUi', + placeholder: 'Add Property', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + description: + 'A collection of arbitrary key-value pairs which are private to the requesting app', + options: [ + { + name: 'appPropertyValues', + displayName: 'APP Property', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Name of the key to add', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value to set for the key', + }, + ], + }, + ], + }, + { + displayName: 'Properties', + name: 'propertiesUi', + placeholder: 'Add Property', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + description: 'A collection of arbitrary key-value pairs which are visible to all apps', + options: [ + { + name: 'propertyValues', + displayName: 'Property', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Name of the key to add', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value to set for the key', + }, + ], + }, + ], + }, + ], + }, + ], +}; diff --git a/packages/nodes-base/nodes/Google/Drive/v3/GoogleDriveV3.node.ts b/packages/nodes-base/nodes/Google/Drive/v3/GoogleDriveV3.node.ts new file mode 100644 index 0000000000000..e21fee367dbb4 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Drive/v3/GoogleDriveV3.node.ts @@ -0,0 +1,807 @@ +import { IExecuteFunctions } from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodeListSearchResult, + INodePropertyOptions, + INodeType, + INodeTypeBaseDescription, + INodeTypeDescription, + NodeOperationError, +} from 'n8n-workflow'; + +import { googleApiRequest, googleApiRequestAllItems } from '../GenericFunctions'; + +import { v4 as uuid } from 'uuid'; +import { versionDescription } from './VersionDescription'; + +interface GoogleDriveFilesItem { + id: string; + name: string; + mimeType: string; + webViewLink: string; +} + +interface GoogleDriveDriveItem { + id: string; + name: string; +} + +export class GoogleDriveV3 implements INodeType { + description: INodeTypeDescription; + + constructor(baseDescription: INodeTypeBaseDescription) { + this.description = { + ...baseDescription, + ...versionDescription, + }; + } + + methods = { + listSearch: { + async fileSearch( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, + ): Promise { + const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, { + q: filter ? `name contains '${filter.replace("'", "\\'")}'` : undefined, + pageToken: paginationToken as string | undefined, + fields: 'nextPageToken,files(id,name,mimeType,webViewLink)', + orderBy: 'name_natural', + }); + return { + results: res.files.map((i: GoogleDriveFilesItem) => ({ + name: i.name, + value: i.id, + url: i.webViewLink, + })), + paginationToken: res.nextPageToken, + }; + }, + async folderSearch( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, + ): Promise { + const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, { + q: filter + ? `name contains '${filter.replace( + "'", + "\\'", + // tslint:disable-next-line: indent + )}' and mimeType = 'application/vnd.google-apps.folder'` + : undefined, + pageToken: paginationToken as string | undefined, + fields: 'nextPageToken,files(id,name,mimeType,webViewLink)', + orderBy: 'name_natural', + }); + return { + results: res.files.map((i: GoogleDriveFilesItem) => ({ + name: i.name, + value: i.id, + url: i.webViewLink, + })), + paginationToken: res.nextPageToken, + }; + }, + async driveSearch( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, + ): Promise { + const res = await googleApiRequest.call(this, 'GET', '/drive/v3/drives', undefined, { + q: filter ? `name contains '${filter.replace("'", "\\'")}'` : undefined, + pageToken: paginationToken as string | undefined, + }); + return { + results: res.drives.map((i: GoogleDriveDriveItem) => ({ + name: i.name, + value: i.id, + })), + paginationToken: res.nextPageToken, + }; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < items.length; i++) { + try { + const options = this.getNodeParameter('options', i, {}) as IDataObject; + + let queryFields = 'id, name'; + if (options && options.fields) { + const fields = options.fields as string[]; + if (fields.includes('*')) { + queryFields = '*'; + } else { + queryFields = fields.join(', '); + } + } + + if (resource === 'drive') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + const name = this.getNodeParameter('name', i) as string; + + const body: IDataObject = { + name, + }; + + Object.assign(body, options); + + const response = await googleApiRequest.call(this, 'POST', `/drive/v3/drives`, body, { + requestId: uuid(), + }); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + const driveId = this.getNodeParameter('driveId', i, undefined, { + extractValue: true, + }) as string; + + await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ success: true }), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + const driveId = this.getNodeParameter('driveId', i, undefined, { + extractValue: true, + }) as string; + + const qs: IDataObject = {}; + + Object.assign(qs, options); + + const response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/drives/${driveId}`, + {}, + qs, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'list') { + // ---------------------------------- + // list + // ---------------------------------- + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const qs: IDataObject = {}; + + let response: IDataObject[] = []; + + Object.assign(qs, options); + + if (returnAll === true) { + response = await googleApiRequestAllItems.call( + this, + 'drives', + 'GET', + `/drive/v3/drives`, + {}, + qs, + ); + } else { + qs.pageSize = this.getNodeParameter('limit', i) as number; + const data = await googleApiRequest.call(this, 'GET', `/drive/v3/drives`, {}, qs); + response = data.drives as IDataObject[]; + } + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + const driveId = this.getNodeParameter('driveId', i, undefined, { + extractValue: true, + }) as string; + + const body: IDataObject = {}; + + Object.assign(body, options); + + const response = await googleApiRequest.call( + this, + 'PATCH', + `/drive/v3/drives/${driveId}`, + body, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + } + if (resource === 'file') { + if (operation === 'copy') { + // ---------------------------------- + // copy + // ---------------------------------- + + const fileId = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; + + const body: IDataObject = { + fields: queryFields, + }; + + const optionProperties = ['name', 'parents']; + for (const propertyName of optionProperties) { + if (options[propertyName] !== undefined) { + body[propertyName] = options[propertyName]; + } + } + + const qs = { + supportsAllDrives: true, + }; + + const response = await googleApiRequest.call( + this, + 'POST', + `/drive/v3/files/${fileId}/copy`, + body, + qs, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } else if (operation === 'download') { + // ---------------------------------- + // download + // ---------------------------------- + + const fileId = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + const requestOptions = { + resolveWithFullResponse: true, + encoding: null, + json: false, + }; + + const file = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${fileId}`, + {}, + { fields: 'mimeType', supportsTeamDrives: true }, + ); + let response; + + if (file.mimeType.includes('vnd.google-apps')) { + const parameterKey = 'options.googleFileConversion.conversion'; + const type = file.mimeType.split('.')[2]; + let mime; + if (type === 'document') { + mime = this.getNodeParameter( + `${parameterKey}.docsToFormat`, + i, + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ) as string; + } else if (type === 'presentation') { + mime = this.getNodeParameter( + `${parameterKey}.slidesToFormat`, + i, + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ) as string; + } else if (type === 'spreadsheet') { + mime = this.getNodeParameter( + `${parameterKey}.sheetsToFormat`, + i, + 'application/x-vnd.oasis.opendocument.spreadsheet', + ) as string; + } else { + mime = this.getNodeParameter( + `${parameterKey}.drawingsToFormat`, + i, + 'image/jpeg', + ) as string; + } + response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${fileId}/export`, + {}, + { mimeType: mime }, + undefined, + requestOptions, + ); + } else { + response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${fileId}`, + {}, + { alt: 'media' }, + undefined, + requestOptions, + ); + } + + let mimeType: string | undefined; + let fileName: string | undefined = undefined; + if (response.headers['content-type']) { + mimeType = response.headers['content-type']; + } + + if (options.fileName) { + fileName = options.fileName as string; + } + + const newItem: INodeExecutionData = { + json: items[i].json, + binary: {}, + }; + + 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. + // @ts-ignore + Object.assign(newItem.binary, items[i].binary); + } + + items[i] = newItem; + + const dataPropertyNameDownload = this.getNodeParameter( + 'binaryPropertyName', + i, + ) as string; + + const data = Buffer.from(response.body as string); + + items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData( + data as unknown as Buffer, + fileName, + mimeType, + ); + } else if (operation === 'list') { + // ---------------------------------- + // list + // ---------------------------------- + + let querySpaces = ''; + if (options.spaces) { + const spaces = options.spaces as string[]; + if (spaces.includes('*')) { + querySpaces = 'appDataFolder, drive, photos'; + } else { + querySpaces = spaces.join(', '); + } + } + + let queryCorpora = ''; + if (options.corpora) { + queryCorpora = options.corpora as string; + } + + let driveId: string | undefined; + driveId = options.driveId as string; + if (driveId === '') { + driveId = undefined; + } + + let queryString = ''; + const useQueryString = this.getNodeParameter('useQueryString', i) as boolean; + if (useQueryString === true) { + // Use the user defined query string + queryString = this.getNodeParameter('queryString', i) as string; + } else { + // Build query string out of parameters set by user + const queryFilters = this.getNodeParameter('queryFilters', i) as IDataObject; + + const queryFilterFields: string[] = []; + if (queryFilters.name) { + (queryFilters.name as IDataObject[]).forEach((nameFilter) => { + let operation = nameFilter.operation; + if (operation === 'is') { + operation = '='; + } else if (operation === 'isNot') { + operation = '!='; + } + queryFilterFields.push(`name ${operation} '${nameFilter.value}'`); + }); + + queryString += queryFilterFields.join(' or '); + } + + queryFilterFields.length = 0; + if (queryFilters.mimeType) { + (queryFilters.mimeType as IDataObject[]).forEach((mimeTypeFilter) => { + let mimeType = mimeTypeFilter.mimeType; + if (mimeTypeFilter.mimeType === 'custom') { + mimeType = mimeTypeFilter.customMimeType; + } + queryFilterFields.push(`mimeType = '${mimeType}'`); + }); + + if (queryFilterFields.length) { + if (queryString !== '') { + queryString += ' and '; + } + + queryString += queryFilterFields.join(' or '); + } + } + } + + const pageSize = this.getNodeParameter('limit', i) as number; + + const qs = { + pageSize, + orderBy: 'modifiedTime', + fields: `nextPageToken, files(${queryFields})`, + spaces: querySpaces, + q: queryString, + includeItemsFromAllDrives: queryCorpora !== '' || driveId !== '', + supportsAllDrives: queryCorpora !== '' || driveId !== '', + }; + + const response = await googleApiRequest.call(this, 'GET', `/drive/v3/files`, {}, qs); + + const files = response!.files; + + const version = this.getNode().typeVersion; + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(files), + { itemData: { item: i } }, + ); + + if (version === 1) { + return [executionData]; + } + + returnData.push(...executionData); + } else if (operation === 'upload') { + // ---------------------------------- + // upload + // ---------------------------------- + const resolveData = this.getNodeParameter('resolveData', 0) as boolean; + + let mimeType = 'text/plain'; + let body; + let originalFilename: string | undefined; + if (this.getNodeParameter('binaryData', i) === true) { + // Is binary file to upload + const item = items[i]; + + if (item.binary === undefined) { + throw new NodeOperationError(this.getNode(), 'No binary data exists on item!', { + itemIndex: i, + }); + } + + const propertyNameUpload = this.getNodeParameter('binaryPropertyName', i) as string; + + if (item.binary[propertyNameUpload] === undefined) { + throw new NodeOperationError( + this.getNode(), + `No binary data property "${propertyNameUpload}" does not exists on item!`, + { itemIndex: i }, + ); + } + + if (item.binary[propertyNameUpload].mimeType) { + mimeType = item.binary[propertyNameUpload].mimeType; + } + + if (item.binary[propertyNameUpload].fileName) { + originalFilename = item.binary[propertyNameUpload].fileName; + } + + body = await this.helpers.getBinaryDataBuffer(i, propertyNameUpload); + } else { + // Is text file + body = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8'); + } + + const name = this.getNodeParameter('name', i) as string; + const parents = this.getNodeParameter('parents', i) as string[]; + + let qs: IDataObject = { + fields: queryFields, + uploadType: 'media', + }; + + const requestOptions = { + headers: { + 'Content-Type': mimeType, + 'Content-Length': body.byteLength, + }, + encoding: null, + json: false, + }; + + let response = await googleApiRequest.call( + this, + 'POST', + `/upload/drive/v3/files`, + body, + qs, + undefined, + requestOptions, + ); + + body = { + mimeType, + name, + originalFilename, + }; + + const properties = this.getNodeParameter( + 'options.propertiesUi.propertyValues', + i, + [], + ) as IDataObject[]; + + if (properties.length) { + Object.assign(body, { + properties: properties.reduce( + (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), + {}, + ), + }); + } + + const appProperties = this.getNodeParameter( + 'options.appPropertiesUi.appPropertyValues', + i, + [], + ) as IDataObject[]; + + if (properties.length) { + Object.assign(body, { + appProperties: appProperties.reduce( + (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), + {}, + ), + }); + } + + qs = { + addParents: parents.join(','), + // When set to true shared drives can be used. + supportsAllDrives: true, + }; + + response = await googleApiRequest.call( + this, + 'PATCH', + `/drive/v3/files/${JSON.parse(response).id}`, + body, + qs, + ); + + if (resolveData === true) { + response = await googleApiRequest.call( + this, + 'GET', + `/drive/v3/files/${response.id}`, + {}, + { fields: '*' }, + ); + } + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } else if (operation === 'update') { + // ---------------------------------- + // file:update + // ---------------------------------- + + const id = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; + const updateFields = this.getNodeParameter('updateFields', i, {}) as IDataObject; + + const qs: IDataObject = { + supportsAllDrives: true, + }; + + Object.assign(qs, options); + + qs.fields = queryFields; + + const body: IDataObject = {}; + + if (updateFields.fileName) { + body.name = updateFields.fileName; + } + + if (updateFields.hasOwnProperty('trashed')) { + body.trashed = updateFields.trashed; + } + + if (updateFields.parentId && updateFields.parentId !== '') { + qs.addParents = updateFields.parentId; + } + + const responseData = await googleApiRequest.call( + this, + 'PATCH', + `/drive/v3/files/${id}`, + body, + qs, + ); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } + } + if (resource === 'folder') { + if (operation === 'create') { + // ---------------------------------- + // folder:create + // ---------------------------------- + + const name = this.getNodeParameter('name', i) as string; + + const body = { + name, + mimeType: 'application/vnd.google-apps.folder', + parents: options.parents || [], + }; + + const qs = { + fields: queryFields, + supportsAllDrives: true, + }; + + const response = await googleApiRequest.call(this, 'POST', '/drive/v3/files', body, qs); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } + } + if (['file', 'folder'].includes(resource)) { + if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + const fileId = this.getNodeParameter('fileOrFolderId', i, undefined, { + extractValue: true, + }) as string; + + await googleApiRequest.call( + this, + 'DELETE', + `/drive/v3/files/${fileId}`, + {}, + { supportsTeamDrives: true }, + ); + + // If we are still here it did succeed + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ + fileId, + success: true, + }), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); + } + if (operation === 'share') { + const fileId = this.getNodeParameter('fileOrFolderId', i, undefined, { + extractValue: true, + }) as string; + + const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject; + + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IDataObject = {}; + + const qs: IDataObject = { + supportsTeamDrives: true, + }; + + if (permissions.permissionsValues) { + Object.assign(body, permissions.permissionsValues); + } + + Object.assign(qs, options); + + const response = await googleApiRequest.call( + this, + 'POST', + `/drive/v3/files/${fileId}/permissions`, + body, + qs, + ); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(response), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } + } + } catch (error) { + if (this.continueOnFail()) { + if (resource === 'file' && operation === 'download') { + items[i].json = { error: error.message }; + } else { + returnData.push({ json: { error: error.message } }); + } + continue; + } + throw error; + } + } + if (resource === 'file' && operation === 'download') { + // For file downloads the files get attached to the existing items + return this.prepareOutputData(items); + } else { + // For all other ones does the output items get replaced + return this.prepareOutputData(returnData); + } + } +} diff --git a/packages/nodes-base/nodes/Google/Drive/v3/VersionDescription.ts b/packages/nodes-base/nodes/Google/Drive/v3/VersionDescription.ts new file mode 100644 index 0000000000000..04bd58e3f182f --- /dev/null +++ b/packages/nodes-base/nodes/Google/Drive/v3/VersionDescription.ts @@ -0,0 +1,1948 @@ +/* eslint-disable n8n-nodes-base/node-filename-against-convention */ +import type { INodeTypeDescription } from 'n8n-workflow'; + +export const versionDescription: INodeTypeDescription = { + displayName: 'Google Drive', + name: 'googleDrive', + icon: 'file:googleDrive.svg', + group: ['input'], + version: 3, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Access data on Google Drive', + defaults: { + name: 'Google Drive', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'googleApi', + required: true, + displayOptions: { + show: { + authentication: ['serviceAccount'], + }, + }, + }, + { + name: 'googleDriveOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: ['oAuth2'], + }, + }, + }, + ], + properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Service Account', + value: 'serviceAccount', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'serviceAccount', + displayOptions: { + show: { + '@version': [1], + }, + }, + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'OAuth2 (Recommended)', + value: 'oAuth2', + }, + { + name: 'Service Account', + value: 'serviceAccount', + }, + ], + default: 'oAuth2', + displayOptions: { + show: { + '@version': [2, 3], + }, + }, + }, + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Drive', + value: 'drive', + }, + { + name: 'File', + value: 'file', + }, + { + name: 'Folder', + value: 'folder', + }, + ], + default: 'file', + }, + + // ---------------------------------- + // operations + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['file'], + }, + }, + options: [ + { + name: 'Copy', + value: 'copy', + description: 'Copy a file', + action: 'Copy a file', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a file', + action: 'Delete a file', + }, + { + name: 'Download', + value: 'download', + description: 'Download a file', + action: 'Download a file', + }, + { + name: 'List', + value: 'list', + description: 'List files and folders', + action: 'List a file', + }, + { + name: 'Share', + value: 'share', + description: 'Share a file', + action: 'Share a file', + }, + { + name: 'Update', + value: 'update', + description: 'Update a file', + action: 'Update a file', + }, + { + name: 'Upload', + value: 'upload', + description: 'Upload a file', + action: 'Upload a file', + }, + ], + default: 'upload', + }, + + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['folder'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a folder', + action: 'Create a folder', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a folder', + action: 'Delete a folder', + }, + { + name: 'Share', + value: 'share', + description: 'Share a folder', + action: 'Share a folder', + }, + ], + default: 'create', + }, + + // ---------------------------------- + // file + // ---------------------------------- + + { + displayName: 'File', + name: 'fileId', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'ID of the file', + placeholder: 'File ID', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'Link', + name: 'link', + type: 'string', + hint: 'Link to the file', + placeholder: + 'https://docs.google.com/spreadsheets/d/1-i6Vx0NN-3333eeeeeeeeee333333333/edit', + extractValue: { + type: 'regex', + regex: + 'https:\\/\\/(?:drive|docs)\\.google.com\\/\\w+\\/(?:d|folders)\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)', + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'File', + name: 'list', + type: 'list', + hint: 'File to use', + placeholder: 'File', + typeOptions: { + searchListMethod: 'fileSearch', + searchable: true, + }, + }, + ], + displayOptions: { + show: { + operation: ['download', 'copy', 'download', 'update', 'share', 'delete'], + resource: ['file'], + }, + }, + description: 'The ID of the file', + }, + + { + displayName: 'File ID', + name: 'fileOrFolderId', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'ID of the folder', + placeholder: 'Folder ID', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'Link', + name: 'link', + type: 'string', + hint: 'Link to the folder', + placeholder: + 'https://docs.google.com/spreadsheets/d/1-i6Vx0NN-3333eeeeeeeeee333333333/edit', + extractValue: { + type: 'regex', + regex: + 'https:\\/\\/(?:drive|docs)\\.google.com\\/\\w+\\/(?:d|folders)\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)', + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'File', + name: 'list', + type: 'list', + hint: 'File to use', + placeholder: 'File', + typeOptions: { + searchListMethod: 'fileSearch', + searchable: true, + }, + }, + ], + displayOptions: { + show: { + operation: ['share', 'delete'], + resource: ['folder'], + }, + }, + description: 'The ID of the folder', + }, + + // ---------------------------------- + // file:download + // ---------------------------------- + { + 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 write the data of the read file', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['download'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'Google File Conversion', + name: 'googleFileConversion', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + placeholder: 'Add Conversion', + options: [ + { + displayName: 'Conversion', + name: 'conversion', + values: [ + { + displayName: 'Google Docs', + name: 'docsToFormat', + type: 'options', + options: [ + { + name: 'To HTML', + value: 'text/html', + }, + { + name: 'To MS Word', + value: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }, + { + name: 'To OpenOffice Doc', + value: 'application/vnd.oasis.opendocument.text', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + { + name: 'To Rich Text', + value: 'application/rtf', + }, + ], + default: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + description: 'Format used to export when downloading Google Docs files', + }, + { + displayName: 'Google Drawings', + name: 'drawingsToFormat', + type: 'options', + options: [ + { + name: 'To JPEG', + value: 'image/jpeg', + }, + { + name: 'To PNG', + value: 'image/png', + }, + { + name: 'To SVG', + value: 'image/svg+xml', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + ], + default: 'image/jpeg', + description: 'Format used to export when downloading Google Drawings files', + }, + { + displayName: 'Google Slides', + name: 'slidesToFormat', + type: 'options', + options: [ + { + name: 'To MS PowerPoint', + value: + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + { + name: 'To OpenOffice Presentation', + value: 'application/vnd.oasis.opendocument.presentation', + }, + { + name: 'To Plain Text', + value: 'text/plain', + }, + ], + default: + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + description: 'Format used to export when downloading Google Slides files', + }, + { + displayName: 'Google Sheets', + name: 'sheetsToFormat', + type: 'options', + options: [ + { + name: 'To MS Excel', + value: 'application/x-vnd.oasis.opendocument.spreadsheet', + }, + { + name: 'To PDF', + value: 'application/pdf', + }, + { + name: 'To CSV', + value: 'text/csv', + }, + ], + default: 'application/x-vnd.oasis.opendocument.spreadsheet', + description: 'Format used to export when downloading Google Spreadsheets files', + }, + ], + }, + ], + }, + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + default: '', + description: 'File name. Ex: data.pdf.', + }, + ], + }, + + // ---------------------------------- + // file:list + // ---------------------------------- + { + displayName: 'Use Query String', + name: 'useQueryString', + type: 'boolean', + default: false, + displayOptions: { + show: { + operation: ['list'], + resource: ['file'], + }, + }, + description: 'Whether a query string should be used to filter results', + }, + { + displayName: 'Query String', + name: 'queryString', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['list'], + useQueryString: [true], + resource: ['file'], + }, + }, + placeholder: "name contains 'invoice'", + description: 'Query to use to return only specific files', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: ['list'], + resource: ['file'], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + default: 50, + description: 'Max number of results to return', + }, + { + displayName: 'Filters', + name: 'queryFilters', + placeholder: 'Add Filter', + description: 'Filters to use to return only specific files', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + operation: ['list'], + useQueryString: [false], + resource: ['file'], + }, + }, + options: [ + { + name: 'name', + displayName: 'Name', + values: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Contains', + value: 'contains', + }, + { + name: 'Is', + value: 'is', + }, + { + name: 'Is Not', + value: 'isNot', + }, + ], + default: 'contains', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'The value for operation', + }, + ], + }, + { + name: 'mimeType', + displayName: 'Mime Type', + values: [ + { + displayName: 'Mime Type', + name: 'mimeType', + type: 'options', + options: [ + { + name: '3rd Party Shortcut', + value: 'application/vnd.google-apps.drive-sdk', + }, + { + name: 'Audio', + value: 'application/vnd.google-apps.audio', + }, + { + name: 'Custom Mime Type', + value: 'custom', + }, + { + name: 'Google Apps Scripts', + value: 'application/vnd.google-apps.script', + }, + { + name: 'Google Docs', + value: 'application/vnd.google-apps.document', + }, + { + name: 'Google Drawing', + value: 'application/vnd.google-apps.drawing', + }, + { + name: 'Google Drive File', + value: 'application/vnd.google-apps.file', + }, + { + name: 'Google Drive Folder', + value: 'application/vnd.google-apps.folder', + }, + { + name: 'Google Forms', + value: 'application/vnd.google-apps.form', + }, + { + name: 'Google Fusion Tables', + value: 'application/vnd.google-apps.fusiontable', + }, + { + name: 'Google My Maps', + value: 'application/vnd.google-apps.map', + }, + { + name: 'Google Sheets', + value: 'application/vnd.google-apps.spreadsheet', + }, + { + name: 'Google Sites', + value: 'application/vnd.google-apps.site', + }, + { + name: 'Google Slides', + value: 'application/vnd.google-apps.presentation', + }, + { + name: 'Photo', + value: 'application/vnd.google-apps.photo', + }, + { + name: 'Unknown', + value: 'application/vnd.google-apps.unknown', + }, + { + name: 'Video', + value: 'application/vnd.google-apps.video', + }, + ], + default: 'application/vnd.google-apps.file', + description: 'The Mime-Type of the files to return', + }, + { + displayName: 'Custom Mime Type', + name: 'customMimeType', + type: 'string', + default: '', + displayOptions: { + show: { + mimeType: ['custom'], + }, + }, + }, + ], + }, + ], + }, + + // ---------------------------------- + // file:share + // ---------------------------------- + + { + displayName: 'Permissions', + name: 'permissionsUi', + placeholder: 'Add Permission', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: ['file', 'folder'], + operation: ['share'], + }, + }, + options: [ + { + displayName: 'Permission', + name: 'permissionsValues', + values: [ + { + displayName: 'Role', + name: 'role', + type: 'options', + options: [ + { + name: 'Commenter', + value: 'commenter', + }, + { + name: 'File Organizer', + value: 'fileOrganizer', + }, + { + name: 'Organizer', + value: 'organizer', + }, + { + name: 'Owner', + value: 'owner', + }, + { + name: 'Reader', + value: 'reader', + }, + { + name: 'Writer', + value: 'writer', + }, + ], + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'User', + value: 'user', + }, + { + name: 'Group', + value: 'group', + }, + { + name: 'Domain', + value: 'domain', + }, + { + name: 'Anyone', + value: 'anyone', + }, + ], + default: '', + description: + 'Information about the different types can be found here', + }, + { + displayName: 'Email Address', + name: 'emailAddress', + type: 'string', + displayOptions: { + show: { + type: ['user', 'group'], + }, + }, + default: '', + description: 'The email address of the user or group to which this permission refers', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { + show: { + type: ['domain'], + }, + }, + default: '', + description: 'The domain to which this permission refers', + }, + { + displayName: 'Allow File Discovery', + name: 'allowFileDiscovery', + type: 'boolean', + displayOptions: { + show: { + type: ['domain', 'anyone'], + }, + }, + default: false, + description: 'Whether the permission allows the file to be discovered through search', + }, + ], + }, + ], + }, + + { + displayName: 'Binary Data', + name: 'binaryData', + type: 'boolean', + default: false, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + description: 'Whether the data to upload should be taken from binary field', + }, + { + displayName: 'File Content', + name: 'fileContent', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + binaryData: [false], + }, + }, + placeholder: '', + description: 'The text content of the file to upload', + }, + { + displayName: 'Binary Property', + name: 'binaryPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + binaryData: [true], + }, + }, + placeholder: '', + description: + 'Name of the binary property which contains the data for the file to be uploaded', + }, + + // ---------------------------------- + // file:update + // ---------------------------------- + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['update'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + default: '', + description: 'The name of the file', + }, + { + displayName: 'Keep Revision Forever', + name: 'keepRevisionForever', + type: 'boolean', + default: false, + description: + "Whether to set the 'keepForever' field in the new head revision. This is only applicable to files with binary content in Google Drive. Only 200 revisions for the file can be kept forever. If the limit is reached, try deleting pinned revisions.", + }, + { + displayName: 'Move to Trash', + name: 'trashed', + type: 'boolean', + default: false, + description: 'Whether to move a file to the trash. Only the owner may trash a file.', + }, + { + displayName: 'OCR Language', + name: 'ocrLanguage', + type: 'string', + default: '', + description: 'A language hint for OCR processing during image import (ISO 639-1 code)', + }, + { + displayName: 'Parent ID', + name: 'parentId', + type: 'string', + default: '', + description: 'The ID of the parent to set', + }, + { + displayName: 'Use Content As Indexable Text', + name: 'useContentAsIndexableText', + type: 'boolean', + default: false, + description: 'Whether to use the uploaded content as indexable text', + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['update'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'multiOptions', + options: [ + { + name: '[All]', + value: '*', + description: 'All fields', + }, + { + name: 'explicitlyTrashed', + value: 'explicitlyTrashed', + }, + { + name: 'exportLinks', + value: 'exportLinks', + }, + { + name: 'hasThumbnail', + value: 'hasThumbnail', + }, + { + name: 'iconLink', + value: 'iconLink', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Kind', + value: 'kind', + }, + { + name: 'mimeType', + value: 'mimeType', + }, + { + name: 'Name', + value: 'name', + }, + { + name: 'Permissions', + value: 'permissions', + }, + { + name: 'Shared', + value: 'shared', + }, + { + name: 'Spaces', + value: 'spaces', + }, + { + name: 'Starred', + value: 'starred', + }, + { + name: 'thumbnailLink', + value: 'thumbnailLink', + }, + { + name: 'Trashed', + value: 'trashed', + }, + { + name: 'Version', + value: 'version', + }, + { + name: 'webViewLink', + value: 'webViewLink', + }, + ], + default: [], + description: 'The fields to return', + }, + ], + }, + // ---------------------------------- + // file:upload + // ---------------------------------- + { + displayName: 'File Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + placeholder: 'invoice_1.pdf', + description: 'The name the file should be saved as', + }, + // ---------------------------------- + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: false, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether + description: + 'By default the response only contain the ID of the file. If this option gets activated, it will resolve the data automatically.', + }, + { + displayName: 'Parents', + name: 'parents', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: [], + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + description: 'The IDs of the parent folders which contain the file', + }, + + // ---------------------------------- + // folder + // ---------------------------------- + + // ---------------------------------- + // folder:create + // ---------------------------------- + { + displayName: 'Folder', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['create'], + resource: ['folder'], + }, + }, + placeholder: 'invoices', + description: 'The name of folder to create', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + '/operation': ['copy', 'list', 'share', 'create'], + '/resource': ['file', 'folder'], + }, + }, + options: [ + { + displayName: 'Email Message', + name: 'emailMessage', + type: 'string', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: '', + description: 'A plain text custom message to include in the notification email', + }, + { + displayName: 'Enforce Single Parent', + name: 'enforceSingleParent', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether to opt in to API behavior that aims for all items to have exactly one parent. This parameter only takes effect if the item is not in a shared drive.', + }, + { + displayName: 'Fields', + name: 'fields', + type: 'multiOptions', + displayOptions: { + show: { + '/operation': ['list', 'copy'], + }, + }, + options: [ + { + name: '*', + value: '*', + description: 'All fields', + }, + { + name: 'explicitlyTrashed', + value: 'explicitlyTrashed', + }, + { + name: 'exportLinks', + value: 'exportLinks', + }, + { + name: 'hasThumbnail', + value: 'hasThumbnail', + }, + { + name: 'iconLink', + value: 'iconLink', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Kind', + value: 'kind', + }, + { + name: 'mimeType', + value: 'mimeType', + }, + { + name: 'Name', + value: 'name', + }, + { + name: 'Permissions', + value: 'permissions', + }, + { + name: 'Shared', + value: 'shared', + }, + { + name: 'Spaces', + value: 'spaces', + }, + { + name: 'Starred', + value: 'starred', + }, + { + name: 'thumbnailLink', + value: 'thumbnailLink', + }, + { + name: 'Trashed', + value: 'trashed', + }, + { + name: 'Version', + value: 'version', + }, + { + name: 'webViewLink', + value: 'webViewLink', + }, + ], + default: [], + description: 'The fields to return', + }, + { + displayName: 'Move To New Owners Root', + name: 'moveToNewOwnersRoot', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether + description: + "

This parameter only takes effect if the item is not in a shared drive and the request is attempting to transfer the ownership of the item.

When set to true, the item is moved to the new owner's My Drive root folder and all prior parents removed.

", + }, + { + displayName: 'Send Notification Email', + name: 'sendNotificationEmail', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: 'Whether to send a notification email when sharing to users or groups', + }, + { + displayName: 'Supports All Drives', + name: 'supportsAllDrives', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether the requesting application supports both My Drives and shared drives', + }, + { + displayName: 'Transfer Ownership', + name: 'transferOwnership', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether to transfer ownership to the specified user and downgrade the current owner to a writer', + }, + { + displayName: 'Use Domain Admin Access', + name: 'useDomainAdminAccess', + type: 'boolean', + displayOptions: { + show: { + '/operation': ['share'], + '/resource': ['file', 'folder'], + }, + }, + default: false, + description: + 'Whether to perform the operation as domain administrator, i.e. if you are an administrator of the domain to which the shared drive belongs, you will be granted access automatically.', + }, + + { + displayName: 'File Name', + name: 'name', + type: 'string', + displayOptions: { + show: { + '/operation': ['copy'], + '/resource': ['file'], + }, + }, + default: '', + placeholder: 'invoice_1.pdf', + description: 'The name the file should be saved as', + }, + { + displayName: 'Parents', + name: 'parents', + type: 'string', + displayOptions: { + show: { + '/operation': ['copy', 'create'], + '/resource': ['file', 'folder'], + }, + }, + typeOptions: { + multipleValues: true, + }, + default: [], + description: 'The IDs of the parent folders the file/folder should be saved in', + }, + { + displayName: 'Spaces', + name: 'spaces', + type: 'multiOptions', + displayOptions: { + show: { + '/operation': ['list'], + '/resource': ['file'], + }, + }, + options: [ + { + name: '[All]', + value: '*', + description: 'All spaces', + }, + { + name: 'appDataFolder', + value: 'appDataFolder', + }, + { + name: 'Drive', + value: 'drive', + }, + { + name: 'Photos', + value: 'photos', + }, + ], + default: [], + description: 'The spaces to operate on', + }, + { + displayName: 'Corpora', + name: 'corpora', + type: 'options', + displayOptions: { + show: { + '/operation': ['list'], + '/resource': ['file'], + }, + }, + options: [ + { + name: 'User', + value: 'user', + description: 'All files in "My Drive" and "Shared with me"', + }, + { + name: 'Domain', + value: 'domain', + description: "All files shared to the user's domain that are searchable", + }, + { + name: 'Drive', + value: 'drive', + description: 'All files contained in a single shared drive', + }, + { + name: 'allDrives', + value: 'allDrives', + description: 'All drives', + }, + ], + default: '', + description: 'The corpora to operate on', + }, + { + displayName: 'Drive ID', + name: 'driveId', + type: 'string', + default: '', + displayOptions: { + show: { + '/operation': ['list'], + '/resource': ['file'], + corpora: ['drive'], + }, + }, + description: + 'ID of the shared drive to search. The driveId parameter must be specified if and only if corpora is set to drive.', + }, + ], + }, + // ---------------------------------- + // drive + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['drive'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a drive', + action: 'Create a drive', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a drive', + action: 'Delete a drive', + }, + { + name: 'Get', + value: 'get', + description: 'Get a drive', + action: 'Get a drive', + }, + { + name: 'List', + value: 'list', + description: 'List all drives', + action: 'List all drives', + }, + { + name: 'Update', + value: 'update', + description: 'Update a drive', + action: 'Update a drive', + }, + ], + default: 'create', + }, + + { + displayName: 'File', + name: 'driveId', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'The ID of the shared drive', + placeholder: 'Drive ID', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'Link', + name: 'link', + type: 'string', + hint: 'Link to the shared drive', + placeholder: 'https://drive.google.com/drive/folders/0AaaaaAAAAAAAaa', + extractValue: { + type: 'regex', + regex: 'https:\\/\\/drive\\.google.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)', + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'Drive', + name: 'list', + type: 'list', + hint: 'Shared drive to use', + placeholder: 'Drive', + typeOptions: { + searchListMethod: 'driveSearch', + searchable: true, + }, + }, + ], + displayOptions: { + show: { + operation: ['delete', 'get', 'update'], + resource: ['drive'], + }, + }, + description: 'The ID of the file', + }, + + // ---------------------------------- + // drive:create + // ---------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + operation: ['create'], + resource: ['drive'], + }, + }, + description: 'The name of this shared drive', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['create'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Capabilities', + name: 'capabilities', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Can Add Children', + name: 'canAddChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can add children to folders in this shared drive', + }, + { + displayName: 'Can Change Copy Requires Writer Permission Restriction', + name: 'canChangeCopyRequiresWriterPermissionRestriction', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the copyRequiresWriterPermission restriction of this shared drive', + }, + { + displayName: 'Can Change Domain Users Only Restriction', + name: 'canChangeDomainUsersOnlyRestriction', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the domainUsersOnly restriction of this shared drive', + }, + { + displayName: 'Can Change Drive Background', + name: 'canChangeDriveBackground', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the background of this shared drive', + }, + { + displayName: 'Can Change Drive Members Only Restriction', + name: 'canChangeDriveMembersOnlyRestriction', + type: 'boolean', + default: false, + description: + 'Whether the current user can change the driveMembersOnly restriction of this shared drive', + }, + { + displayName: 'Can Comment', + name: 'canComment', + type: 'boolean', + default: false, + description: 'Whether the current user can comment on files in this shared drive', + }, + { + displayName: 'Can Copy', + name: 'canCopy', + type: 'boolean', + default: false, + description: 'Whether the current user can copy files in this shared drive', + }, + { + displayName: 'Can Delete Children', + name: 'canDeleteChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can delete children from folders in this shared drive', + }, + { + displayName: 'Can Delete Drive', + name: 'canDeleteDrive', + type: 'boolean', + default: false, + description: + 'Whether the current user can delete this shared drive. Attempting to delete the shared drive may still fail if there are untrashed items inside the shared drive.', + }, + { + displayName: 'Can Download', + name: 'canDownload', + type: 'boolean', + default: false, + description: 'Whether the current user can download files in this shared drive', + }, + { + displayName: 'Can Edit', + name: 'canEdit', + type: 'boolean', + default: false, + description: 'Whether the current user can edit files in this shared drive', + }, + { + displayName: 'Can List Children', + name: 'canListChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can list the children of folders in this shared drive', + }, + { + displayName: 'Can Manage Members', + name: 'canManageMembers', + type: 'boolean', + default: false, + description: + 'Whether the current user can add members to this shared drive or remove them or change their role', + }, + { + displayName: 'Can Read Revisions', + name: 'canReadRevisions', + type: 'boolean', + default: false, + description: + 'Whether the current user can read the revisions resource of files in this shared drive', + }, + { + displayName: 'Can Rename', + name: 'canRename', + type: 'boolean', + default: false, + description: + 'Whether the current user can rename files or folders in this shared drive', + }, + { + displayName: 'Can Rename Drive', + name: 'canRenameDrive', + type: 'boolean', + default: false, + description: 'Whether the current user can rename this shared drive', + }, + { + displayName: 'Can Share', + name: 'canShare', + type: 'boolean', + default: false, + description: 'Whether the current user can rename this shared drive', + }, + { + displayName: 'Can Trash Children', + name: 'canTrashChildren', + type: 'boolean', + default: false, + description: + 'Whether the current user can trash children from folders in this shared drive', + }, + ], + }, + { + displayName: 'Color RGB', + name: 'colorRgb', + type: 'color', + default: '', + description: 'The color of this shared drive as an RGB hex string', + }, + { + displayName: 'Created Time', + name: 'createdTime', + type: 'dateTime', + default: '', + description: 'The time at which the shared drive was created (RFC 3339 date-time)', + }, + { + displayName: 'Hidden', + name: 'hidden', + type: 'boolean', + default: false, + description: 'Whether the shared drive is hidden from default view', + }, + { + displayName: 'Restrictions', + name: 'restrictions', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Admin Managed Restrictions', + name: 'adminManagedRestrictions', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Copy Requires Writer Permission', + name: 'copyRequiresWriterPermission', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Domain Users Only', + name: 'domainUsersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.', + }, + { + displayName: 'Drive Members Only', + name: 'driveMembersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to items inside this shared drive is restricted to its members', + }, + ], + }, + ], + }, + // ---------------------------------- + // drive:get + // ---------------------------------- + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['get'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Use Domain Admin Access', + name: 'useDomainAdminAccess', + type: 'boolean', + default: false, + description: + 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).', + }, + ], + }, + // ---------------------------------- + // drive:list + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: ['list'], + resource: ['drive'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: ['list'], + resource: ['drive'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 200, + }, + default: 100, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['list'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Query', + name: 'q', + type: 'string', + default: '', + description: + 'Query string for searching shared drives. See the "Search for shared drives" guide for supported syntax.', + }, + { + displayName: 'Use Domain Admin Access', + name: 'useDomainAdminAccess', + type: 'boolean', + default: false, + description: + 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).', + }, + ], + }, + // ---------------------------------- + // drive:update + // ---------------------------------- + { + displayName: 'Update Fields', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['update'], + resource: ['drive'], + }, + }, + options: [ + { + displayName: 'Color RGB', + name: 'colorRgb', + type: 'color', + default: '', + description: 'The color of this shared drive as an RGB hex string', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of this shared drive', + }, + { + displayName: 'Restrictions', + name: 'restrictions', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Admin Managed Restrictions', + name: 'adminManagedRestrictions', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Copy Requires Writer Permission', + name: 'copyRequiresWriterPermission', + type: 'boolean', + default: false, + description: + 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.', + }, + { + displayName: 'Domain Users Only', + name: 'domainUsersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.', + }, + { + displayName: 'Drive Members Only', + name: 'driveMembersOnly', + type: 'boolean', + default: false, + description: + 'Whether access to items inside this shared drive is restricted to its members', + }, + ], + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: ['upload'], + resource: ['file'], + }, + }, + options: [ + { + displayName: 'APP Properties', + name: 'appPropertiesUi', + placeholder: 'Add Property', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + description: + 'A collection of arbitrary key-value pairs which are private to the requesting app', + options: [ + { + name: 'appPropertyValues', + displayName: 'APP Property', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Name of the key to add', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value to set for the key', + }, + ], + }, + ], + }, + { + displayName: 'Properties', + name: 'propertiesUi', + placeholder: 'Add Property', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + description: 'A collection of arbitrary key-value pairs which are visible to all apps', + options: [ + { + name: 'propertyValues', + displayName: 'Property', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Name of the key to add', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value to set for the key', + }, + ], + }, + ], + }, + ], + }, + ], +}; diff --git a/packages/nodes-base/nodes/Trello/v1/AttachmentDescription.ts b/packages/nodes-base/nodes/Trello/AttachmentDescription.ts similarity index 72% rename from packages/nodes-base/nodes/Trello/v1/AttachmentDescription.ts rename to packages/nodes-base/nodes/Trello/AttachmentDescription.ts index 75aee8d37689f..535c95b323061 100644 --- a/packages/nodes-base/nodes/Trello/v1/AttachmentDescription.ts +++ b/packages/nodes-base/nodes/Trello/AttachmentDescription.ts @@ -45,6 +45,76 @@ export const attachmentOperations: INodeProperties[] = [ ]; export const attachmentFields: INodeProperties[] = [ + { + displayName: 'Card ID', + name: 'cardIdAttachmentRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'From List', + name: 'list', + type: 'list', + hint: 'Select a card from the list', + placeholder: 'Choose...', + typeOptions: { + searchListMethod: 'searchCards', + searchFilterRequired: true, + searchable: true, + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter Card Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'wiIaGwqE', + url: '=https://trello.com/c/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter Card URL', + placeholder: 'https://trello.com/c/e123456/card-name', + validation: [ + { + type: 'regex', + properties: { + regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', + errorMessage: + 'URL has to be in the format: http(s)://trello.com/c//', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://trello.com/c/([a-zA-Z0-9]+)', + }, + }, + ], + displayOptions: { + show: { + operation: ['delete', 'create', 'get', 'getAll'], + resource: ['attachment'], + '@version': [2], + }, + }, + description: 'The ID of the card', + }, // ---------------------------------- // attachment:create // ---------------------------------- @@ -58,6 +128,7 @@ export const attachmentFields: INodeProperties[] = [ show: { operation: ['create'], resource: ['attachment'], + '@version': [1], }, }, description: 'The ID of the card to add attachment to', @@ -120,6 +191,7 @@ export const attachmentFields: INodeProperties[] = [ show: { operation: ['delete'], resource: ['attachment'], + '@version': [1], }, }, description: 'The ID of the card that attachment belongs to', @@ -152,6 +224,7 @@ export const attachmentFields: INodeProperties[] = [ show: { operation: ['getAll'], resource: ['attachment'], + '@version': [1], }, }, description: 'The ID of the card to get attachments', @@ -192,6 +265,7 @@ export const attachmentFields: INodeProperties[] = [ show: { operation: ['get'], resource: ['attachment'], + '@version': [1], }, }, description: 'The ID of the card to get attachment', diff --git a/packages/nodes-base/nodes/Trello/v1/BoardDescription.ts b/packages/nodes-base/nodes/Trello/BoardDescription.ts similarity index 82% rename from packages/nodes-base/nodes/Trello/v1/BoardDescription.ts rename to packages/nodes-base/nodes/Trello/BoardDescription.ts index ccd0f1a60a477..8a5054be2e94f 100644 --- a/packages/nodes-base/nodes/Trello/v1/BoardDescription.ts +++ b/packages/nodes-base/nodes/Trello/BoardDescription.ts @@ -294,6 +294,79 @@ export const boardFields: INodeProperties[] = [ ], }, + { + displayName: 'Board', + name: 'boardIdRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + displayOptions: { + show: { + operation: ['get', 'delete', 'update'], + resource: ['board'], + '@version': [2], + }, + }, + description: 'The ID of the board', + modes: [ + // TODO: This rule should only apply for direct node properties, not their children + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'From List', + name: 'list', + type: 'list', + hint: 'Select a board from the list', + placeholder: 'Choose...', + initType: 'board', + typeOptions: { + searchListMethod: 'searchBoards', + searchFilterRequired: true, + searchable: true, + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter Board Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'KdEAAdde', + url: '=https://trello.com/b/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter board URL', + placeholder: 'https://trello.com/b/e123456/board-name', + validation: [ + { + type: 'regex', + properties: { + regex: 'http(s)?://trello.com/b/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', + errorMessage: + 'URL has to be in the format: http(s)://trello.com/b//', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://trello.com/b/([a-zA-Z0-9]+)', + }, + }, + ], + }, + // ---------------------------------- // board:delete // ---------------------------------- @@ -307,6 +380,7 @@ export const boardFields: INodeProperties[] = [ show: { operation: ['delete'], resource: ['board'], + '@version': [1], }, }, description: 'The ID of the board to delete', @@ -325,6 +399,7 @@ export const boardFields: INodeProperties[] = [ show: { operation: ['get'], resource: ['board'], + '@version': [1], }, }, description: 'The ID of the board to get', @@ -373,6 +448,7 @@ export const boardFields: INodeProperties[] = [ show: { operation: ['update'], resource: ['board'], + '@version': [1], }, }, description: 'The ID of the board to update', diff --git a/packages/nodes-base/nodes/Trello/v1/BoardMemberDescription.ts b/packages/nodes-base/nodes/Trello/BoardMemberDescription.ts similarity index 100% rename from packages/nodes-base/nodes/Trello/v1/BoardMemberDescription.ts rename to packages/nodes-base/nodes/Trello/BoardMemberDescription.ts diff --git a/packages/nodes-base/nodes/Trello/v1/CardCommentDescription.ts b/packages/nodes-base/nodes/Trello/CardCommentDescription.ts similarity index 61% rename from packages/nodes-base/nodes/Trello/v1/CardCommentDescription.ts rename to packages/nodes-base/nodes/Trello/CardCommentDescription.ts index b3a733077328e..0652a526f4589 100644 --- a/packages/nodes-base/nodes/Trello/v1/CardCommentDescription.ts +++ b/packages/nodes-base/nodes/Trello/CardCommentDescription.ts @@ -36,6 +36,77 @@ export const cardCommentOperations: INodeProperties[] = [ ]; export const cardCommentFields: INodeProperties[] = [ + { + displayName: 'Card ID', + name: 'cardIdCommentRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'From List', + name: 'list', + type: 'list', + hint: 'Select a card from the list', + placeholder: 'Choose...', + typeOptions: { + searchListMethod: 'searchCards', + searchFilterRequired: true, + searchable: true, + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter Card Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'wiIaGwqE', + url: '=https://trello.com/c/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter Card URL', + placeholder: 'https://trello.com/c/e123456/card-name', + validation: [ + { + type: 'regex', + properties: { + regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', + errorMessage: + 'URL has to be in the format: http(s)://trello.com/c//', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://trello.com/c/([a-zA-Z0-9]+)', + }, + }, + ], + displayOptions: { + show: { + operation: ['update', 'delete', 'create'], + resource: ['cardComment'], + '@version': [2], + }, + }, + description: 'The ID of the card', + }, + // ---------------------------------- // cardComment:create // ---------------------------------- @@ -49,6 +120,7 @@ export const cardCommentFields: INodeProperties[] = [ show: { operation: ['create'], resource: ['cardComment'], + '@version': [1], }, }, description: 'The ID of the card', @@ -81,6 +153,7 @@ export const cardCommentFields: INodeProperties[] = [ show: { operation: ['delete'], resource: ['cardComment'], + '@version': [1], }, }, description: 'The ID of the card', @@ -113,6 +186,7 @@ export const cardCommentFields: INodeProperties[] = [ show: { operation: ['update'], resource: ['cardComment'], + '@version': [1], }, }, description: 'The ID of the card to update', diff --git a/packages/nodes-base/nodes/Trello/v1/CardDescription.ts b/packages/nodes-base/nodes/Trello/CardDescription.ts similarity index 83% rename from packages/nodes-base/nodes/Trello/v1/CardDescription.ts rename to packages/nodes-base/nodes/Trello/CardDescription.ts index 2acd2ede0ec8b..15749acbb018a 100644 --- a/packages/nodes-base/nodes/Trello/v1/CardDescription.ts +++ b/packages/nodes-base/nodes/Trello/CardDescription.ts @@ -163,6 +163,77 @@ export const cardFields: INodeProperties[] = [ ], }, + { + displayName: 'Card ID', + name: 'cardIdRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'From List', + name: 'list', + type: 'list', + hint: 'Select a card from the list', + placeholder: 'Choose...', + typeOptions: { + searchListMethod: 'searchCards', + searchFilterRequired: true, + searchable: true, + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter Card Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'wiIaGwqE', + url: '=https://trello.com/c/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter Card URL', + placeholder: 'https://trello.com/c/e123456/card-name', + validation: [ + { + type: 'regex', + properties: { + regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', + errorMessage: + 'URL has to be in the format: http(s)://trello.com/c//', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://trello.com/c/([a-zA-Z0-9]+)', + }, + }, + ], + displayOptions: { + show: { + operation: ['get', 'delete', 'update'], + resource: ['card'], + '@version': [2], + }, + }, + description: 'The ID of the card', + }, + // ---------------------------------- // card:delete // ---------------------------------- @@ -176,6 +247,7 @@ export const cardFields: INodeProperties[] = [ show: { operation: ['delete'], resource: ['card'], + '@version': [1], }, }, description: 'The ID of the card to delete', @@ -194,6 +266,7 @@ export const cardFields: INodeProperties[] = [ show: { operation: ['get'], resource: ['card'], + '@version': [1], }, }, description: 'The ID of the card to get', @@ -295,6 +368,7 @@ export const cardFields: INodeProperties[] = [ show: { operation: ['update'], resource: ['card'], + '@version': [1], }, }, description: 'The ID of the card to update', diff --git a/packages/nodes-base/nodes/Trello/v1/ChecklistDescription.ts b/packages/nodes-base/nodes/Trello/ChecklistDescription.ts similarity index 85% rename from packages/nodes-base/nodes/Trello/v1/ChecklistDescription.ts rename to packages/nodes-base/nodes/Trello/ChecklistDescription.ts index 83ff9ed98866d..9ffe499161fae 100644 --- a/packages/nodes-base/nodes/Trello/v1/ChecklistDescription.ts +++ b/packages/nodes-base/nodes/Trello/ChecklistDescription.ts @@ -75,6 +75,77 @@ export const checklistOperations: INodeProperties[] = [ ]; export const checklistFields: INodeProperties[] = [ + { + displayName: 'Card ID', + name: 'cardIdChecklistRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'From List', + name: 'list', + type: 'list', + hint: 'Select a card from the list', + placeholder: 'Choose...', + typeOptions: { + searchListMethod: 'searchCards', + searchFilterRequired: true, + searchable: true, + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter Card Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'wiIaGwqE', + url: '=https://trello.com/c/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter Card URL', + placeholder: 'https://trello.com/c/e123456/card-name', + validation: [ + { + type: 'regex', + properties: { + regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', + errorMessage: + 'URL has to be in the format: http(s)://trello.com/c//', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://trello.com/c/([a-zA-Z0-9]+)', + }, + }, + ], + displayOptions: { + show: { + operation: ['delete', 'create', 'getAll', 'deleteCheckItem', 'getCheckItem', 'updateCheckItem', 'completeCheckItems'], + resource: ['checklist'], + '@version': [2], + }, + }, + description: 'The ID of the card', + }, + // ---------------------------------- // checklist:create // ---------------------------------- @@ -88,6 +159,7 @@ export const checklistFields: INodeProperties[] = [ show: { operation: ['create'], resource: ['checklist'], + '@version': [1], }, }, description: 'The ID of the card to add checklist to', @@ -150,6 +222,7 @@ export const checklistFields: INodeProperties[] = [ show: { operation: ['delete'], resource: ['checklist'], + '@version': [1], }, }, description: 'The ID of the card that checklist belongs to', @@ -182,6 +255,7 @@ export const checklistFields: INodeProperties[] = [ show: { operation: ['getAll'], resource: ['checklist'], + '@version': [1], }, }, description: 'The ID of the card to get checklists', @@ -324,6 +398,7 @@ export const checklistFields: INodeProperties[] = [ show: { operation: ['deleteCheckItem'], resource: ['checklist'], + '@version': [1], }, }, description: 'The ID of the card that checklist belongs to', @@ -356,6 +431,7 @@ export const checklistFields: INodeProperties[] = [ show: { operation: ['getCheckItem'], resource: ['checklist'], + '@version': [1], }, }, description: 'The ID of the card that checklist belongs to', @@ -410,6 +486,7 @@ export const checklistFields: INodeProperties[] = [ show: { operation: ['updateCheckItem'], resource: ['checklist'], + '@version': [1], }, }, description: 'The ID of the card that checklist belongs to', @@ -495,6 +572,7 @@ export const checklistFields: INodeProperties[] = [ show: { operation: ['completedCheckItems'], resource: ['checklist'], + '@version': [1], }, }, description: 'The ID of the card for checkItems', diff --git a/packages/nodes-base/nodes/Trello/v1/LabelDescription.ts b/packages/nodes-base/nodes/Trello/LabelDescription.ts similarity index 67% rename from packages/nodes-base/nodes/Trello/v1/LabelDescription.ts rename to packages/nodes-base/nodes/Trello/LabelDescription.ts index 2bb7e4e0c99e0..7a995ab9157b7 100644 --- a/packages/nodes-base/nodes/Trello/v1/LabelDescription.ts +++ b/packages/nodes-base/nodes/Trello/LabelDescription.ts @@ -63,6 +63,80 @@ export const labelOperations: INodeProperties[] = [ ]; export const labelFields: INodeProperties[] = [ + { + displayName: 'Board', + name: 'boardIdLabelRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + displayOptions: { + show: { + operation: ['create', 'getAll'], + resource: ['label'], + '@version': [2], + }, + }, + description: 'The ID of the board', + modes: [ + // TODO: This rule should only apply for direct node properties, not their children + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'From List', + name: 'list', + type: 'list', + hint: 'Select a board from the list', + placeholder: 'Choose...', + initType: 'board', + typeOptions: { + searchListMethod: 'searchBoards', + searchFilterRequired: true, + searchable: true, + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter Board Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'KdEAAdde', + url: '=https://trello.com/b/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter board URL', + placeholder: 'https://trello.com/b/e123456/board-name', + validation: [ + { + type: 'regex', + properties: { + regex: 'http(s)?://trello.com/b/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', + errorMessage: + 'URL has to be in the format: http(s)://trello.com/b//', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://trello.com/b/([a-zA-Z0-9]+)', + }, + }, + ], + }, + + // ---------------------------------- // label:create // ---------------------------------- @@ -76,6 +150,7 @@ export const labelFields: INodeProperties[] = [ show: { operation: ['create'], resource: ['label'], + '@version': [1], }, }, description: 'The ID of the board to create the label on', @@ -186,6 +261,7 @@ export const labelFields: INodeProperties[] = [ show: { operation: ['getAll'], resource: ['label'], + '@version': [1], }, }, description: 'The ID of the board to get label', @@ -253,6 +329,77 @@ export const labelFields: INodeProperties[] = [ ], }, + { + displayName: 'Card ID', + name: 'cardIdLabelRLC', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'From List', + name: 'list', + type: 'list', + hint: 'Select a card from the list', + placeholder: 'Choose...', + typeOptions: { + searchListMethod: 'searchCards', + searchFilterRequired: true, + searchable: true, + }, + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'ID', + name: 'id', + type: 'string', + hint: 'Enter Card Id', + validation: [ + { + type: 'regex', + properties: { + regex: '[a-zA-Z0-9]+', + errorMessage: 'ID value cannot be empty', + }, + }, + ], + placeholder: 'wiIaGwqE', + url: '=https://trello.com/c/{{$value}}', + }, + // eslint-disable-next-line n8n-nodes-base/node-param-default-missing + { + displayName: 'By URL', + name: 'url', + type: 'string', + hint: 'Enter Card URL', + placeholder: 'https://trello.com/c/e123456/card-name', + validation: [ + { + type: 'regex', + properties: { + regex: 'http(s)?://trello.com/c/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', + errorMessage: + 'URL has to be in the format: http(s)://trello.com/c//', + }, + }, + ], + extractValue: { + type: 'regex', + regex: 'https://trello.com/c/([a-zA-Z0-9]+)', + }, + }, + ], + displayOptions: { + show: { + operation: ['addLabel', 'removeLabel'], + resource: ['label'], + '@version': [2], + }, + }, + description: 'The ID of the card', + }, + // ---------------------------------- // label:addLabel // ---------------------------------- @@ -266,6 +413,7 @@ export const labelFields: INodeProperties[] = [ show: { operation: ['addLabel'], resource: ['label'], + '@version': [1], }, }, description: 'The ID of the card to get label', diff --git a/packages/nodes-base/nodes/Trello/v1/ListDescription.ts b/packages/nodes-base/nodes/Trello/ListDescription.ts similarity index 100% rename from packages/nodes-base/nodes/Trello/v1/ListDescription.ts rename to packages/nodes-base/nodes/Trello/ListDescription.ts diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index b9a5b6d48ded8..1db1db9d37e87 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -1,28 +1,1066 @@ -import { INodeTypeBaseDescription, INodeVersionedType } from 'n8n-workflow'; +import { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-core'; -import { TrelloV1 } from './v1/TrelloV1.node'; +import { + IDataObject, + INodeExecutionData, + INodeListSearchResult, + INodeType, + INodeTypeDescription, + NodeOperationError, +} from 'n8n-workflow'; -import { TrelloV2 } from './v2/TrelloV2.node'; +import { apiRequest, apiRequestAllItems } from './GenericFunctions'; -import { NodeVersionedType } from '../../src/NodeVersionedType'; +import { attachmentFields, attachmentOperations } from './AttachmentDescription'; -export class Trello extends NodeVersionedType { - constructor() { - const baseDescription: INodeTypeBaseDescription = { - displayName: 'Trello', - name: 'trello', - icon: 'file:trello.svg', - group: ['transform'], - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Create, change and delete boards and cards', - defaultVersion: 2, - }; +import { boardFields, boardOperations } from './BoardDescription'; - const nodeVersions: INodeVersionedType['nodeVersions'] = { - 1: new TrelloV1(baseDescription), - 2: new TrelloV2(baseDescription), - }; +import { boardMemberFields, boardMemberOperations } from './BoardMemberDescription'; - super(nodeVersions, baseDescription); +import { cardFields, cardOperations } from './CardDescription'; + +import { cardCommentFields, cardCommentOperations } from './CardCommentDescription'; + +import { checklistFields, checklistOperations } from './ChecklistDescription'; + +import { labelFields, labelOperations } from './LabelDescription'; + +import { listFields, listOperations } from './ListDescription'; + +interface TrelloBoardType { + id: string; + name: string; + url: string; + desc: string; +} + +// We retrieve the same fields. This is just to make it clear it's not actually +// getting boards back. +type TrelloCardType = TrelloBoardType; + +export class Trello implements INodeType { + description: INodeTypeDescription = { + displayName: 'Trello', + name: 'trello', + icon: 'file:trello.svg', + group: ['transform'], + version: [1, 2], + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Create, change and delete boards and cards', + defaults: { + name: 'Trello', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'trelloApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Attachment', + value: 'attachment', + }, + { + name: 'Board', + value: 'board', + }, + { + name: 'Board Member', + value: 'boardMember', + }, + { + name: 'Card', + value: 'card', + }, + { + name: 'Card Comment', + value: 'cardComment', + }, + { + name: 'Checklist', + value: 'checklist', + }, + { + name: 'Label', + value: 'label', + }, + { + name: 'List', + value: 'list', + }, + ], + default: 'card', + }, + + // ---------------------------------- + // operations + // ---------------------------------- + ...attachmentOperations, + ...boardOperations, + ...boardMemberOperations, + ...cardOperations, + ...cardCommentOperations, + ...checklistOperations, + ...labelOperations, + ...listOperations, + + // ---------------------------------- + // fields + // ---------------------------------- + ...attachmentFields, + ...boardFields, + ...boardMemberFields, + ...cardFields, + ...cardCommentFields, + ...checklistFields, + ...labelFields, + ...listFields, + ], + }; + + methods = { + listSearch: { + async searchBoards( + this: ILoadOptionsFunctions, + query?: string, + ): Promise { + if (!query) { + throw new NodeOperationError(this.getNode(), 'Query required for Trello search'); + } + const searchResults = await apiRequest.call( + this, + 'GET', + 'search', + {}, + { + query, + modelTypes: 'boards', + board_fields: 'name,url,desc', + // Enables partial word searching, only for the start of words though + partial: true, + // Seems like a good number since it isn't paginated. Default is 10. + boards_limit: 50, + }, + ); + return { + results: searchResults.boards.map((b: TrelloBoardType) => ({ + name: b.name, + value: b.id, + url: b.url, + description: b.desc, + })), + }; + }, + async searchCards( + this: ILoadOptionsFunctions, + query?: string, + ): Promise { + if (!query) { + throw new NodeOperationError(this.getNode(), 'Query required for Trello search'); + } + const searchResults = await apiRequest.call( + this, + 'GET', + 'search', + {}, + { + query, + modelTypes: 'cards', + board_fields: 'name,url,desc', + // Enables partial word searching, only for the start of words though + partial: true, + // Seems like a good number since it isn't paginated. Default is 10. + cards_limit: 50, + }, + ); + return { + results: searchResults.cards.map((b: TrelloBoardType) => ({ + name: b.name, + value: b.id, + url: b.url, + description: b.desc, + })), + }; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + const operation = this.getNodeParameter('operation', 0) as string; + const resource = this.getNodeParameter('resource', 0) as string; + const version = this.getNode().typeVersion; + + // For Post + let body: IDataObject; + // For Query string + let qs: IDataObject; + + let requestMethod: string; + let endpoint: string; + let returnAll = false; + let responseData; + + for (let i = 0; i < items.length; i++) { + try { + requestMethod = 'GET'; + endpoint = ''; + body = {}; + qs = {}; + + if (resource === 'board') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + endpoint = 'boards'; + + qs.name = this.getNodeParameter('name', i) as string; + qs.desc = this.getNodeParameter('description', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + let id: string; + + if (version === 2) { + id = this.getNodeParameter('boardIdRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + id = this.getNodeParameter('id', i) as string; + } + + endpoint = `boards/${id}`; + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + let id: string; + + if (version === 2) { + id = this.getNodeParameter('boardIdRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + id = this.getNodeParameter('id', i) as string; + } + + endpoint = `boards/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + requestMethod = 'PUT'; + + let id: string; + + if (version === 2) { + id = this.getNodeParameter('boardIdRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + id = this.getNodeParameter('id', i) as string; + } + + endpoint = `boards/${id}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(qs, updateFields); + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else if (resource === 'boardMember') { + if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll === false) { + qs.limit = this.getNodeParameter('limit', i) as number; + } + + endpoint = `boards/${id}/members`; + } else if (operation === 'add') { + // ---------------------------------- + // add + // ---------------------------------- + + requestMethod = 'PUT'; + + const id = this.getNodeParameter('id', i) as string; + const idMember = this.getNodeParameter('idMember', i) as string; + + endpoint = `boards/${id}/members/${idMember}`; + + qs.type = this.getNodeParameter('type', i) as string; + qs.allowBillableGuest = this.getNodeParameter( + 'additionalFields.allowBillableGuest', + i, + false, + ) as boolean; + } else if (operation === 'invite') { + // ---------------------------------- + // invite + // ---------------------------------- + + requestMethod = 'PUT'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `boards/${id}/members`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + qs.email = this.getNodeParameter('email', i) as string; + qs.type = additionalFields.type as string; + body.fullName = additionalFields.fullName as string; + } else if (operation === 'remove') { + // ---------------------------------- + // remove + // ---------------------------------- + + requestMethod = 'DELETE'; + + const id = this.getNodeParameter('id', i) as string; + const idMember = this.getNodeParameter('idMember', i) as string; + + endpoint = `boards/${id}/members/${idMember}`; + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else if (resource === 'card') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + endpoint = 'cards'; + + qs.idList = this.getNodeParameter('listId', i) as string; + + qs.name = this.getNodeParameter('name', i) as string; + qs.desc = this.getNodeParameter('description', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + let id: string; + + if (version === 2) { + id = this.getNodeParameter('cardIdRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + id = this.getNodeParameter('id', i) as string; + } + + endpoint = `cards/${id}`; + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + let id: string; + + if (version === 2) { + id = this.getNodeParameter('cardIdRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + id = this.getNodeParameter('id', i) as string; + } + + endpoint = `cards/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + requestMethod = 'PUT'; + + let id: string; + + if (version === 2) { + id = this.getNodeParameter('cardIdRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + id = this.getNodeParameter('id', i) as string; + } + + endpoint = `cards/${id}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(qs, updateFields); + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else if (resource === 'cardComment') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdCommentRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + qs.text = this.getNodeParameter('text', i) as string; + + requestMethod = 'POST'; + + endpoint = `cards/${cardId}/actions/comments`; + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdCommentRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const commentId = this.getNodeParameter('commentId', i) as string; + + endpoint = `/cards/${cardId}/actions/${commentId}/comments`; + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + requestMethod = 'PUT'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdCommentRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const commentId = this.getNodeParameter('commentId', i) as string; + + qs.text = this.getNodeParameter('text', i) as string; + + endpoint = `cards/${cardId}/actions/${commentId}/comments`; + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else if (resource === 'list') { + if (operation === 'archive') { + // ---------------------------------- + // archive + // ---------------------------------- + + requestMethod = 'PUT'; + + const id = this.getNodeParameter('id', i) as string; + qs.value = this.getNodeParameter('archive', i) as boolean; + + endpoint = `lists/${id}/closed`; + } else if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + endpoint = 'lists'; + + qs.idBoard = this.getNodeParameter('idBoard', i) as string; + + qs.name = this.getNodeParameter('name', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `lists/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (returnAll === false) { + qs.limit = this.getNodeParameter('limit', i) as number; + } + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `boards/${id}/lists`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'getCards') { + // ---------------------------------- + // getCards + // ---------------------------------- + + requestMethod = 'GET'; + + returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (returnAll === false) { + qs.limit = this.getNodeParameter('limit', i) as number; + } + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `lists/${id}/cards`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + requestMethod = 'PUT'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `lists/${id}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(qs, updateFields); + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else if (resource === 'attachment') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdAttachmentRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const url = this.getNodeParameter('url', i) as string; + + Object.assign(qs, { + url, + }); + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + endpoint = `cards/${cardId}/attachments`; + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdAttachmentRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `cards/${cardId}/attachments/${id}`; + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdAttachmentRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `cards/${cardId}/attachments/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdAttachmentRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + endpoint = `cards/${cardId}/attachments`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else if (resource === 'checklist') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdChecklistRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const name = this.getNodeParameter('name', i) as string; + + Object.assign(qs, { name }); + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + endpoint = `cards/${cardId}/checklists`; + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdChecklistRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `cards/${cardId}/checklists/${id}`; + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `checklists/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdChecklistRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + endpoint = `cards/${cardId}/checklists`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'getCheckItem') { + // ---------------------------------- + // getCheckItem + // ---------------------------------- + + requestMethod = 'GET'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdChecklistRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'createCheckItem') { + // ---------------------------------- + // createCheckItem + // ---------------------------------- + + requestMethod = 'POST'; + + const checklistId = this.getNodeParameter('checklistId', i) as string; + + endpoint = `checklists/${checklistId}/checkItems`; + + const name = this.getNodeParameter('name', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, { name, ...additionalFields }); + } else if (operation === 'deleteCheckItem') { + // ---------------------------------- + // deleteCheckItem + // ---------------------------------- + + requestMethod = 'DELETE'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdChecklistRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + } else if (operation === 'updateCheckItem') { + // ---------------------------------- + // updateCheckItem + // ---------------------------------- + + requestMethod = 'PUT'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdChecklistRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'completedCheckItems') { + // ---------------------------------- + // completedCheckItems + // ---------------------------------- + + requestMethod = 'GET'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdChecklistRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + endpoint = `cards/${cardId}/checkItemStates`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else if (resource === 'label') { + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + let idBoard: string; + if (version === 2) { + idBoard = this.getNodeParameter('boardIdLabelRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + idBoard = this.getNodeParameter('boardId', i) as string; + } + + const name = this.getNodeParameter('name', i) as string; + const color = this.getNodeParameter('color', i) as string; + + Object.assign(qs, { + idBoard, + name, + color, + }); + + endpoint = 'labels'; + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + let idBoard: string; + if (version === 2) { + idBoard = this.getNodeParameter('boardIdLabelRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + idBoard = this.getNodeParameter('boardId', i) as string; + } + + endpoint = `board/${idBoard}/labels`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + requestMethod = 'PUT'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(qs, updateFields); + } else if (operation === 'addLabel') { + // ---------------------------------- + // addLabel + // ---------------------------------- + + requestMethod = 'POST'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdLabelRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const id = this.getNodeParameter('id', i) as string; + + qs.value = id; + + endpoint = `/cards/${cardId}/idLabels`; + } else if (operation === 'removeLabel') { + // ---------------------------------- + // removeLabel + // ---------------------------------- + + requestMethod = 'DELETE'; + + let cardId: string; + if (version === 2) { + cardId = this.getNodeParameter('cardIdLabelRLC', i, undefined, { + extractValue: true, + }) as string; + } else { + cardId = this.getNodeParameter('cardId', i) as string; + } + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `/cards/${cardId}/idLabels/${id}`; + } else { + throw new NodeOperationError( + this.getNode(), + `The operation "${operation}" is not known!`, + { itemIndex: i }, + ); + } + } else { + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`, { + itemIndex: i, + }); + } + + // resources listed here do not support pagination so + // paginate them 'manually' + const skipPagination = ['list:getAll']; + + if (returnAll === true && !skipPagination.includes(`${resource}:${operation}`)) { + responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + } else { + responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); + if (returnAll === false && qs.limit) { + responseData = responseData.splice(0, qs.limit); + } + } + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + } catch (error) { + if (this.continueOnFail()) { + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: error.message }), + { itemData: { item: i } }, + ); + returnData.push(...executionData); + continue; + } + throw error; + } + } + + return this.prepareOutputData(returnData); } } diff --git a/packages/nodes-base/nodes/Trello/v1/GenericFunctions.ts b/packages/nodes-base/nodes/Trello/v1/GenericFunctions.ts deleted file mode 100644 index daee29cb49082..0000000000000 --- a/packages/nodes-base/nodes/Trello/v1/GenericFunctions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - IExecuteFunctions, - IHookFunctions, - ILoadOptionsFunctions, -} from 'n8n-core'; - -import { - OptionsWithUri, -} from 'request'; - -import { - IDataObject, - JsonObject, - NodeApiError, -} from 'n8n-workflow'; - -/** - * Make an API request to Trello - * - * @param {IHookFunctions} this - * @param {string} method - * @param {string} url - * @param {object} body - * @returns {Promise} - */ -export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise { // tslint:disable-line:no-any - query = query || {}; - - const options: OptionsWithUri = { - method, - body, - qs: query, - uri: `https://api.trello.com/1/${endpoint}`, - json: true, - }; - - try { - return await this.helpers.requestWithAuthentication.call(this, 'trelloApi', options); - } catch(error) { - throw new NodeApiError(this.getNode(), error as JsonObject); - } -} - -export async function apiRequestAllItems(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query: IDataObject = {}): Promise { // tslint:disable-line:no-any - - query.limit = 30; - - query.sort = '-id'; - - const returnData: IDataObject[] = []; - - let responseData; - - do { - responseData = await apiRequest.call(this, method, endpoint, body, query); - returnData.push.apply(returnData, responseData); - if (responseData.length !== 0) { - query.before = responseData[responseData.length - 1].id; - } - } while ( - query.limit <= responseData.length - ); - - return returnData; -} diff --git a/packages/nodes-base/nodes/Trello/v1/TrelloV1.node.ts b/packages/nodes-base/nodes/Trello/v1/TrelloV1.node.ts deleted file mode 100644 index 69f46cddf8a0d..0000000000000 --- a/packages/nodes-base/nodes/Trello/v1/TrelloV1.node.ts +++ /dev/null @@ -1,706 +0,0 @@ -import { IExecuteFunctions } from 'n8n-core'; - -import { - IDataObject, - INodeExecutionData, - INodeType, - INodeTypeBaseDescription, - INodeTypeDescription, - NodeOperationError, -} from 'n8n-workflow'; - -import { apiRequest, apiRequestAllItems } from '../GenericFunctions'; - -import { versionDescription } from './VersionDescription'; - -export class TrelloV1 implements INodeType { - description: INodeTypeDescription; - - constructor(baseDescription: INodeTypeBaseDescription) { - this.description = { - ...baseDescription, - ...versionDescription, - }; - } - - async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - - const operation = this.getNodeParameter('operation', 0) as string; - const resource = this.getNodeParameter('resource', 0) as string; - - // For Post - let body: IDataObject; - // For Query string - let qs: IDataObject; - - let requestMethod: string; - let endpoint: string; - let returnAll = false; - let responseData; - - for (let i = 0; i < items.length; i++) { - try { - requestMethod = 'GET'; - endpoint = ''; - body = {}; - qs = {}; - - if (resource === 'board') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - endpoint = 'boards'; - - qs.name = this.getNodeParameter('name', i) as string; - qs.desc = this.getNodeParameter('description', i) as string; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `boards/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `boards/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `boards/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'boardMember') { - if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - returnAll = this.getNodeParameter('returnAll', i) as boolean; - if (returnAll === false) { - qs.limit = this.getNodeParameter('limit', i) as number; - } - - endpoint = `boards/${id}/members`; - } else if (operation === 'add') { - // ---------------------------------- - // add - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - const idMember = this.getNodeParameter('idMember', i) as string; - - endpoint = `boards/${id}/members/${idMember}`; - - qs.type = this.getNodeParameter('type', i) as string; - qs.allowBillableGuest = this.getNodeParameter( - 'additionalFields.allowBillableGuest', - i, - false, - ) as boolean; - } else if (operation === 'invite') { - // ---------------------------------- - // invite - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `boards/${id}/members`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - - qs.email = this.getNodeParameter('email', i) as string; - qs.type = additionalFields.type as string; - body.fullName = additionalFields.fullName as string; - } else if (operation === 'remove') { - // ---------------------------------- - // remove - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('id', i) as string; - const idMember = this.getNodeParameter('idMember', i) as string; - - endpoint = `boards/${id}/members/${idMember}`; - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'card') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - endpoint = 'cards'; - - qs.idList = this.getNodeParameter('listId', i) as string; - - qs.name = this.getNodeParameter('name', i) as string; - qs.desc = this.getNodeParameter('description', i) as string; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'cardComment') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - const cardId = this.getNodeParameter('cardId', i) as string; - - qs.text = this.getNodeParameter('text', i) as string; - - requestMethod = 'POST'; - - endpoint = `cards/${cardId}/actions/comments`; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - const commentId = this.getNodeParameter('commentId', i) as string; - - endpoint = `/cards/${cardId}/actions/${commentId}/comments`; - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - const commentId = this.getNodeParameter('commentId', i) as string; - - qs.text = this.getNodeParameter('text', i) as string; - - endpoint = `cards/${cardId}/actions/${commentId}/comments`; - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'list') { - if (operation === 'archive') { - // ---------------------------------- - // archive - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - qs.value = this.getNodeParameter('archive', i) as boolean; - - endpoint = `lists/${id}/closed`; - } else if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - endpoint = 'lists'; - - qs.idBoard = this.getNodeParameter('idBoard', i) as string; - - qs.name = this.getNodeParameter('name', i) as string; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `lists/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - returnAll = this.getNodeParameter('returnAll', i) as boolean; - - if (returnAll === false) { - qs.limit = this.getNodeParameter('limit', i) as number; - } - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `boards/${id}/lists`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getCards') { - // ---------------------------------- - // getCards - // ---------------------------------- - - requestMethod = 'GET'; - - returnAll = this.getNodeParameter('returnAll', i) as boolean; - - if (returnAll === false) { - qs.limit = this.getNodeParameter('limit', i) as number; - } - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `lists/${id}/cards`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `lists/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'attachment') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const url = this.getNodeParameter('url', i) as string; - - Object.assign(qs, { - url, - }); - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - - endpoint = `cards/${cardId}/attachments`; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${cardId}/attachments/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${cardId}/attachments/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - endpoint = `cards/${cardId}/attachments`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'checklist') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const name = this.getNodeParameter('name', i) as string; - - Object.assign(qs, { name }); - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - - endpoint = `cards/${cardId}/checklists`; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${cardId}/checklists/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `checklists/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - endpoint = `cards/${cardId}/checklists`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getCheckItem') { - // ---------------------------------- - // getCheckItem - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const checkItemId = this.getNodeParameter('checkItemId', i) as string; - - endpoint = `cards/${cardId}/checkItem/${checkItemId}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'createCheckItem') { - // ---------------------------------- - // createCheckItem - // ---------------------------------- - - requestMethod = 'POST'; - - const checklistId = this.getNodeParameter('checklistId', i) as string; - - endpoint = `checklists/${checklistId}/checkItems`; - - const name = this.getNodeParameter('name', i) as string; - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, { name, ...additionalFields }); - } else if (operation === 'deleteCheckItem') { - // ---------------------------------- - // deleteCheckItem - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const checkItemId = this.getNodeParameter('checkItemId', i) as string; - - endpoint = `cards/${cardId}/checkItem/${checkItemId}`; - } else if (operation === 'updateCheckItem') { - // ---------------------------------- - // updateCheckItem - // ---------------------------------- - - requestMethod = 'PUT'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const checkItemId = this.getNodeParameter('checkItemId', i) as string; - - endpoint = `cards/${cardId}/checkItem/${checkItemId}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'completedCheckItems') { - // ---------------------------------- - // completedCheckItems - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - endpoint = `cards/${cardId}/checkItemStates`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'label') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - - const idBoard = this.getNodeParameter('boardId', i) as string; - const name = this.getNodeParameter('name', i) as string; - const color = this.getNodeParameter('color', i) as string; - - Object.assign(qs, { - idBoard, - name, - color, - }); - - endpoint = 'labels'; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `labels/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `labels/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const idBoard = this.getNodeParameter('boardId', i) as string; - - endpoint = `board/${idBoard}/labels`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `labels/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else if (operation === 'addLabel') { - // ---------------------------------- - // addLabel - // ---------------------------------- - - requestMethod = 'POST'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - qs.value = id; - - endpoint = `/cards/${cardId}/idLabels`; - } else if (operation === 'removeLabel') { - // ---------------------------------- - // removeLabel - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `/cards/${cardId}/idLabels/${id}`; - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else { - throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`, { - itemIndex: i, - }); - } - - // resources listed here do not support pagination so - // paginate them 'manually' - const skipPagination = ['list:getAll']; - - if (returnAll === true && !skipPagination.includes(`${resource}:${operation}`)) { - responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); - } else { - responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); - if (returnAll === false && qs.limit) { - responseData = responseData.splice(0, qs.limit); - } - } - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(responseData), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - } catch (error) { - if (this.continueOnFail()) { - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - continue; - } - throw error; - } - } - - return this.prepareOutputData(returnData); - } -} diff --git a/packages/nodes-base/nodes/Trello/v1/VersionDescription.ts b/packages/nodes-base/nodes/Trello/v1/VersionDescription.ts deleted file mode 100644 index 3e00901e75a8c..0000000000000 --- a/packages/nodes-base/nodes/Trello/v1/VersionDescription.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-filename-against-convention */ -import type { - INodeTypeDescription -} from 'n8n-workflow'; - -import { - attachmentFields, - attachmentOperations, -} from './AttachmentDescription'; - -import { - boardFields, - boardOperations, -} from './BoardDescription'; - -import { - boardMemberFields, - boardMemberOperations, -} from './BoardMemberDescription'; - -import { - cardFields, - cardOperations, -} from './CardDescription'; - -import { - cardCommentFields, - cardCommentOperations, -} from './CardCommentDescription'; - -import { - checklistFields, - checklistOperations, -} from './ChecklistDescription'; - -import { - labelFields, - labelOperations, -} from './LabelDescription'; - -import { - listFields, - listOperations, -} from './ListDescription'; - -export const versionDescription: INodeTypeDescription = { - displayName: 'Trello', - name: 'trello', - icon: 'file:trello.svg', - group: ['transform'], - version: 1, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Create, change and delete boards and cards', - defaults: { - name: 'Trello', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'trelloApi', - required: true, - }, - ], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Attachment', - value: 'attachment', - }, - { - name: 'Board', - value: 'board', - }, - { - name: 'Board Member', - value: 'boardMember', - }, - { - name: 'Card', - value: 'card', - }, - { - name: 'Card Comment', - value: 'cardComment', - }, - { - name: 'Checklist', - value: 'checklist', - }, - { - name: 'Label', - value: 'label', - }, - { - name: 'List', - value: 'list', - }, - ], - default: 'card', - }, - - // ---------------------------------- - // operations - // ---------------------------------- - ...attachmentOperations, - ...boardOperations, - ...boardMemberOperations, - ...cardOperations, - ...cardCommentOperations, - ...checklistOperations, - ...labelOperations, - ...listOperations, - - // ---------------------------------- - // fields - // ---------------------------------- - ...attachmentFields, - ...boardFields, - ...boardMemberFields, - ...cardFields, - ...cardCommentFields, - ...checklistFields, - ...labelFields, - ...listFields, - - ], -}; diff --git a/packages/nodes-base/nodes/Trello/v2/AttachmentDescription.ts b/packages/nodes-base/nodes/Trello/v2/AttachmentDescription.ts deleted file mode 100644 index 08b6d36cea5d8..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/AttachmentDescription.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { - INodeProperties, -} from 'n8n-workflow'; - -export const attachmentOperations: INodeProperties[] = [ - // ---------------------------------- - // attachment - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'attachment', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new attachment for a card', - action: 'Create an attachment', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete an attachment', - action: 'Delete an attachment', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of an attachment', - action: 'Get an attachment', - }, - { - name: 'Get All', - value: 'getAll', - description: 'Returns all attachments for the card', - action: 'Get all attachments', - }, - ], - default: 'getAll', - }, - -]; - -export const attachmentFields: INodeProperties[] = [ - - // ---------------------------------- - // attachment:create - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card to add attachment to', - }, - { - displayName: 'Source URL', - name: 'url', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The URL of the attachment to add', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'attachment', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'MIME Type', - name: 'mimeType', - type: 'string', - default: '', - placeholder: 'image/png', - description: 'The MIME type of the attachment to add', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'The name of the attachment to add', - }, - ], - }, - - // ---------------------------------- - // attachment:delete - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card that attachment belongs to', - }, - { - displayName: 'Attachment ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the attachment to delete', - }, - - // ---------------------------------- - // attachment:getAll - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card to get attachments', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'attachment', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // attachment:get - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card to get attachment', - }, - { - displayName: 'Attachment ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the attachment to get', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'attachment', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - -]; diff --git a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts b/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts deleted file mode 100644 index 91feabcae32cb..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/BoardDescription.ts +++ /dev/null @@ -1,595 +0,0 @@ -import { - IExecuteSingleFunctions, - INodeProperties, -} from 'n8n-workflow'; - -export const boardOperations: INodeProperties[] = [ - // ---------------------------------- - // board - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'board', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new board', - action: 'Create a board', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a board', - action: 'Delete a board', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a board', - action: 'Get a board', - }, - { - name: 'Update', - value: 'update', - description: 'Update a board', - action: 'Update a board', - }, - ], - default: 'create', - }, -]; - -export const boardFields: INodeProperties[] = [ - - // ---------------------------------- - // board:create - // ---------------------------------- - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'My board', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The name of the board', - }, - { - displayName: 'Description', - name: 'description', - type: 'string', - default: '', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The description of the board', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'board', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Aging', - name: 'prefs_cardAging', - type: 'options', - options: [ - { - name: 'Pirate', - value: 'pirate', - }, - { - name: 'Regular', - value: 'regular', - }, - ], - default: 'regular', - description: 'Determines the type of card aging that should take place on the board if card aging is enabled', - }, - { - displayName: 'Background', - name: 'prefs_background', - type: 'string', - default: 'blue', - description: 'The ID of a custom background or one of: blue, orange, green, red, purple, pink, lime, sky, grey', - }, - { - displayName: 'Comments', - name: 'prefs_comments', - type: 'options', - options: [ - { - name: 'Disabled', - value: 'disabled', - }, - { - name: 'Members', - value: 'members', - }, - { - name: 'Observers', - value: 'observers', - }, - { - name: 'Organization', - value: 'org', - }, - { - name: 'Public', - value: 'public', - }, - ], - default: 'members', - description: 'Who can comment on cards on this board', - }, - { - displayName: 'Covers', - name: 'prefs_cardCovers', - type: 'boolean', - default: true, - description: 'Whether card covers are enabled', - }, - { - displayName: 'Invitations', - name: 'prefs_invitations', - type: 'options', - options: [ - { - name: 'Admins', - value: 'admins', - }, - { - name: 'Members', - value: 'members', - }, - ], - default: 'members', - description: 'Determines what types of members can invite users to join', - }, - { - displayName: 'Keep From Source', - name: 'keepFromSource', - type: 'string', - default: 'none', - description: 'To keep cards from the original board pass in the value cards', - }, - { - displayName: 'Labels', - name: 'defaultLabels', - type: 'boolean', - default: true, - description: 'Whether to use the default set of labels', - }, - { - displayName: 'Lists', - name: 'defaultLists', - type: 'boolean', - default: true, - description: 'Whether to add the default set of lists to a board(To Do, Doing, Done).It is ignored if idBoardSource is provided', - }, - { - displayName: 'Organization ID', - name: 'idOrganization', - type: 'string', - default: '', - description: 'The ID or name of the team the board should belong to', - }, - { - displayName: 'Permission Level', - name: 'prefs_permissionLevel', - type: 'options', - options: [ - { - name: 'Organization', - value: 'org', - }, - { - name: 'Private', - value: 'private', - }, - { - name: 'Public', - value: 'public', - }, - ], - default: 'private', - description: 'The permissions level of the board', - }, - { - displayName: 'Power Ups', - name: 'powerUps', - type: 'options', - options: [ - { - name: 'All', - value: 'all', - }, - { - name: 'Calendar', - value: 'calendar', - }, - { - name: 'Card Aging', - value: 'cardAging', - }, - { - name: 'Recap', - value: 'recap', - }, - { - name: 'Voting', - value: 'voting', - }, - ], - default: 'all', - description: 'The Power-Ups that should be enabled on the new board', - }, - { - displayName: 'Self Join', - name: 'prefs_selfJoin', - type: 'boolean', - default: true, - description: 'Whether users can join the boards themselves or whether they have to be invited', - }, - { - displayName: 'Source IDs', - name: 'idBoardSource', - type: 'string', - default: '', - description: 'The ID of a board to copy into the new board', - }, - { - displayName: 'Voting', - name: 'prefs_voting', - type: 'options', - options: [ - { - name: 'Disabled', - value: 'disabled', - }, - { - name: 'Members', - value: 'members', - }, - { - name: 'Observers', - value: 'observers', - }, - { - name: 'Organization', - value: 'org', - }, - { - name: 'Public', - value: 'public', - }, - ], - default: 'disabled', - description: 'Who can vote on this board', - }, - ], - }, - - { - displayName: 'Board', - name: 'boardId', - type: 'resourceLocator', - default: { mode: 'list', value: '' }, - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The ID of the board', - modes: [ - // TODO: This rule should only apply for direct node properties, not their children - // eslint-disable-next-line n8n-nodes-base/node-param-default-missing - { - displayName: "From List", - name: "list", - type: "list", - hint: "Select a board from the list", - placeholder: "Choose...", - initType: "board", - entryTypes: { - board: { - selectable: true, - queryable: true, - data: { - request: { - baseURL: "https://api.trello.com/1", - url: "/members/me/boards", - method: "GET", - }, - }, - }, - }, - search: { - send: { - paginate: true, - }, - request: { - baseURL: "https://api.trello.com/1", - url: "/search", - qs: { - query: "={{$value}}", // TODO: See what goes here - modelTypes: "=boards", // Search only boards - idBoards: "=mine", // That belong to current user - }, - }, - }, - }, - // eslint-disable-next-line n8n-nodes-base/node-param-default-missing - { - displayName: 'ID', - name: 'id', - type: 'string', - hint: 'Enter Board Id', - validation: [ - { - type: 'regex', - properties: { - regex: '[a-zA-Z0-9]+', - errorMessage: 'ID value cannot be empty', - }, - }, - ], - placeholder: '45g950pa5n24054o43t453fe5', - url: '=https://api.trello.com/1/boards/{{$value}}', - }, - // eslint-disable-next-line n8n-nodes-base/node-param-default-missing - { - displayName: 'By URL', - name: 'url', - type: 'string', - hint: 'Enter board URL', - placeholder: 'https://trello.com/b/e123456/board-name', - validation: [ - { - type: 'regex', - properties: { - regex: 'http(s)?:\/\/trello.com\/b\/([a-zA-Z0-9]+)/[a-zA-Z0-9]+', - errorMessage: 'URL has to be in the format: http(s)://trello.com/b//', - }, - }, - ], - extractValue: { - type: 'regex', - regex: 'https:\/\/trello\.com\/b\/([a-zA-Z0-9]+)', - }, - }, - ], - }, - - { - displayName: 'Board', - name: 'boardIdDelete', - type: 'resourceLocator', - default: { mode: 'id', value: '' }, - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The ID of the board', - modes: [ - // TODO: This rule should only apply for direct node properties, not their children - // eslint-disable-next-line n8n-nodes-base/node-param-default-missing - { - displayName: 'ID', - name: 'id', - type: 'string', - hint: 'Enter Board Id', - validation: [ - { - type: 'regex', - properties: { - regex: '[a-zA-Z0-9]+', - errorMessage: 'ID value cannot be empty', - }, - }, - ], - placeholder: '45g950pa5n24054o43t453fe5', - url: '=https://api.trello.com/1/boards/{{$value}}', - }, - ], - }, - - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'board', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list: closed, dateLastActivity, dateLastView, desc, descData, idOrganization, invitations, invited, labelNames, memberships, name, pinned, powerUps, prefs, shortLink, shortUrl, starred, subscribed, URL.', - }, - { - displayName: 'Plugin Data', - name: 'pluginData', - type: 'boolean', - default: false, - description: 'Whether to include pluginData on the card with the response', - }, - ], - }, - - // ---------------------------------- - // board:update - // ---------------------------------- - { - displayName: 'Board ID', - name: 'boardIdUpdate', - type: 'resourceLocator', - default: { mode: 'list', value: '' }, - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The ID of the board to update', - modes: [ - // eslint-disable-next-line n8n-nodes-base/node-param-default-missing - { - displayName: "From List", - name: "list", - type: "list", - hint: "Select a board from the list", - placeholder: "Choose...", - initType: "board", - entryTypes: { - board: { - selectable: true, - queryable: true, - data: { - request: { - baseURL: "https://api.trello.com/1", - url: "/members/me/boards", - method: "GET", - }, - }, - }, - }, - search: { - send: { - paginate: true, - }, - request: { - baseURL: "https://api.trello.com/1", - url: "/search", - qs: { - query: "={{$value}}", // TODO: See what goes here - modelTypes: "=boards", // Search only boards - idBoards: "=mine", // That belong to current user - }, - }, - }, - }, - ], - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'board', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Closed', - name: 'closed', - type: 'boolean', - default: false, - description: 'Whether the board is closed', - }, - { - displayName: 'Description', - name: 'desc', - type: 'string', - default: '', - description: 'New description of the board', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'New name of the board', - }, - { - displayName: 'Organization ID', - name: 'idOrganization', - type: 'string', - default: '', - description: 'The ID of the team the board should be moved to', - }, - { - displayName: 'Subscribed', - name: 'subscribed', - type: 'boolean', - default: false, - description: 'Whether the acting user is subscribed to the board', - }, - ], - }, -]; diff --git a/packages/nodes-base/nodes/Trello/v2/BoardMemberDescription.ts b/packages/nodes-base/nodes/Trello/v2/BoardMemberDescription.ts deleted file mode 100644 index 54a720b2d349b..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/BoardMemberDescription.ts +++ /dev/null @@ -1,345 +0,0 @@ -import { - INodeProperties, -} from 'n8n-workflow'; - -export const boardMemberOperations: INodeProperties[] = [ - // ---------------------------------- - // boardMember - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'boardMember', - ], - }, - }, - options: [ - { - name: 'Add', - value: 'add', - description: 'Add member to board using member ID', - action: 'Add a board member', - }, - { - name: 'Get All', - value: 'getAll', - description: 'Get all members of a board', - action: 'Get all board members', - }, - { - name: 'Invite', - value: 'invite', - description: 'Invite a new member to a board via email', - action: 'Invite a board member', - }, - { - name: 'Remove', - value: 'remove', - description: 'Remove member from board using member ID', - action: 'Remove a board member', - }, - ], - default: 'add', - }, -]; - -export const boardMemberFields: INodeProperties[] = [ - // ---------------------------------- - // boardMember:getAll - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'boardMember', - ], - }, - }, - description: 'The ID of the board to get members from', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'boardMember', - ], - }, - }, - default: false, - description: 'Whether to return all results or only up to a given limit', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - typeOptions: { - minValue: 1, - }, - description: 'Max number of results to return', - default: 20, - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'boardMember', - ], - returnAll: [ - false, - ], - }, - }, - }, - - // ---------------------------------- - // boardMember:add - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'add', - ], - resource: [ - 'boardMember', - ], - }, - }, - description: 'The ID of the board to add member to', - }, - { - displayName: 'Member ID', - name: 'idMember', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'add', - ], - resource: [ - 'boardMember', - ], - }, - }, - description: 'The ID of the member to add to the board', - }, - { - displayName: 'Type', - name: 'type', - type: 'options', - required: true, - default: 'normal', - displayOptions: { - show: { - operation: [ - 'add', - ], - resource: [ - 'boardMember', - ], - }, - }, - options: [ - { - name: 'Normal', - value: 'normal', - description: 'Invite as normal member', - }, - { - name: 'Admin', - value: 'admin', - description: 'Invite as admin', - }, - { - name: 'Observer', - value: 'observer', - description: 'Invite as observer (Trello premium feature)', - }, - ], - description: 'Determines the type of membership the user being added should have', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - operation: [ - 'add', - ], - resource: [ - 'boardMember', - ], - }, - }, - options: [ - { - displayName: 'Allow Billable Guest', - name: 'allowBillableGuest', - type: 'boolean', - default: false, - description: 'Whether to allow organization admins to add multi-board guests onto a board', - }, - ], - }, - - // ---------------------------------- - // boardMember:invite - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'invite', - ], - resource: [ - 'boardMember', - ], - }, - }, - description: 'The ID of the board to invite member to', - }, - { - displayName: 'Email', - name: 'email', - type: 'string', - placeholder: 'name@email.com', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'invite', - ], - resource: [ - 'boardMember', - ], - }, - }, - description: 'The ID of the board to update', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'invite', - ], - resource: [ - 'boardMember', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Type', - name: 'type', - type: 'options', - default: 'normal', - options: [ - { - name: 'Normal', - value: 'normal', - description: 'Invite as normal member', - }, - { - name: 'Admin', - value: 'admin', - description: 'Invite as admin', - }, - { - name: 'Observer', - value: 'observer', - description: 'Invite as observer (Trello premium feature)', - }, - ], - description: 'Determines the type of membership the user being added should have', - }, - { - displayName: 'Full Name', - name: 'fullName', - type: 'string', - default: '', - description: 'The full name of the user to add as a member of the board. Must have a length of at least 1 and cannot begin nor end with a space.', - }, - ], - }, - - // ---------------------------------- - // boardMember:remove - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'remove', - ], - resource: [ - 'boardMember', - ], - }, - }, - description: 'The ID of the board to remove member from', - }, - { - displayName: 'Member ID', - name: 'idMember', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'remove', - ], - resource: [ - 'boardMember', - ], - }, - }, - description: 'The ID of the member to remove from the board', - }, -]; diff --git a/packages/nodes-base/nodes/Trello/v2/CardCommentDescription.ts b/packages/nodes-base/nodes/Trello/v2/CardCommentDescription.ts deleted file mode 100644 index 5a482232e605f..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/CardCommentDescription.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { - INodeProperties, -} from 'n8n-workflow'; - -export const cardCommentOperations: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'cardComment', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a comment on a card', - action: 'Create a card comment', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a comment from a card', - action: 'Delete a card comment', - }, - { - name: 'Update', - value: 'update', - description: 'Update a comment on a card', - action: 'Update a card comment', - }, - ], - default: 'create', - }, -]; - -export const cardCommentFields: INodeProperties[] = [ - // ---------------------------------- - // cardComment:create - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'cardComment', - ], - }, - }, - description: 'The ID of the card', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'cardComment', - ], - }, - }, - description: 'Text of the comment', - }, - - // ---------------------------------- - // cardComment:remove - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'cardComment', - ], - }, - }, - description: 'The ID of the card', - }, - { - displayName: 'Comment ID', - name: 'commentId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'cardComment', - ], - }, - }, - description: 'The ID of the comment to delete', - }, - - // ---------------------------------- - // cardComment:update - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'cardComment', - ], - }, - }, - description: 'The ID of the card to update', - }, - { - displayName: 'Comment ID', - name: 'commentId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'cardComment', - ], - }, - }, - description: 'The ID of the comment to delete', - }, - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'cardComment', - ], - }, - }, - description: 'Text of the comment', - }, -]; diff --git a/packages/nodes-base/nodes/Trello/v2/CardDescription.ts b/packages/nodes-base/nodes/Trello/v2/CardDescription.ts deleted file mode 100644 index 2caec8ca28337..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/CardDescription.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { - INodeProperties, -} from 'n8n-workflow'; - -export const cardOperations: INodeProperties[] = [ - // ---------------------------------- - // card - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'card', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new card', - action: 'Create a card', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a card', - action: 'Delete a card', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a card', - action: 'Get a card', - }, - { - name: 'Update', - value: 'update', - description: 'Update a card', - action: 'Update a card', - }, - ], - default: 'create', - }, -]; - -export const cardFields: INodeProperties[] = [ - // ---------------------------------- - // card:create - // ---------------------------------- - { - displayName: 'List ID', - name: 'listId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The ID of the list to create card in', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'My card', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The name of the card', - }, - { - displayName: 'Description', - name: 'description', - type: 'string', - default: '', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The description of the card', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Due Date', - name: 'due', - type: 'dateTime', - default: '', - description: 'A due date for the card', - }, - { - displayName: 'Due Complete', - name: 'dueComplete', - type: 'boolean', - default: false, - description: 'Whether the card is completed', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: 'The position of the new card. top, bottom, or a positive float.', - }, - { - displayName: 'Member IDs', - name: 'idMembers', - type: 'string', - default: '', - description: 'Comma-separated list of member IDs to add to the card', - }, - { - displayName: 'Label IDs', - name: 'idLabels', - type: 'string', - default: '', - description: 'Comma-separated list of label IDs to add to the card', - }, - { - displayName: 'URL Source', - name: 'urlSource', - type: 'string', - default: '', - description: 'A source URL to attach to card', - }, - { - displayName: 'Source ID', - name: 'idCardSource', - type: 'string', - default: '', - description: 'The ID of a card to copy into the new card', - }, - { - displayName: 'Keep From Source', - name: 'keepFromSource', - type: 'string', - default: 'all', - description: 'If using idCardSource you can specify which properties to copy over. all or comma-separated list of: attachments, checklists, comments, due, labels, members, stickers.', - }, - ], - }, - - // ---------------------------------- - // card:delete - // ---------------------------------- - { - displayName: 'Card ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The ID of the card to delete', - }, - - // ---------------------------------- - // card:get - // ---------------------------------- - { - displayName: 'Card ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The ID of the card to get', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'card', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - // eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-url - description: 'Fields to return. Either "all" or a comma-separated list: badges, checkItemStates, closed, dateLastActivity, desc, descData, due, email, idBoard, idChecklists, idLabels, idList, idMembers, idShort, idAttachmentCover, manualCoverAttachment, labels, name, pos, shortUrl, url.', - }, - { - displayName: 'Board', - name: 'board', - type: 'boolean', - default: false, - description: 'Whether to return the board object the card is on', - }, - { - displayName: 'Board Fields', - name: 'board_fields', - type: 'string', - default: 'all', - // eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-url - description: 'Fields to return. Either "all" or a comma-separated list: name, desc, descData, closed, idOrganization, pinned, url, prefs.', - }, - { - displayName: 'Custom Field Items', - name: 'customFieldItems', - type: 'boolean', - default: false, - description: 'Whether to include the customFieldItems', - }, - { - displayName: 'Members', - name: 'members', - type: 'boolean', - default: false, - description: 'Whether to return member objects for members on the card', - }, - { - displayName: 'Member Fields', - name: 'member_fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list: avatarHash, fullName, initials, username.', - }, - { - displayName: 'Plugin Data', - name: 'pluginData', - type: 'boolean', - default: false, - description: 'Whether to include pluginData on the card with the response', - }, - { - displayName: 'Stickers', - name: 'stickers', - type: 'boolean', - default: false, - description: 'Whether to include sticker models with the response', - }, - { - displayName: 'Sticker Fields', - name: 'sticker_fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of sticker fields.', - }, - ], - }, - - // ---------------------------------- - // card:update - // ---------------------------------- - { - displayName: 'Card ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The ID of the card to update', - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'card', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Attachment Cover', - name: 'idAttachmentCover', - type: 'string', - default: '', - description: 'The ID of the image attachment the card should use as its cover, or null for none', - }, - { - displayName: 'Board ID', - name: 'idBoard', - type: 'string', - default: '', - description: 'The ID of the board the card should be on', - }, - { - displayName: 'Closed', - name: 'closed', - type: 'boolean', - default: false, - description: 'Whether the board is closed', - }, - { - displayName: 'Description', - name: 'desc', - type: 'string', - default: '', - description: 'New description of the board', - }, - { - displayName: 'Due Date', - name: 'due', - type: 'dateTime', - default: '', - description: 'A due date for the card', - }, - { - displayName: 'Due Complete', - name: 'dueComplete', - type: 'boolean', - default: false, - description: 'Whether the card is completed', - }, - { - displayName: 'Label IDs', - name: 'idLabels', - type: 'string', - default: '', - description: 'Comma-separated list of label IDs to set on card', - }, - { - displayName: 'List ID', - name: 'idList', - type: 'string', - default: '', - description: 'The ID of the list the card should be in', - }, - { - displayName: 'Member IDs', - name: 'idMembers', - type: 'string', - default: '', - description: 'Comma-separated list of member IDs to set on card', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'New name of the board', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: 'The position of the card. top, bottom, or a positive float.', - }, - { - displayName: 'Subscribed', - name: 'subscribed', - type: 'boolean', - default: false, - description: 'Whether the acting user is subscribed to the board', - }, - ], - }, -]; diff --git a/packages/nodes-base/nodes/Trello/v2/ChecklistDescription.ts b/packages/nodes-base/nodes/Trello/v2/ChecklistDescription.ts deleted file mode 100644 index e8a1ace9e5a86..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/ChecklistDescription.ts +++ /dev/null @@ -1,616 +0,0 @@ -import { - INodeProperties, -} from 'n8n-workflow'; - -export const checklistOperations: INodeProperties[] = [ - // ---------------------------------- - // checklist - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'checklist', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new checklist', - action: 'Create a checklist', - }, - { - name: 'Create Checklist Item', - value: 'createCheckItem', - description: 'Create a checklist item', - action: 'Create checklist item', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a checklist', - action: 'Delete a checklist', - }, - { - name: 'Delete Checklist Item', - value: 'deleteCheckItem', - description: 'Delete a checklist item', - action: 'Delete a checklist item', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a checklist', - action: 'Get a checklist', - }, - { - name: 'Get All', - value: 'getAll', - description: 'Returns all checklists for the card', - action: 'Get all checklists', - }, - { - name: 'Get Checklist Items', - value: 'getCheckItem', - description: 'Get a specific checklist on a card', - action: 'Get checklist items', - }, - { - name: 'Get Completed Checklist Items', - value: 'completedCheckItems', - description: 'Get the completed checklist items on a card', - action: 'Get completed checklist items', - }, - { - name: 'Update Checklist Item', - value: 'updateCheckItem', - description: 'Update an item in a checklist on a card', - action: 'Update a checklist item', - }, - ], - default: 'getAll', - }, - -]; - -export const checklistFields: INodeProperties[] = [ - // ---------------------------------- - // checklist:create - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the card to add checklist to', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The URL of the checklist to add', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'checklist', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'ID Of Checklist Source', - name: 'idChecklistSource', - type: 'string', - default: '', - description: 'The ID of a source checklist to copy into the new one', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: '', - description: 'The position of the checklist on the card. One of: top, bottom, or a positive number.', - }, - ], - }, - - // ---------------------------------- - // checklist:delete - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the card that checklist belongs to', - }, - { - displayName: 'Checklist ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the checklist to delete', - }, - - - // ---------------------------------- - // checklist:getAll - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the card to get checklists', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'checklist', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // checklist:get - // ---------------------------------- - { - displayName: 'Checklist ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the checklist to get', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'checklist', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // checklist:createCheckItem - // ---------------------------------- - { - displayName: 'Checklist ID', - name: 'checklistId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'createCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the checklist to update', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'createCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The name of the new check item on the checklist', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'createCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Checked', - name: 'checked', - type: 'boolean', - default: false, - description: 'Whether the check item is already checked when created', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: '', - description: 'The position of the checklist on the card. One of: top, bottom, or a positive number.', - }, - ], - }, - - // ---------------------------------- - // checklist:deleteCheckItem - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'deleteCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the card that checklist belongs to', - }, - { - displayName: 'CheckItem ID', - name: 'checkItemId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'deleteCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the checklist item to delete', - }, - - // ---------------------------------- - // checklist:getCheckItem - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the card that checklist belongs to', - }, - { - displayName: 'CheckItem ID', - name: 'checkItemId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the checklist item to get', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'getCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // checklist:updateCheckItem - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'updateCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the card that checklist belongs to', - }, - { - displayName: 'CheckItem ID', - name: 'checkItemId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'updateCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the checklist item to update', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'updateCheckItem', - ], - resource: [ - 'checklist', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'The new name for the checklist item', - }, - { - displayName: 'State', - name: 'state', - type: 'options', - options: [ - { - name: 'Complete', - value: 'complete', - }, - { - name: 'Incomplete', - value: 'incomplete', - }, - ], - default: 'complete', - }, - { - displayName: 'Checklist ID', - name: 'checklistId', - type: 'string', - default: '', - description: 'The ID of the checklist this item is in', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: '', - description: 'The position of the checklist on the card. One of: top, bottom, or a positive number.', - }, - ], - }, - - // ---------------------------------- - // checklist:completedCheckItems - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'completedCheckItems', - ], - resource: [ - 'checklist', - ], - }, - }, - description: 'The ID of the card for checkItems', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'completedCheckItems', - ], - resource: [ - 'checklist', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of: "idCheckItem", "state".', - }, - ], - }, - -]; diff --git a/packages/nodes-base/nodes/Trello/v2/GenericFunctions.ts b/packages/nodes-base/nodes/Trello/v2/GenericFunctions.ts deleted file mode 100644 index daee29cb49082..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/GenericFunctions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - IExecuteFunctions, - IHookFunctions, - ILoadOptionsFunctions, -} from 'n8n-core'; - -import { - OptionsWithUri, -} from 'request'; - -import { - IDataObject, - JsonObject, - NodeApiError, -} from 'n8n-workflow'; - -/** - * Make an API request to Trello - * - * @param {IHookFunctions} this - * @param {string} method - * @param {string} url - * @param {object} body - * @returns {Promise} - */ -export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise { // tslint:disable-line:no-any - query = query || {}; - - const options: OptionsWithUri = { - method, - body, - qs: query, - uri: `https://api.trello.com/1/${endpoint}`, - json: true, - }; - - try { - return await this.helpers.requestWithAuthentication.call(this, 'trelloApi', options); - } catch(error) { - throw new NodeApiError(this.getNode(), error as JsonObject); - } -} - -export async function apiRequestAllItems(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query: IDataObject = {}): Promise { // tslint:disable-line:no-any - - query.limit = 30; - - query.sort = '-id'; - - const returnData: IDataObject[] = []; - - let responseData; - - do { - responseData = await apiRequest.call(this, method, endpoint, body, query); - returnData.push.apply(returnData, responseData); - if (responseData.length !== 0) { - query.before = responseData[responseData.length - 1].id; - } - } while ( - query.limit <= responseData.length - ); - - return returnData; -} diff --git a/packages/nodes-base/nodes/Trello/v2/LabelDescription.ts b/packages/nodes-base/nodes/Trello/v2/LabelDescription.ts deleted file mode 100644 index 68de293905fb4..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/LabelDescription.ts +++ /dev/null @@ -1,476 +0,0 @@ -import { - INodeProperties, -} from 'n8n-workflow'; - -export const labelOperations: INodeProperties[] = [ - // ---------------------------------- - // label - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'label', - ], - }, - }, - options: [ - { - name: 'Add to Card', - value: 'addLabel', - description: 'Add a label to a card', - action: 'Add a label to a card', - }, - { - name: 'Create', - value: 'create', - description: 'Create a new label', - action: 'Create a label', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a label', - action: 'Delete a label', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a label', - action: 'Get a label', - }, - { - name: 'Get All', - value: 'getAll', - description: 'Returns all labels for the board', - action: 'Get all labels', - }, - { - name: 'Remove From Card', - value: 'removeLabel', - description: 'Remove a label from a card', - action: 'Remove a label from a card', - }, - { - name: 'Update', - value: 'update', - description: 'Update a label', - action: 'Update a label', - }, - - ], - default: 'getAll', - }, - -]; - -export const labelFields: INodeProperties[] = [ - // ---------------------------------- - // label:create - // ---------------------------------- - { - displayName: 'Board ID', - name: 'labelBoardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the board to create the label on', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'label', - ], - }, - }, - description: 'Name for the label', - }, - { - displayName: 'Color', - name: 'color', - type: 'options', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'label', - ], - }, - }, - options: [ - { - name: 'Black', - value: 'black', - }, - { - name: 'Blue', - value: 'blue', - }, - { - name: 'Green', - value: 'green', - }, - { - name: 'Lime', - value: 'lime', - }, - { - name: 'Null', - value: 'null', - }, - { - name: 'Orange', - value: 'orange', - }, - { - name: 'Pink', - value: 'pink', - }, - { - name: 'Purple', - value: 'purple', - }, - { - name: 'Red', - value: 'red', - }, - { - name: 'Sky', - value: 'sky', - }, - { - name: 'Yellow', - value: 'yellow', - }, - ], - default: 'null', - description: 'The color for the label', - }, - - - // ---------------------------------- - // label:delete - // ---------------------------------- - { - displayName: 'Label ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the label to delete', - }, - - // ---------------------------------- - // label:getAll - // ---------------------------------- - { - displayName: 'Board ID', - name: 'labelBoardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the board to get label', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'label', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // label:get - // ---------------------------------- - { - displayName: 'Label ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'label', - ], - }, - }, - description: 'Get information about a label by ID', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'label', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // label:addLabel - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'addLabel', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the card to get label', - }, - { - displayName: 'Label ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'addLabel', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the label to add', - }, - - // ---------------------------------- - // label:removeLabel - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'removeLabel', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the card to remove label from', - }, - { - displayName: 'Label ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'removeLabel', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the label to remove', - }, - - // ---------------------------------- - // label:update - // ---------------------------------- - { - displayName: 'Label ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'label', - ], - }, - }, - description: 'The ID of the label to update', - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'label', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'Name of the label', - }, - { - displayName: 'Color', - name: 'color', - type: 'options', - options: [ - { - name: 'Black', - value: 'black', - }, - { - name: 'Blue', - value: 'blue', - }, - { - name: 'Green', - value: 'green', - }, - { - name: 'Lime', - value: 'lime', - }, - { - name: 'Null', - value: 'null', - }, - { - name: 'Orange', - value: 'orange', - }, - { - name: 'Pink', - value: 'pink', - }, - { - name: 'Purple', - value: 'purple', - }, - { - name: 'Red', - value: 'red', - }, - { - name: 'Sky', - value: 'sky', - }, - { - name: 'Yellow', - value: 'yellow', - }, - ], - default: 'null', - description: 'The color for the label', - }, - ], - }, - -]; diff --git a/packages/nodes-base/nodes/Trello/v2/ListDescription.ts b/packages/nodes-base/nodes/Trello/v2/ListDescription.ts deleted file mode 100644 index f7e8be365feba..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/ListDescription.ts +++ /dev/null @@ -1,477 +0,0 @@ -import { - INodeProperties, -} from 'n8n-workflow'; - -export const listOperations: INodeProperties[] = [ - // ---------------------------------- - // list - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: [ - 'list', - ], - }, - }, - options: [ - { - name: 'Archive', - value: 'archive', - description: 'Archive/Unarchive a list', - action: 'Archive/unarchive a list', - }, - { - name: 'Create', - value: 'create', - description: 'Create a new list', - action: 'Create a list', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a list', - action: 'Get a list', - }, - { - name: 'Get All', - value: 'getAll', - description: 'Get all the lists', - action: 'Get all lists', - }, - { - name: 'Get Cards', - value: 'getCards', - description: 'Get all the cards in a list', - action: 'Get all cards in a list', - }, - { - name: 'Update', - value: 'update', - description: 'Update a list', - action: 'Update a list', - }, - ], - default: 'create', - }, -]; - -export const listFields: INodeProperties[] = [ - // ---------------------------------- - // list:archive - // ---------------------------------- - { - displayName: 'List ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'archive', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the list to archive or unarchive', - }, - { - displayName: 'Archive', - name: 'archive', - type: 'boolean', - default: false, - displayOptions: { - show: { - operation: [ - 'archive', - ], - resource: [ - 'list', - ], - }, - }, - description: 'Whether the list should be archived or unarchived', - }, - - // ---------------------------------- - // list:create - // ---------------------------------- - { - displayName: 'Board ID', - name: 'idBoard', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the board the list should be created in', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'My list', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The name of the list', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'List Source', - name: 'idListSource', - type: 'string', - default: '', - description: 'ID of the list to copy into the new list', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: - 'The position of the new list. top, bottom, or a positive float.', - }, - ], - }, - - // ---------------------------------- - // list:getCards - // ---------------------------------- - { - displayName: 'List ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getCards', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the list to get cards', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - displayOptions: { - show: { - resource: [ - 'list', - ], - operation: [ - 'getCards', - ], - }, - }, - default: false, - description: 'Whether to return all results or only up to a given limit', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - typeOptions: { - minValue: 1, - }, - description: 'Max number of results to return', - default: 20, - displayOptions: { - show: { - resource: [ - 'list', - ], - operation: [ - 'getCards', - ], - returnAll: [ - false, - ], - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'getCards', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - // ---------------------------------- - // list:get - // ---------------------------------- - { - displayName: 'List ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the list to get', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // list:getAll - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the board', - }, - { - displayName: 'Return All', - name: 'returnAll', - type: 'boolean', - displayOptions: { - show: { - resource: [ - 'list', - ], - operation: [ - 'getAll', - ], - }, - }, - default: false, - description: 'Whether to return all results or only up to a given limit', - }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - typeOptions: { - minValue: 1, - }, - description: 'Max number of results to return', - default: 20, - displayOptions: { - show: { - resource: [ - 'list', - ], - operation: [ - 'getAll', - ], - returnAll: [ - false, - ], - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // list:update - // ---------------------------------- - { - displayName: 'List ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the list to update', - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Board ID', - name: 'idBoard', - type: 'string', - default: '', - description: 'ID of a board the list should be moved to', - }, - { - displayName: 'Closed', - name: 'closed', - type: 'boolean', - default: false, - description: 'Whether the list is closed', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'New name of the list', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: - 'The position of the list. top, bottom, or a positive float.', - }, - { - displayName: 'Subscribed', - name: 'subscribed', - type: 'boolean', - default: false, - description: 'Whether the acting user is subscribed to the list', - }, - ], - }, -]; diff --git a/packages/nodes-base/nodes/Trello/v2/TrelloV2.node.ts b/packages/nodes-base/nodes/Trello/v2/TrelloV2.node.ts deleted file mode 100644 index 76a725c856925..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/TrelloV2.node.ts +++ /dev/null @@ -1,712 +0,0 @@ -import { IExecuteFunctions } from 'n8n-core'; - -import { - IDataObject, - INodeExecutionData, - INodeType, - INodeTypeBaseDescription, - INodeTypeDescription, - NodeOperationError, -} from 'n8n-workflow'; - -import { apiRequest, apiRequestAllItems } from '../GenericFunctions'; - -import { versionDescription } from './VersionDescription'; - -export class TrelloV2 implements INodeType { - description: INodeTypeDescription; - - constructor(baseDescription: INodeTypeBaseDescription) { - this.description = { - ...baseDescription, - ...versionDescription, - }; - } - - async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - - const operation = this.getNodeParameter('operation', 0) as string; - const resource = this.getNodeParameter('resource', 0) as string; - - // For Post - let body: IDataObject; - // For Query string - let qs: IDataObject; - - let requestMethod: string; - let endpoint: string; - let returnAll = false; - let responseData; - - for (let i = 0; i < items.length; i++) { - try { - requestMethod = 'GET'; - endpoint = ''; - body = {}; - qs = {}; - - if (resource === 'board') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - endpoint = 'boards'; - - qs.name = this.getNodeParameter('name', i) as string; - qs.desc = this.getNodeParameter('description', i) as string; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('boardIdDelete', i, undefined, { - extractValue: true, - }) as string; - - endpoint = `boards/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('boardId', i, undefined, { - extractValue: true, - }) as string; - - endpoint = `boards/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('boardIdUpdate', i, undefined, { - extractValue: true, - }) as string; - - endpoint = `boards/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'boardMember') { - if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - returnAll = this.getNodeParameter('returnAll', i) as boolean; - if (returnAll === false) { - qs.limit = this.getNodeParameter('limit', i) as number; - } - - endpoint = `boards/${id}/members`; - } else if (operation === 'add') { - // ---------------------------------- - // add - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - const idMember = this.getNodeParameter('idMember', i) as string; - - endpoint = `boards/${id}/members/${idMember}`; - - qs.type = this.getNodeParameter('type', i) as string; - qs.allowBillableGuest = this.getNodeParameter( - 'additionalFields.allowBillableGuest', - i, - false, - ) as boolean; - } else if (operation === 'invite') { - // ---------------------------------- - // invite - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `boards/${id}/members`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - - qs.email = this.getNodeParameter('email', i) as string; - qs.type = additionalFields.type as string; - body.fullName = additionalFields.fullName as string; - } else if (operation === 'remove') { - // ---------------------------------- - // remove - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('id', i) as string; - const idMember = this.getNodeParameter('idMember', i) as string; - - endpoint = `boards/${id}/members/${idMember}`; - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'card') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - endpoint = 'cards'; - - qs.idList = this.getNodeParameter('listId', i) as string; - - qs.name = this.getNodeParameter('name', i) as string; - qs.desc = this.getNodeParameter('description', i) as string; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'cardComment') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - const cardId = this.getNodeParameter('cardId', i) as string; - - qs.text = this.getNodeParameter('text', i) as string; - - requestMethod = 'POST'; - - endpoint = `cards/${cardId}/actions/comments`; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - const commentId = this.getNodeParameter('commentId', i) as string; - - endpoint = `/cards/${cardId}/actions/${commentId}/comments`; - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - const commentId = this.getNodeParameter('commentId', i) as string; - - qs.text = this.getNodeParameter('text', i) as string; - - endpoint = `cards/${cardId}/actions/${commentId}/comments`; - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'list') { - if (operation === 'archive') { - // ---------------------------------- - // archive - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - qs.value = this.getNodeParameter('archive', i) as boolean; - - endpoint = `lists/${id}/closed`; - } else if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - endpoint = 'lists'; - - qs.idBoard = this.getNodeParameter('idBoard', i) as string; - - qs.name = this.getNodeParameter('name', i) as string; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `lists/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - returnAll = this.getNodeParameter('returnAll', i) as boolean; - - if (returnAll === false) { - qs.limit = this.getNodeParameter('limit', i) as number; - } - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `boards/${id}/lists`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getCards') { - // ---------------------------------- - // getCards - // ---------------------------------- - - requestMethod = 'GET'; - - returnAll = this.getNodeParameter('returnAll', i) as boolean; - - if (returnAll === false) { - qs.limit = this.getNodeParameter('limit', i) as number; - } - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `lists/${id}/cards`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `lists/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'attachment') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const url = this.getNodeParameter('url', i) as string; - - Object.assign(qs, { - url, - }); - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - - endpoint = `cards/${cardId}/attachments`; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${cardId}/attachments/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${cardId}/attachments/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - endpoint = `cards/${cardId}/attachments`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'checklist') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const name = this.getNodeParameter('name', i) as string; - - Object.assign(qs, { name }); - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - - endpoint = `cards/${cardId}/checklists`; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `cards/${cardId}/checklists/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `checklists/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - endpoint = `cards/${cardId}/checklists`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getCheckItem') { - // ---------------------------------- - // getCheckItem - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const checkItemId = this.getNodeParameter('checkItemId', i) as string; - - endpoint = `cards/${cardId}/checkItem/${checkItemId}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'createCheckItem') { - // ---------------------------------- - // createCheckItem - // ---------------------------------- - - requestMethod = 'POST'; - - const checklistId = this.getNodeParameter('checklistId', i) as string; - - endpoint = `checklists/${checklistId}/checkItems`; - - const name = this.getNodeParameter('name', i) as string; - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, { name, ...additionalFields }); - } else if (operation === 'deleteCheckItem') { - // ---------------------------------- - // deleteCheckItem - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const checkItemId = this.getNodeParameter('checkItemId', i) as string; - - endpoint = `cards/${cardId}/checkItem/${checkItemId}`; - } else if (operation === 'updateCheckItem') { - // ---------------------------------- - // updateCheckItem - // ---------------------------------- - - requestMethod = 'PUT'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const checkItemId = this.getNodeParameter('checkItemId', i) as string; - - endpoint = `cards/${cardId}/checkItem/${checkItemId}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'completedCheckItems') { - // ---------------------------------- - // completedCheckItems - // ---------------------------------- - - requestMethod = 'GET'; - - const cardId = this.getNodeParameter('cardId', i) as string; - - endpoint = `cards/${cardId}/checkItemStates`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else if (resource === 'label') { - if (operation === 'create') { - // ---------------------------------- - // create - // ---------------------------------- - - requestMethod = 'POST'; - - const idBoard = this.getNodeParameter('labelBoardId', i) as string; - const name = this.getNodeParameter('name', i) as string; - const color = this.getNodeParameter('color', i) as string; - - Object.assign(qs, { - idBoard, - name, - color, - }); - - endpoint = 'labels'; - } else if (operation === 'delete') { - // ---------------------------------- - // delete - // ---------------------------------- - - requestMethod = 'DELETE'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `labels/${id}`; - } else if (operation === 'get') { - // ---------------------------------- - // get - // ---------------------------------- - - requestMethod = 'GET'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `labels/${id}`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - Object.assign(qs, additionalFields); - } else if (operation === 'getAll') { - // ---------------------------------- - // getAll - // ---------------------------------- - - requestMethod = 'GET'; - - const idBoard = this.getNodeParameter('labelBoardId', i) as string; - - endpoint = `board/${idBoard}/labels`; - - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - - Object.assign(qs, additionalFields); - } else if (operation === 'update') { - // ---------------------------------- - // update - // ---------------------------------- - - requestMethod = 'PUT'; - - const id = this.getNodeParameter('id', i) as string; - - endpoint = `labels/${id}`; - - const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; - Object.assign(qs, updateFields); - } else if (operation === 'addLabel') { - // ---------------------------------- - // addLabel - // ---------------------------------- - - requestMethod = 'POST'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - qs.value = id; - - endpoint = `/cards/${cardId}/idLabels`; - } else if (operation === 'removeLabel') { - // ---------------------------------- - // removeLabel - // ---------------------------------- - - requestMethod = 'DELETE'; - - const cardId = this.getNodeParameter('cardId', i) as string; - const id = this.getNodeParameter('id', i) as string; - - endpoint = `/cards/${cardId}/idLabels/${id}`; - } else { - throw new NodeOperationError( - this.getNode(), - `The operation "${operation}" is not known!`, - { itemIndex: i }, - ); - } - } else { - throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`, { - itemIndex: i, - }); - } - - // resources listed here do not support pagination so - // paginate them 'manually' - const skipPagination = ['list:getAll']; - - if (returnAll === true && !skipPagination.includes(`${resource}:${operation}`)) { - responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); - } else { - responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); - if (returnAll === false && qs.limit) { - responseData = responseData.splice(0, qs.limit); - } - } - - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(responseData), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - } catch (error) { - if (this.continueOnFail()) { - const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: i } }, - ); - returnData.push(...executionData); - continue; - } - throw error; - } - } - - return this.prepareOutputData(returnData); - } -} diff --git a/packages/nodes-base/nodes/Trello/v2/VersionDescription.ts b/packages/nodes-base/nodes/Trello/v2/VersionDescription.ts deleted file mode 100644 index a2e6081526f90..0000000000000 --- a/packages/nodes-base/nodes/Trello/v2/VersionDescription.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-filename-against-convention */ -import type { - INodeTypeDescription, -} from 'n8n-workflow'; - -import { - attachmentFields, - attachmentOperations, -} from './AttachmentDescription'; - -import { - boardFields, - boardOperations, -} from './BoardDescription'; - -import { - boardMemberFields, - boardMemberOperations, -} from './BoardMemberDescription'; - -import { - cardFields, - cardOperations, -} from './CardDescription'; - -import { - cardCommentFields, - cardCommentOperations, -} from './CardCommentDescription'; - -import { - checklistFields, - checklistOperations, -} from './ChecklistDescription'; - -import { - labelFields, - labelOperations, -} from './LabelDescription'; - -import { - listFields, - listOperations, -} from './ListDescription'; - -export const versionDescription: INodeTypeDescription = { - displayName: 'Trello', - name: 'trello', - icon: 'file:trello.svg', - group: ['transform'], - version: 2, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Create, change and delete boards and cards', - defaults: { - name: 'Trello', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'trelloApi', - required: true, - }, - ], - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Attachment', - value: 'attachment', - }, - { - name: 'Board', - value: 'board', - }, - { - name: 'Board Member', - value: 'boardMember', - }, - { - name: 'Card', - value: 'card', - }, - { - name: 'Card Comment', - value: 'cardComment', - }, - { - name: 'Checklist', - value: 'checklist', - }, - { - name: 'Label', - value: 'label', - }, - { - name: 'List', - value: 'list', - }, - ], - default: 'card', - }, - - // ---------------------------------- - // operations - // ---------------------------------- - ...attachmentOperations, - ...boardOperations, - ...boardMemberOperations, - ...cardOperations, - ...cardCommentOperations, - ...checklistOperations, - ...labelOperations, - ...listOperations, - - // ---------------------------------- - // fields - // ---------------------------------- - ...attachmentFields, - ...boardFields, - ...boardMemberFields, - ...cardFields, - ...cardCommentFields, - ...checklistFields, - ...labelFields, - ...listFields, - - ], -}; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 262df3d5ee81b..62f8d8bda3784 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -987,6 +987,12 @@ export interface INodeProperties { modes?: INodePropertyMode[]; } +export interface INodePropertyModeTypeOptions { + searchListMethod?: string; // Supported by: options + searchFilterRequired?: boolean; + searchable?: boolean; +} + export interface INodePropertyMode { displayName: string; name: string; @@ -1011,7 +1017,9 @@ export interface INodePropertyMode { }; }; search?: INodePropertyRouting; + typeOptions?: INodePropertyModeTypeOptions; } + export interface INodePropertyModeValidation { type: string; properties: {}; @@ -1033,6 +1041,18 @@ export interface INodePropertyOptions { routing?: INodePropertyRouting; } +export interface INodeListSearchItems extends INodePropertyOptions { + breadcrumb?: string[]; + icon?: string; + url?: string; + disabled?: boolean; +} + +export interface INodeListSearchResult { + results: INodeListSearchItems[]; + paginationToken?: unknown; +} + export interface INodePropertyCollection { displayName: string; name: string; @@ -1089,6 +1109,13 @@ export interface INodeType { loadOptions?: { [key: string]: (this: ILoadOptionsFunctions) => Promise; }; + listSearch?: { + [key: string]: ( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, + ) => Promise; + }; credentialTest?: { // Contains a group of functins that test credentials. [functionName: string]: ICredentialTestFunction;