From f08d8c9375224929cdbffae26badc8d94c3c665a Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Mon, 19 Oct 2020 19:57:50 +0200 Subject: [PATCH 1/2] :sparkles: Add Google Books node --- .../GoogleBooksOAuth2Api.credentials.ts | 25 ++ .../nodes/Google/Books/GenericFunctions.ts | 127 ++++++ .../nodes/Google/Books/GoogleBooks.node.ts | 404 ++++++++++++++++++ .../nodes/Google/Books/googlebooks.svg | 37 ++ packages/nodes-base/package.json | 2 + 5 files changed, 595 insertions(+) create mode 100644 packages/nodes-base/credentials/GoogleBooksOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Google/Books/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts create mode 100644 packages/nodes-base/nodes/Google/Books/googlebooks.svg diff --git a/packages/nodes-base/credentials/GoogleBooksOAuth2Api.credentials.ts b/packages/nodes-base/credentials/GoogleBooksOAuth2Api.credentials.ts new file mode 100644 index 0000000000..d7453334b7 --- /dev/null +++ b/packages/nodes-base/credentials/GoogleBooksOAuth2Api.credentials.ts @@ -0,0 +1,25 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +const scopes = [ + 'https://www.googleapis.com/auth/books', +]; + +export class GoogleBooksOAuth2Api implements ICredentialType { + name = 'googleBooksOAuth2Api'; + extends = [ + 'googleOAuth2Api', + ]; + displayName = 'Google Books OAuth2 API'; + documentationUrl = 'google'; + properties = [ + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(' '), + }, + ]; +} diff --git a/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts new file mode 100644 index 0000000000..9961eb995f --- /dev/null +++ b/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts @@ -0,0 +1,127 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +import * as moment from 'moment-timezone'; + +import * as jwt from 'jsonwebtoken'; + +export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any + const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string; + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://www.googleapis.com/books/${resource}`, + json: true + }; + try { + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + if (Object.keys(body).length === 0) { + delete options.body; + } + + if (authenticationMethod === 'serviceAccount') { + const credentials = this.getCredentials('googleApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const { access_token } = await getAccessToken.call(this, credentials as IDataObject); + + options.headers!.Authorization = `Bearer ${access_token}`; + //@ts-ignore + return await this.helpers.request(options); + } else { + //@ts-ignore + return await this.helpers.requestOAuth2.call(this, 'googleBooksOAuth2Api', options); + } + } catch (error) { + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + throw new Error(`Google Books error response [${error.statusCode}]: ${error.response.body.message}`); + } + throw error; + } +} + +export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query.maxResults = 100; + + do { + responseData = await googleApiRequest.call(this, method, endpoint, body, query); + query.pageToken = responseData['nextPageToken']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['nextPageToken'] !== undefined && + responseData['nextPageToken'] !== '' + ); + + return returnData; +} + +function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise { + //https://developers.google.com/identity/protocols/oauth2/service-account#httprest + + const scopes = [ + 'https://www.googleapis.com/auth/books', + ]; + + const now = moment().unix(); + + const signature = jwt.sign( + { + 'iss': credentials.email as string, + 'sub': credentials.email as string, + 'scope': scopes.join(' '), + 'aud': `https://oauth2.googleapis.com/token`, + 'iat': now, + 'exp': now + 3600, + }, + credentials.privateKey as string, + { + algorithm: 'RS256', + header: { + 'kid': credentials.privateKey as string, + 'typ': 'JWT', + 'alg': 'RS256', + }, + } + ); + + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + form: { + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + assertion: signature, + }, + uri: 'https://oauth2.googleapis.com/token', + json: true + }; + + //@ts-ignore + return this.helpers.request(options); +} diff --git a/packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts b/packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts new file mode 100644 index 0000000000..a534f91b2b --- /dev/null +++ b/packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts @@ -0,0 +1,404 @@ + +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + googleApiRequest, +} from './GenericFunctions'; + +export interface IGoogleAuthCredentials { + email: string; + privateKey: string; +} + +export class GoogleBooks implements INodeType { + description: INodeTypeDescription = { + displayName: 'Google Books', + name: 'googleBooks', + icon: 'file:googlebooks.svg', + group: ['input', 'output'], + version: 1, + description: 'Read data from Google Books', + defaults: { + name: 'Google Books', + color: '#0aa55c', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'googleApi', + required: true, + displayOptions: { + show: { + authentication: [ + 'serviceAccount', + ], + }, + }, + }, + { + name: 'googleBooksOAuth2Api', + 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', + }, + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Bookshelf', + value: 'bookshelf', + description: 'This resource allows you to view bookshelf metadata as well as to modify the contents of a bookshelf', + }, + { + name: 'My Library', + value: 'library', + description: 'This resource allows you to view and modify the contents of an authenticated users own bookshelf', + }, + { + name: 'Volume', + value: 'volume', + description: 'This resource is used to perform a search or listing the contents of a bookshelf and to retrieve volumes from another users public bookshelves', + }, + ], + default: 'bookshelf', + description: 'The resource to operate on', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Get', + value: 'getShelf', + description: 'Retrieve a specific bookshelf resource for the specified user', + }, + { + name: 'List', + value: 'listShelves', + description: 'Retrieve a list of public bookshelf resource for the specified user', + }, + { + name: 'Get volumes', + value: 'getVolumes', + description: 'Retrieve volumes in a specific bookshelf for the specified user', + }, + ], + displayOptions: { + show: { + resource: [ + 'bookshelf', + ], + }, + }, + default: 'getShelf', + description: 'The operation to perform', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Add Volume', + value: 'addVolume', + description: 'Adds a volume to a bookshelf', + }, + { + name: 'Clear Volumes', + value: 'clearVolumes', + description: 'Clear all volumes from a bookshelf', + }, + { + name: 'Get', + value: 'getMyShelf', + description: 'Retrieve metadata for a specific bookshelf belonging to the authenticated user', + }, + { + name: 'List', + value: 'listMyShelves', + description: 'Retrieve a list of bookshelves belonging to the authenticated user', + }, + { + name: 'Move Volume', + value: 'moveVolume', + description: 'Move a volume within a bookshelf', + }, + { + name: 'Remove Volume', + value: 'removeVolume', + description: 'Remove a volume from a bookshelf', + }, + { + name: 'List Volumes', + value: 'listVolumes', + description: 'Get volume information for volumes on a bookshelf', + }, + ], + displayOptions: { + show: { + resource: [ + 'library', + ], + }, + }, + default: 'addVolume', + description: 'The operation to perform', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Get', + value: 'getVolume', + description: 'Retrieve a Volume resource based on ID', + }, + { + name: 'List', + value: 'bookSearch', + description: 'Perform a book search', + }, + ], + displayOptions: { + show: { + resource: [ + 'volume', + ], + }, + }, + default: 'getVolume', + description: 'The operation to perform', + }, + + // ---------------------------------- + // All + // ---------------------------------- + { + displayName: 'Search Query', + name: 'searchQuery', + type: 'string', + description: 'Full-text search query string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'bookSearch', + ], + resource: [ + 'volume', + ], + }, + }, + }, + { + displayName: 'User ID', + name: 'userId', + type: 'string', + description: 'ID of user', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getShelf', + 'listShelves', + 'getVolumes', + ], + resource: [ + 'bookshelf', + ], + }, + }, + }, + { + displayName: 'Bookshelf ID', + name: 'shelfId', + type: 'string', + description: 'ID of the bookshelf', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getShelf', + 'getVolumes', + 'addVolume', + 'clearVolumes', + 'getMyShelf', + 'moveVolume', + 'removeVolume', + 'listVolumes', + ], + resource: [ + 'bookshelf', + 'library', + ], + }, + }, + }, + { + displayName: 'Volume ID', + name: 'volumeId', + type: 'string', + description: 'ID of the volume', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getVolume', + 'addVolume', + 'moveVolume', + 'removeVolume', + ], + resource: [ + 'bookshelf', + 'library', + 'volume', + ], + }, + }, + }, + { + displayName: 'Volume Position', + name: 'volumePosition', + type: 'string', + description: 'Position on shelf to move the item (0 puts the item before the current first item, 1 puts it between the first and the second and so on) ', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'moveVolume', + ], + resource: [ + 'library', + ], + }, + }, + }, + ], + }; + + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const length = items.length as unknown as number; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + const responseData = []; + + for (let i=0; i < length; i++) { + if (resource === 'volume') { + if (operation === 'getVolume') { + const volumeId = this.getNodeParameter('volumeId', i) as string; + + const response = await googleApiRequest.call(this, 'GET', `v1/volumes/${volumeId}`, {}); + responseData.push(response); + } else if (operation === 'bookSearch') { + const searchQuery = this.getNodeParameter('searchQuery', i) as string; + + const response = await googleApiRequest.call(this, 'GET', `v1/volumes?q=${searchQuery}`, {}); + responseData.push(response); + } + } else if (resource === 'bookshelf') { + if (operation === 'getShelf') { + const userId = this.getNodeParameter('userId', i) as string; + const shelfId = this.getNodeParameter('shelfId', i) as string; + + const response = await googleApiRequest.call(this, 'GET', `v1/users/${userId}/bookshelves/${shelfId}`, {}); + responseData.push(response); + } else if (operation === 'listShelves') { + const userId = this.getNodeParameter('userId', i) as string; + + const response = await googleApiRequest.call(this, 'GET', `v1/users/${userId}/bookshelves`, {}); + responseData.push(response); + } else if (operation === 'getVolumes') { + const userId = this.getNodeParameter('userId', i) as string; + const shelfId = this.getNodeParameter('shelfId', i) as string; + + const response = await googleApiRequest.call(this, 'GET', `v1/users/${userId}/bookshelves/${shelfId}/volumes`, {}); + responseData.push(response); + } + } else if (resource === 'library') { + if (operation === 'addVolume') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + const volumeId = this.getNodeParameter('volumeId', i) as string; + + const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/addVolume`, {volumeId}); + responseData.push(response); + } else if (operation === 'clearVolumes') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + + const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/clearVolumes`, {}); + responseData.push(response); + } else if (operation === 'getMyShelf') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + + const response = await googleApiRequest.call(this, 'GET', `v1/mylibrary/bookshelves/${shelfId}`, {}); + responseData.push(response); + } else if (operation === 'listMyShelves') { + const response = await googleApiRequest.call(this, 'GET', `v1/mylibrary/bookshelves`, {}); + responseData.push(response); + } else if (operation === 'moveVolume') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + const volumeId = this.getNodeParameter('volumeId', i) as string; + const volumePosition = this.getNodeParameter('volumePosition', i) as string; + + const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/moveVolume`, {shelfId, volumeId, volumePosition}); + responseData.push(response); + } else if (operation === 'removeVolume') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + const volumeId = this.getNodeParameter('volumeId', i) as string; + + const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/removeVolume`, {shelfId, volumeId}); + responseData.push(response); + } else if (operation === 'listVolumes') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + + const response = await googleApiRequest.call(this, 'GET', `v1/mylibrary/bookshelves/${shelfId}/volumes`, {}); + responseData.push(response); + } + } + } + return [this.helpers.returnJsonArray(responseData)]; + } +} diff --git a/packages/nodes-base/nodes/Google/Books/googlebooks.svg b/packages/nodes-base/nodes/Google/Books/googlebooks.svg new file mode 100644 index 0000000000..752d18d549 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Books/googlebooks.svg @@ -0,0 +1,37 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 7822f86a66..a79d5b0a5b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -75,6 +75,7 @@ "dist/credentials/GitlabOAuth2Api.credentials.js", "dist/credentials/GmailOAuth2Api.credentials.js", "dist/credentials/GoogleApi.credentials.js", + "dist/credentials/GoogleBooksOAuth2Api.credentials.js", "dist/credentials/GoogleCalendarOAuth2Api.credentials.js", "dist/credentials/GoogleContactsOAuth2Api.credentials.js", "dist/credentials/GoogleDriveOAuth2Api.credentials.js", @@ -264,6 +265,7 @@ "dist/nodes/Github/GithubTrigger.node.js", "dist/nodes/Gitlab/Gitlab.node.js", "dist/nodes/Gitlab/GitlabTrigger.node.js", + "dist/nodes/Google/Books/GoogleBooks.node.js", "dist/nodes/Google/Calendar/GoogleCalendar.node.js", "dist/nodes/Google/Contacts/GoogleContacts.node.js", "dist/nodes/Google/Drive/GoogleDrive.node.js", From bc887640fc0cb8aa5a18bb7d5762170274f8dea9 Mon Sep 17 00:00:00 2001 From: ricardo Date: Tue, 20 Oct 2020 18:09:13 -0400 Subject: [PATCH 2/2] :zap: Improvements to #1083 --- .../nodes/Google/Books/GenericFunctions.ts | 27 +- .../nodes/Google/Books/GoogleBooks.node.ts | 348 +++++++++++------- 2 files changed, 233 insertions(+), 142 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts index 9961eb995f..d357f307aa 100644 --- a/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts @@ -46,16 +46,31 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF const { access_token } = await getAccessToken.call(this, credentials as IDataObject); options.headers!.Authorization = `Bearer ${access_token}`; + console.log(options); //@ts-ignore return await this.helpers.request(options); } else { + console.log(this.getCredentials('googleBooksOAuth2Api')); //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'googleBooksOAuth2Api', options); } } catch (error) { - if (error.response && error.response.body && error.response.body.message) { + if (error.response && error.response.body && error.response.body.error) { + + let errors; + + if (error.response.body.error.errors) { + errors = error.response.body.error.errors; + + errors = errors.map((e: IDataObject) => e.message).join('|'); + + } else { + errors = error.response.body.error.message; + } // Try to return the error prettier - throw new Error(`Google Books error response [${error.statusCode}]: ${error.response.body.message}`); + throw new Error( + `Google Books error response [${error.statusCode}]: ${errors}` + ); } throw error; } @@ -66,15 +81,13 @@ export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOp const returnData: IDataObject[] = []; let responseData; - query.maxResults = 100; + query.maxResults = 40; do { responseData = await googleApiRequest.call(this, method, endpoint, body, query); - query.pageToken = responseData['nextPageToken']; - returnData.push.apply(returnData, responseData[propertyName]); + returnData.push.apply(returnData, responseData[propertyName] || []); } while ( - responseData['nextPageToken'] !== undefined && - responseData['nextPageToken'] !== '' + returnData.length < responseData.totalItems ); return returnData; diff --git a/packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts b/packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts index a534f91b2b..eab2ad39e6 100644 --- a/packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts +++ b/packages/nodes-base/nodes/Google/Books/GoogleBooks.node.ts @@ -4,6 +4,7 @@ import { } from 'n8n-core'; import { + IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, @@ -11,6 +12,7 @@ import { import { googleApiRequest, + googleApiRequestAllItems, } from './GenericFunctions'; export interface IGoogleAuthCredentials { @@ -26,9 +28,10 @@ export class GoogleBooks implements INodeType { group: ['input', 'output'], version: 1, description: 'Read data from Google Books', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', defaults: { name: 'Google Books', - color: '#0aa55c', + color: '#4198e6', }, inputs: ['main'], outputs: ['main'], @@ -81,17 +84,14 @@ export class GoogleBooks implements INodeType { { name: 'Bookshelf', value: 'bookshelf', - description: 'This resource allows you to view bookshelf metadata as well as to modify the contents of a bookshelf', }, { - name: 'My Library', - value: 'library', - description: 'This resource allows you to view and modify the contents of an authenticated users own bookshelf', + name: 'Bookshelf Volume', + value: 'bookshelfVolume', }, { name: 'Volume', value: 'volume', - description: 'This resource is used to perform a search or listing the contents of a bookshelf and to retrieve volumes from another users public bookshelves', }, ], default: 'bookshelf', @@ -104,18 +104,13 @@ export class GoogleBooks implements INodeType { options: [ { name: 'Get', - value: 'getShelf', + value: 'get', description: 'Retrieve a specific bookshelf resource for the specified user', }, { - name: 'List', - value: 'listShelves', - description: 'Retrieve a list of public bookshelf resource for the specified user', - }, - { - name: 'Get volumes', - value: 'getVolumes', - description: 'Retrieve volumes in a specific bookshelf for the specified user', + name: 'Get All', + value: 'getAll', + description: 'Get all public bookshelf resource for the specified user', }, ], displayOptions: { @@ -125,7 +120,7 @@ export class GoogleBooks implements INodeType { ], }, }, - default: 'getShelf', + default: 'get', description: 'The operation to perform', }, { @@ -134,49 +129,39 @@ export class GoogleBooks implements INodeType { type: 'options', options: [ { - name: 'Add Volume', - value: 'addVolume', - description: 'Adds a volume to a bookshelf', + name: 'Add', + value: 'add', + description: 'Add a volume to a bookshelf', }, { - name: 'Clear Volumes', - value: 'clearVolumes', - description: 'Clear all volumes from a bookshelf', + name: 'Clear', + value: 'clear', + description: 'Clears all volumes from a bookshelf', }, { - name: 'Get', - value: 'getMyShelf', - description: 'Retrieve metadata for a specific bookshelf belonging to the authenticated user', + name: 'Get All', + value: 'getAll', + description: 'Get all volumes in a specific bookshelf for the specified user', }, { - name: 'List', - value: 'listMyShelves', - description: 'Retrieve a list of bookshelves belonging to the authenticated user', + name: 'Move', + value: 'move', + description: 'Moves a volume within a bookshelf', }, { - name: 'Move Volume', - value: 'moveVolume', - description: 'Move a volume within a bookshelf', - }, - { - name: 'Remove Volume', - value: 'removeVolume', - description: 'Remove a volume from a bookshelf', - }, - { - name: 'List Volumes', - value: 'listVolumes', - description: 'Get volume information for volumes on a bookshelf', + name: 'Remove', + value: 'remove', + description: 'Removes a volume from a bookshelf', }, ], displayOptions: { show: { resource: [ - 'library', + 'bookshelfVolume', ], }, }, - default: 'addVolume', + default: 'getAll', description: 'The operation to perform', }, { @@ -186,13 +171,13 @@ export class GoogleBooks implements INodeType { options: [ { name: 'Get', - value: 'getVolume', - description: 'Retrieve a Volume resource based on ID', + value: 'get', + description: 'Get a volume resource based on ID', }, { - name: 'List', - value: 'bookSearch', - description: 'Perform a book search', + name: 'Get All', + value: 'getAll', + description: 'Get all volumes filtered by query', }, ], displayOptions: { @@ -202,9 +187,28 @@ export class GoogleBooks implements INodeType { ], }, }, - default: 'getVolume', + default: 'get', description: 'The operation to perform', }, + { + displayName: 'My Library', + name: 'myLibrary', + type: 'boolean', + default: false, + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'getAll', + ], + resource: [ + 'bookshelf', + 'bookshelfVolume' + ], + }, + }, + }, // ---------------------------------- // All @@ -219,7 +223,7 @@ export class GoogleBooks implements INodeType { displayOptions: { show: { operation: [ - 'bookSearch', + 'getAll', ], resource: [ 'volume', @@ -237,12 +241,17 @@ export class GoogleBooks implements INodeType { displayOptions: { show: { operation: [ - 'getShelf', - 'listShelves', - 'getVolumes', + 'get', + 'getAll', ], resource: [ 'bookshelf', + 'bookshelfVolume', + ], + }, + hide: { + myLibrary: [ + true, ], }, }, @@ -257,18 +266,15 @@ export class GoogleBooks implements INodeType { displayOptions: { show: { operation: [ - 'getShelf', - 'getVolumes', - 'addVolume', - 'clearVolumes', - 'getMyShelf', - 'moveVolume', - 'removeVolume', - 'listVolumes', + 'get', + 'add', + 'clear', + 'move', + 'remove', ], resource: [ 'bookshelf', - 'library', + 'bookshelfVolume' ], }, }, @@ -283,15 +289,13 @@ export class GoogleBooks implements INodeType { displayOptions: { show: { operation: [ - 'getVolume', - 'addVolume', - 'moveVolume', - 'removeVolume', + 'add', + 'move', + 'remove', + 'get', ], resource: [ - 'bookshelf', - 'library', - 'volume', + 'bookshelfVolume', ], }, }, @@ -306,97 +310,171 @@ export class GoogleBooks implements INodeType { displayOptions: { show: { operation: [ - 'moveVolume', + 'move', ], resource: [ - 'library', + 'bookshelfVolume', ], }, }, }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + 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: { + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 40, + }, + default: 40, + description: 'How many results to return.', + }, ], }; - async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const length = items.length as unknown as number; - + const returnData: IDataObject[] = []; const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; - const responseData = []; + const qs: IDataObject = {}; + let responseData; - for (let i=0; i < length; i++) { + for (let i = 0; i < length; i++) { if (resource === 'volume') { - if (operation === 'getVolume') { + if (operation === 'get') { const volumeId = this.getNodeParameter('volumeId', i) as string; - - const response = await googleApiRequest.call(this, 'GET', `v1/volumes/${volumeId}`, {}); - responseData.push(response); - } else if (operation === 'bookSearch') { + responseData = await googleApiRequest.call(this, 'GET', `v1/volumes/${volumeId}`, {}); + } else if (operation === 'getAll') { const searchQuery = this.getNodeParameter('searchQuery', i) as string; - - const response = await googleApiRequest.call(this, 'GET', `v1/volumes?q=${searchQuery}`, {}); - responseData.push(response); + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll) { + responseData = await googleApiRequestAllItems.call(this, 'items', 'GET', `v1/volumes?q=${searchQuery}`, {}); + } else { + qs.maxResults = this.getNodeParameter('limit', i) as number; + responseData = await googleApiRequest.call(this, 'GET', `v1/volumes?q=${searchQuery}`, {}, qs); + responseData = responseData.items || []; + } } - } else if (resource === 'bookshelf') { - if (operation === 'getShelf') { - const userId = this.getNodeParameter('userId', i) as string; + } + + if (resource === 'bookshelf') { + if (operation === 'get') { const shelfId = this.getNodeParameter('shelfId', i) as string; + const myLibrary = this.getNodeParameter('myLibrary', i) as boolean; + let endpoint; + if (myLibrary === false) { + const userId = this.getNodeParameter('userId', i) as string; + endpoint = `v1/users/${userId}/bookshelves/${shelfId}`; + } else { + endpoint = `v1/mylibrary/bookshelves/${shelfId}`; + } - const response = await googleApiRequest.call(this, 'GET', `v1/users/${userId}/bookshelves/${shelfId}`, {}); - responseData.push(response); - } else if (operation === 'listShelves') { - const userId = this.getNodeParameter('userId', i) as string; - - const response = await googleApiRequest.call(this, 'GET', `v1/users/${userId}/bookshelves`, {}); - responseData.push(response); - } else if (operation === 'getVolumes') { - const userId = this.getNodeParameter('userId', i) as string; - const shelfId = this.getNodeParameter('shelfId', i) as string; - - const response = await googleApiRequest.call(this, 'GET', `v1/users/${userId}/bookshelves/${shelfId}/volumes`, {}); - responseData.push(response); + responseData = await googleApiRequest.call(this, 'GET', endpoint, {}); + } else if (operation === 'getAll') { + const myLibrary = this.getNodeParameter('myLibrary', i) as boolean; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + let endpoint; + if (myLibrary === false) { + const userId = this.getNodeParameter('userId', i) as string; + endpoint = `v1/users/${userId}/bookshelves`; + } else { + endpoint = `v1/mylibrary/bookshelves`; + } + if (returnAll) { + responseData = await googleApiRequestAllItems.call(this, 'items', 'GET', endpoint, {}); + } else { + qs.maxResults = this.getNodeParameter('limit', i) as number; + responseData = await googleApiRequest.call(this, 'GET', endpoint, {}, qs); + responseData = responseData.items || []; + } } - } else if (resource === 'library') { - if (operation === 'addVolume') { + } + + if (resource === 'bookshelfVolume') { + if (operation === 'add') { const shelfId = this.getNodeParameter('shelfId', i) as string; const volumeId = this.getNodeParameter('volumeId', i) as string; - - const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/addVolume`, {volumeId}); - responseData.push(response); - } else if (operation === 'clearVolumes') { - const shelfId = this.getNodeParameter('shelfId', i) as string; - - const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/clearVolumes`, {}); - responseData.push(response); - } else if (operation === 'getMyShelf') { - const shelfId = this.getNodeParameter('shelfId', i) as string; - - const response = await googleApiRequest.call(this, 'GET', `v1/mylibrary/bookshelves/${shelfId}`, {}); - responseData.push(response); - } else if (operation === 'listMyShelves') { - const response = await googleApiRequest.call(this, 'GET', `v1/mylibrary/bookshelves`, {}); - responseData.push(response); - } else if (operation === 'moveVolume') { - const shelfId = this.getNodeParameter('shelfId', i) as string; - const volumeId = this.getNodeParameter('volumeId', i) as string; - const volumePosition = this.getNodeParameter('volumePosition', i) as string; - - const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/moveVolume`, {shelfId, volumeId, volumePosition}); - responseData.push(response); - } else if (operation === 'removeVolume') { - const shelfId = this.getNodeParameter('shelfId', i) as string; - const volumeId = this.getNodeParameter('volumeId', i) as string; - - const response = await googleApiRequest.call(this, 'POST', `v1/mylibrary/bookshelves/${shelfId}/removeVolume`, {shelfId, volumeId}); - responseData.push(response); - } else if (operation === 'listVolumes') { - const shelfId = this.getNodeParameter('shelfId', i) as string; - - const response = await googleApiRequest.call(this, 'GET', `v1/mylibrary/bookshelves/${shelfId}/volumes`, {}); - responseData.push(response); + const body: IDataObject = { + volumeId, + }; + responseData = await googleApiRequest.call(this, 'POST', `mylibrary/bookshelves/${shelfId}/addVolume`, body); } + + if (operation === 'clear') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + responseData = await googleApiRequest.call(this, 'POST', `mylibrary/bookshelves/${shelfId}/clearVolumes`); + } + + if (operation === 'getAll') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const myLibrary = this.getNodeParameter('myLibrary', i) as boolean; + let endpoint; + if (myLibrary === false) { + const userId = this.getNodeParameter('userId', i) as string; + endpoint = `v1/users/${userId}/bookshelves/${shelfId}/volumes`; + } else { + endpoint = `v1/mylibrary/bookshelves/${shelfId}/volumes`; + } + if (returnAll) { + responseData = await googleApiRequestAllItems.call(this, 'items', 'GET', endpoint, {}); + } else { + qs.maxResults = this.getNodeParameter('limit', i) as number; + responseData = await googleApiRequest.call(this, 'GET', endpoint, {}, qs); + responseData = responseData.items || []; + } + } + + if (operation === 'move') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + const volumeId = this.getNodeParameter('volumeId', i) as string; + const volumePosition = this.getNodeParameter('volumePosition', i) as number; + const body: IDataObject = { + volumeId, + volumePosition, + }; + responseData = await googleApiRequest.call(this, 'POST', `mylibrary/bookshelves/${shelfId}/moveVolume`, body); + } + + if (operation === 'move') { + const shelfId = this.getNodeParameter('shelfId', i) as string; + const volumeId = this.getNodeParameter('volumeId', i) as string; + const body: IDataObject = { + volumeId, + }; + responseData = await googleApiRequest.call(this, 'POST', `mylibrary/bookshelves/${shelfId}/removeVolume`, body); + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); } } return [this.helpers.returnJsonArray(responseData)];