diff --git a/packages/nodes-base/credentials/RaindropOAuth2Api.credentials.ts b/packages/nodes-base/credentials/RaindropOAuth2Api.credentials.ts new file mode 100644 index 0000000000..f0c636ac63 --- /dev/null +++ b/packages/nodes-base/credentials/RaindropOAuth2Api.credentials.ts @@ -0,0 +1,47 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +// https://developer.raindrop.io/v1/authentication + +export class RaindropOAuth2Api implements ICredentialType { + name = 'raindropOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Raindrop OAuth2 API'; + documentationUrl = 'raindrop'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://raindrop.io/oauth/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://raindrop.io/oauth/access_token', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Raindrop/GenericFunctions.ts b/packages/nodes-base/nodes/Raindrop/GenericFunctions.ts new file mode 100644 index 0000000000..ff50006513 --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/GenericFunctions.ts @@ -0,0 +1,63 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, +} from 'n8n-workflow'; + +import { + OptionsWithUri, +} from 'request'; + +/** + * Make an authenticated API request to Raindrop. + */ +export async function raindropApiRequest( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + qs: IDataObject, + body: IDataObject, + option: IDataObject = {}, +) { + + const options: OptionsWithUri = { + headers: { + 'user-agent': 'n8n', + 'Content-Type': 'application/json', + }, + method, + uri: `https://api.raindrop.io/rest/v1${endpoint}`, + qs, + body, + json: true, + }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + + if (Object.keys(option).length !== 0) { + Object.assign(options, option); + } + + try { + return await this.helpers.requestOAuth2!.call(this, 'raindropOAuth2Api', options); + + } catch (error) { + + if (error?.response?.body?.errorMessage) { + const errorMessage = error?.response?.body?.errorMessage; + throw new Error(`Raindrop error response [${error.statusCode}]: ${errorMessage}`); + } + + throw error; + } +} diff --git a/packages/nodes-base/nodes/Raindrop/Raindrop.node.ts b/packages/nodes-base/nodes/Raindrop/Raindrop.node.ts new file mode 100644 index 0000000000..7792df40a8 --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/Raindrop.node.ts @@ -0,0 +1,452 @@ +import { + BINARY_ENCODING, + IExecuteFunctions, +} from 'n8n-core'; + +import { + IBinaryData, + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + isEmpty, + omit, +} from 'lodash'; + +import { + raindropApiRequest, +} from './GenericFunctions'; + +import { + bookmarkFields, + bookmarkOperations, + collectionFields, + collectionOperations, + tagFields, + tagOperations, + userFields, + userOperations, +} from './descriptions'; + +export class Raindrop implements INodeType { + description: INodeTypeDescription = { + displayName: 'Raindrop', + name: 'raindrop', + icon: 'file:raindrop.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume the Raindrop API', + defaults: { + name: 'Raindrop', + color: '#1988e0', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'raindropOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Bookmark', + value: 'bookmark', + }, + { + name: 'Collection', + value: 'collection', + }, + { + name: 'Tag', + value: 'tag', + }, + { + name: 'User', + value: 'user', + }, + ], + default: 'collection', + description: 'Resource to consume', + }, + ...bookmarkOperations, + ...bookmarkFields, + ...collectionOperations, + ...collectionFields, + ...tagOperations, + ...tagFields, + ...userOperations, + ...userFields, + ], + }; + + methods = { + loadOptions: { + async getCollections(this: ILoadOptionsFunctions) { + const responseData = await raindropApiRequest.call(this, 'GET', '/collections', {}, {}); + return responseData.items.map((item: { title: string, _id: string }) => ({ + name: item.title, + value: item._id, + })); + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let responseData; + const returnData: IDataObject[] = []; + + for (let i = 0; i < items.length; i++) { + if (resource === 'bookmark') { + + // ********************************************************************* + // bookmark + // ********************************************************************* + + // https://developer.raindrop.io/v1/raindrops + + if (operation === 'create') { + + // ---------------------------------- + // bookmark: create + // ---------------------------------- + + const body: IDataObject = { + link: this.getNodeParameter('link', i), + collection: { + '$id': this.getNodeParameter('collectionId', i), + }, + }; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (!isEmpty(additionalFields)) { + Object.assign(body, additionalFields); + } + + if (additionalFields.pleaseParse === true) { + body.pleaseParse = {}; + delete additionalFields.pleaseParse; + } + + if (additionalFields.tags) { + body.tags = (additionalFields.tags as string).split(',').map(tag => tag.trim()) as string[]; + } + + const endpoint = `/raindrop`; + responseData = await raindropApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData.item; + + } else if (operation === 'delete') { + + // ---------------------------------- + // bookmark: delete + // ---------------------------------- + + const bookmarkId = this.getNodeParameter('bookmarkId', i); + const endpoint = `/raindrop/${bookmarkId}`; + responseData = await raindropApiRequest.call(this, 'DELETE', endpoint, {}, {}); + + } else if (operation === 'get') { + + // ---------------------------------- + // bookmark: get + // ---------------------------------- + + const bookmarkId = this.getNodeParameter('bookmarkId', i); + const endpoint = `/raindrop/${bookmarkId}`; + responseData = await raindropApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.item; + + } else if (operation === 'getAll') { + + // ---------------------------------- + // bookmark: getAll + // ---------------------------------- + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const collectionId = this.getNodeParameter('collectionId', i); + const endpoint = `/raindrops/${collectionId}`; + responseData = await raindropApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.items; + + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', 0) as number; + responseData = responseData.slice(0, limit); + } + + } else if (operation === 'update') { + + // ---------------------------------- + // bookmark: update + // ---------------------------------- + + const bookmarkId = this.getNodeParameter('bookmarkId', i); + + const body = {} as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + Object.assign(body, updateFields); + + if (updateFields.collectionId) { + body.collection = { + '$id': updateFields.collectionId, + }; + delete updateFields.collectionId; + } + + if (updateFields.tags) { + body.tags = (updateFields.tags as string).split(',').map(tag => tag.trim()) as string[]; + } + + const endpoint = `/raindrop/${bookmarkId}`; + responseData = await raindropApiRequest.call(this, 'PUT', endpoint, {}, body); + responseData = responseData.item; + } + } else if (resource === 'collection') { + + // ********************************************************************* + // collection + // ********************************************************************* + + // https://developer.raindrop.io/v1/collections/methods + + if (operation === 'create') { + + // ---------------------------------- + // collection: create + // ---------------------------------- + + const body = { + title: this.getNodeParameter('title', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (!isEmpty(additionalFields)) { + Object.assign(body, additionalFields); + } + + if (additionalFields.cover) { + body.cover = [body.cover]; + } + + if (additionalFields.parentId) { + body['parent.$id'] = parseInt(additionalFields.parentId as string, 10) as number; + delete additionalFields.parentId; + } + + responseData = await raindropApiRequest.call(this, 'POST', `/collection`, {}, body); + responseData = responseData.item; + + } else if (operation === 'delete') { + + // ---------------------------------- + // collection: delete + // ---------------------------------- + + const collectionId = this.getNodeParameter('collectionId', i); + const endpoint = `/collection/${collectionId}`; + responseData = await raindropApiRequest.call(this, 'DELETE', endpoint, {}, {}); + + } else if (operation === 'get') { + + // ---------------------------------- + // collection: get + // ---------------------------------- + + const collectionId = this.getNodeParameter('collectionId', i); + const endpoint = `/collection/${collectionId}`; + responseData = await raindropApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.item; + + } else if (operation === 'getAll') { + + // ---------------------------------- + // collection: getAll + // ---------------------------------- + + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + const endpoint = this.getNodeParameter('type', i) === 'parent' + ? '/collections' + : '/collections/childrens'; + + responseData = await raindropApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.items; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', 0) as number; + responseData = responseData.slice(0, limit); + } + + } else if (operation === 'update') { + + // ---------------------------------- + // collection: update + // ---------------------------------- + + const collectionId = this.getNodeParameter('collectionId', i); + + const body = {} as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + if (updateFields.parentId) { + body['parent.$id'] = parseInt(updateFields.parentId as string, 10) as number; + delete updateFields.parentId; + } + + Object.assign(body, omit(updateFields, 'binaryPropertyName')); + + const endpoint = `/collection/${collectionId}`; + responseData = await raindropApiRequest.call(this, 'PUT', endpoint, {}, body); + responseData = responseData.item; + + // cover-specific endpoint + + if (updateFields.cover) { + + if (!items[i].binary) { + throw new Error('No binary data exists on item!'); + } + + if (!updateFields.cover) { + throw new Error('Please enter a binary property to upload a cover image.'); + } + + const binaryPropertyName = updateFields.cover as string; + + const binaryData = items[i].binary![binaryPropertyName] as IBinaryData; + + const formData = { + cover: { + value: Buffer.from(binaryData.data, BINARY_ENCODING), + options: { + filename: binaryData.fileName, + contentType: binaryData.mimeType, + }, + }, + }; + + const endpoint = `/collection/${collectionId}/cover`; + responseData = await raindropApiRequest.call(this, 'PUT', endpoint, {}, {}, { 'Content-Type': 'multipart/form-data', formData }); + responseData = responseData.item; + } + } + + } else if (resource === 'user') { + + // ********************************************************************* + // user + // ********************************************************************* + + // https://developer.raindrop.io/v1/user + + if (operation === 'get') { + + // ---------------------------------- + // user: get + // ---------------------------------- + + const self = this.getNodeParameter('self', i); + let endpoint = '/user'; + + if (self === false) { + const userId = this.getNodeParameter('userId', i); + endpoint += `/${userId}`; + } + + responseData = await raindropApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.user; + + } + + } else if (resource === 'tag') { + + // ********************************************************************* + // tag + // ********************************************************************* + + // https://developer.raindrop.io/v1/tags + + if (operation === 'delete') { + + // ---------------------------------- + // tag: delete + // ---------------------------------- + + let endpoint = `/tags`; + + const body: IDataObject = { + tags: (this.getNodeParameter('tags', i) as string).split(',') as string[], + }; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.collectionId) { + endpoint += `/${additionalFields.collectionId}`; + } + + responseData = await raindropApiRequest.call(this, 'DELETE', endpoint, {}, body); + + } else if (operation === 'getAll') { + + // ---------------------------------- + // tag: getAll + // ---------------------------------- + + let endpoint = `/tags`; + + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const filter = this.getNodeParameter('filters', i) as IDataObject; + + if (filter.collectionId) { + endpoint += `/${filter.collectionId}`; + } + + responseData = await raindropApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.items; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', 0) as number; + responseData = responseData.slice(0, limit); + } + } + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + } + + return [this.helpers.returnJsonArray(returnData)]; + + } +} diff --git a/packages/nodes-base/nodes/Raindrop/descriptions/BookmarkDescription.ts b/packages/nodes-base/nodes/Raindrop/descriptions/BookmarkDescription.ts new file mode 100644 index 0000000000..2fc575df85 --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/descriptions/BookmarkDescription.ts @@ -0,0 +1,320 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const bookmarkOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + }, + }, + }, +] as INodeProperties[]; + +export const bookmarkFields = [ + // ---------------------------------- + // bookmark: create + // ---------------------------------- + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'options', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'create', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + default: '', + }, + { + displayName: 'Link', + name: 'link', + type: 'string', + required: true, + default: '', + description: 'Link of the bookmark to be created.', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Important', + name: 'important', + type: 'boolean', + default: false, + description: 'Whether this bookmark is marked as favorite.', + }, + { + displayName: 'Order', + name: 'order', + type: 'number', + default: 0, + description: 'Sort order for the bookmark. For example, to move it to first place, enter 0.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'Bookmark tags. Multiple can be set separated by comma.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the bookmark to create.', + }, + ], + }, + + // ---------------------------------- + // bookmark: delete + // ---------------------------------- + { + displayName: 'Bookmark ID', + name: 'bookmarkId', + type: 'string', + default: '', + required: true, + description: 'The ID of the bookmark to delete.', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // bookmark: get + // ---------------------------------- + { + displayName: 'Bookmark ID', + name: 'bookmarkId', + type: 'string', + default: '', + required: true, + description: 'The ID of the bookmark to retrieve.', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // bookmark: getAll + // ---------------------------------- + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + default: [], + required: true, + description: 'The ID of the collection from which to retrieve all bookmarks.', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', + }, + + // ---------------------------------- + // bookmark: update + // ---------------------------------- + { + displayName: 'Bookmark ID', + name: 'bookmarkId', + type: 'string', + default: '', + required: true, + description: 'The ID of the bookmark to update.', + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'bookmark', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + default: '', + }, + { + displayName: 'Important', + name: 'important', + type: 'boolean', + default: false, + description: 'Whether this bookmark is marked as favorite.', + }, + { + displayName: 'Order', + name: 'order', + type: 'number', + default: 0, + description: 'For example if you want to move bookmark to the first place set this field to 0', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: 'Bookmark tags. Multiple can be set separated by comma.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the bookmark to be created.', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Raindrop/descriptions/CollectionDescription.ts b/packages/nodes-base/nodes/Raindrop/descriptions/CollectionDescription.ts new file mode 100644 index 0000000000..3f5cb17dbb --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/descriptions/CollectionDescription.ts @@ -0,0 +1,358 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const collectionOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'collection', + ], + }, + }, + }, +] as INodeProperties[]; + +export const collectionFields = [ + // ---------------------------------- + // collection: create + // ---------------------------------- + { + displayName: 'Title', + name: 'title', + type: 'string', + required: true, + default: '', + description: 'Title of the collection to create.', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Cover', + name: 'cover', + type: 'string', + default: '', + description: 'URL of an image to use as cover for the collection.', + }, + { + displayName: 'Public', + name: 'public', + type: 'boolean', + default: false, + description: 'Whether the collection will be accessible without authentication.', + }, + { + displayName: 'Parent ID', + name: 'parentId', + type: 'string', + default: '', + description: 'ID of this collection\'s parent collection, if it is a child collection.', + }, + { + displayName: 'Sort Order', + name: 'sort', + type: 'number', + default: 1, + description: 'Descending sort order of this collection. The number is the position of the collection
among all the collections with the same parent ID.', + }, + { + displayName: 'View', + name: 'view', + type: 'options', + default: 'list', + description: 'View style of this collection.', + options: [ + { + name: 'List', + value: 'list', + }, + { + name: 'Simple', + value: 'simple', + }, + { + name: 'Grid', + value: 'grid', + }, + { + name: 'Masonry', + value: 'Masonry', + }, + ], + }, + ], + }, + + // ---------------------------------- + // collection: delete + // ---------------------------------- + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'string', + default: '', + required: true, + description: 'The ID of the collection to delete.', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // collection: get + // ---------------------------------- + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'string', + default: '', + required: true, + description: 'The ID of the collection to retrieve.', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // collection: getAll + // ---------------------------------- + { + displayName: 'Type', + name: 'type', + type: 'options', + required: true, + default: 'parent', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + name: 'Parent', + value: 'parent', + description: 'Root-level collections.', + }, + { + name: 'Children', + value: 'children', + description: 'Nested collections.', + }, + ], + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', + }, + + // ---------------------------------- + // collection: update + // ---------------------------------- + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'string', + default: '', + required: true, + description: 'The ID of the collection to update.', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Cover', + name: 'cover', + type: 'string', + default: 'data', + placeholder: '', + description: 'Name of the binary property containing the data
for the image to upload as a cover.', + }, + { + displayName: 'Public', + name: 'public', + type: 'boolean', + default: false, + description: 'Whether the collection will be accessible without authentication.', + }, + { + displayName: 'Parent ID', + name: 'parentId', + type: 'string', + default: '', + description: 'ID of this collection\'s parent collection, if it is a child collection.', + }, + { + displayName: 'Sort Order', + name: 'sort', + type: 'number', + default: 1, + description: 'Descending sort order of this collection. The number is the position of the collection
among all the collections with the same parent ID.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the collection to update.', + }, + { + displayName: 'View', + name: 'view', + type: 'options', + default: 'list', + description: 'View style of this collection.', + options: [ + { + name: 'List', + value: 'list', + }, + { + name: 'Simple', + value: 'simple', + }, + { + name: 'Grid', + value: 'grid', + }, + { + name: 'Masonry', + value: 'Masonry', + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Raindrop/descriptions/TagDescription.ts b/packages/nodes-base/nodes/Raindrop/descriptions/TagDescription.ts new file mode 100644 index 0000000000..2579be7ca8 --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/descriptions/TagDescription.ts @@ -0,0 +1,155 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const tagOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get All', + value: 'getAll', + }, + ], + displayOptions: { + show: { + resource: [ + 'tag', + ], + }, + }, + }, +] as INodeProperties[]; + +export const tagFields = [ + // ---------------------------------- + // tag: delete + // ---------------------------------- + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'One or more tags to delete. Enter comma-separated values to delete multiple tags.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'delete', + ], + }, + }, + options: [ + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + default: '', + description: `It's possible to restrict remove action to just one collection. It's optional`, + }, + ], + }, + // ---------------------------------- + // tag: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + default: '', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Raindrop/descriptions/UserDescription.ts b/packages/nodes-base/nodes/Raindrop/descriptions/UserDescription.ts new file mode 100644 index 0000000000..8d452487a3 --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/descriptions/UserDescription.ts @@ -0,0 +1,71 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const userOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Get', + value: 'get', + }, + ], + displayOptions: { + show: { + resource: [ + 'user', + ], + }, + }, + }, +] as INodeProperties[]; + +export const userFields = [ + // ---------------------------------- + // user: get + // ---------------------------------- + { + displayName: 'Self', + name: 'self', + type: 'boolean', + default: true, + required: true, + description: 'Whether to return details on the logged-in user.', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + required: true, + description: 'The ID of the user to retrieve.', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'get', + ], + self: [ + false, + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Raindrop/descriptions/index.ts b/packages/nodes-base/nodes/Raindrop/descriptions/index.ts new file mode 100644 index 0000000000..42ab082f8a --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/descriptions/index.ts @@ -0,0 +1,4 @@ +export * from './BookmarkDescription'; +export * from './CollectionDescription'; +export * from './TagDescription'; +export * from './UserDescription'; diff --git a/packages/nodes-base/nodes/Raindrop/raindrop.svg b/packages/nodes-base/nodes/Raindrop/raindrop.svg new file mode 100644 index 0000000000..c226932017 --- /dev/null +++ b/packages/nodes-base/nodes/Raindrop/raindrop.svg @@ -0,0 +1 @@ + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2cb31b0484..f235403bab 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -184,6 +184,7 @@ "dist/credentials/QuickBaseApi.credentials.js", "dist/credentials/QuickBooksOAuth2Api.credentials.js", "dist/credentials/RabbitMQ.credentials.js", + "dist/credentials/RaindropOAuth2Api.credentials.js", "dist/credentials/RedditOAuth2Api.credentials.js", "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", @@ -441,6 +442,7 @@ "dist/nodes/QuickBase/QuickBase.node.js", "dist/nodes/QuickBooks/QuickBooks.node.js", "dist/nodes/RabbitMQ/RabbitMQ.node.js", + "dist/nodes/Raindrop/Raindrop.node.js", "dist/nodes/RabbitMQ/RabbitMQTrigger.node.js", "dist/nodes/ReadBinaryFile.node.js", "dist/nodes/ReadBinaryFiles.node.js",