From 1cc820eb732b5885e462c4e932be9f168b906e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 29 Mar 2021 10:13:28 +0200 Subject: [PATCH] :hammer: Refactor Google Slides node --- .../nodes/Google/Slides/GenericFunctions.ts | 106 ++++++------ .../nodes/Google/Slides/GoogleSlides.node.ts | 151 ++++++++++++------ .../nodes/Google/Slides/googleslides.svg | 2 +- 3 files changed, 149 insertions(+), 110 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts index 35a8bad903..5818598e40 100644 --- a/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts @@ -16,72 +16,63 @@ 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 +export async function googleApiRequest( + this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, + resource: string, + body: IDataObject = {}, + qs: IDataObject = {}, +) { const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string; - const options: OptionsWithUri = { + const options: OptionsWithUri & { headers: IDataObject } = { headers: { 'Content-Type': 'application/json', }, method, body, qs, - uri: uri || `https://slides.googleapis.com${resource}`, - json: true + uri: `https://slides.googleapis.com/v1/presentations${resource}`, + json: true, }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + 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 credentials = this.getCredentials('googleApi') as { access_token: string, email: string, privateKey: string }; + const { access_token } = await getAccessToken.call(this, credentials); + options.headers.Authorization = `Bearer ${access_token}`; + return await this.helpers.request!(options); - 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, 'googleSlidesOAuth2Api', options); + + return await this.helpers.requestOAuth2!.call(this, 'googleSlidesOAuth2Api', options); + } + } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier + + if (error?.response?.body?.message) { throw new Error(`Google Slides 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 +function getAccessToken( + this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + { email, privateKey }: { email: string, privateKey: string }, +) { + // https://developers.google.com/identity/protocols/oauth2/service-account#httprest const scopes = [ 'https://www.googleapis.com/auth/drive', @@ -93,22 +84,22 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa 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, + iss: email, + sub: email, + scope: scopes.join(' '), + aud: 'https://oauth2.googleapis.com/token', + iat: now, + exp: now + 3600, }, - credentials.privateKey as string, + privateKey, { algorithm: 'RS256', header: { - 'kid': credentials.privateKey as string, - 'typ': 'JWT', - 'alg': 'RS256', + kid: privateKey, + typ: 'JWT', + alg: 'RS256', }, - } + }, ); const options: OptionsWithUri = { @@ -121,9 +112,8 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa assertion: signature, }, uri: 'https://oauth2.googleapis.com/token', - json: true + json: true, }; - //@ts-ignore - return this.helpers.request(options); + return this.helpers.request!(options); } diff --git a/packages/nodes-base/nodes/Google/Slides/GoogleSlides.node.ts b/packages/nodes-base/nodes/Google/Slides/GoogleSlides.node.ts index 49c46d716f..636c15db7e 100644 --- a/packages/nodes-base/nodes/Google/Slides/GoogleSlides.node.ts +++ b/packages/nodes-base/nodes/Google/Slides/GoogleSlides.node.ts @@ -1,9 +1,9 @@ - import { IExecuteFunctions, } from 'n8n-core'; import { + IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, @@ -26,7 +26,7 @@ export class GoogleSlides implements INodeType { group: ['input', 'output'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Read data from Google Slides', + description: 'Consume the Google Slides API', defaults: { name: 'Google Slides', color: '#edba25', @@ -63,14 +63,14 @@ export class GoogleSlides implements INodeType { name: 'authentication', type: 'options', options: [ - { - name: 'Service Account', - value: 'serviceAccount', - }, { name: 'OAuth2', value: 'oAuth2', }, + { + name: 'Service Account', + value: 'serviceAccount', + }, ], default: 'serviceAccount', }, @@ -79,17 +79,17 @@ export class GoogleSlides implements INodeType { name: 'resource', type: 'options', options: [ - { - name: 'Presentation', - value: 'presentation', - }, { name: 'Page', value: 'page', }, + { + name: 'Presentation', + value: 'presentation', + }, ], default: 'presentation', - description: 'The resource to operate on', + description: 'Resource to operate on', }, { displayName: 'Operation', @@ -106,6 +106,11 @@ export class GoogleSlides implements INodeType { value: 'get', description: 'Get a presentation', }, + { + name: 'Get Slides', + value: 'getSlides', + description: 'Get presentation slides', + }, ], displayOptions: { show: { @@ -115,7 +120,7 @@ export class GoogleSlides implements INodeType { }, }, default: 'create', - description: 'The operation to perform', + description: 'Operation to perform', }, { displayName: 'Operation', @@ -141,109 +146,153 @@ export class GoogleSlides implements INodeType { }, }, default: 'get', - description: 'The operation to perform', + description: 'Operation to perform', }, - - // ---------------------------------- - // All - // ---------------------------------- { displayName: 'Title', name: 'title', + description: 'Title of the presentation to create.', type: 'string', default: '', required: true, displayOptions: { show: { - operation: [ - 'create', - ], resource: [ 'presentation', ], + operation: [ + 'create', + ], }, }, }, { displayName: 'Presentation ID', name: 'presentationId', + description: 'ID of the presentation to retrieve. Found in the presentation URL:
https://docs.google.com/presentation/d/PRESENTATION_ID/edit', + placeholder: '1wZtNFZ8MO-WKrxhYrOLMvyiqSgFwdSz5vn8_l_7eNqw', type: 'string', default: '', required: true, displayOptions: { show: { - operation: [ - 'get', - 'getThumbnail' - ], resource: [ 'presentation', 'page', ], + operation: [ + 'get', + 'getThumbnail', + 'getSlides', + ], }, }, }, { displayName: 'Page Object ID', name: 'pageObjectId', + description: 'ID of the page object to retrieve.', type: 'string', default: '', required: true, displayOptions: { show: { + resource: [ + 'page', + ], operation: [ 'get', 'getThumbnail', ], - resource: [ - 'page', - ], }, }, }, ], }; - 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 === 'presentation') { + let responseData; + const returnData: IDataObject[] = []; + + for (let i = 0; i < items.length; i++) { + + if (resource === 'page') { + + // ********************************************************************* + // page + // ********************************************************************* + + if (operation === 'get') { + + // ---------------------------------- + // page: get + // ---------------------------------- + + const presentationId = this.getNodeParameter('presentationId', i) as string; + const pageObjectId = this.getNodeParameter('pageObjectId', i) as string; + responseData = await googleApiRequest.call(this, 'GET', `/${presentationId}/pages/${pageObjectId}`); + + } else if (operation === 'getThumbnail') { + + // ---------------------------------- + // page: getThumbnail + // ---------------------------------- + + const presentationId = this.getNodeParameter('presentationId', i) as string; + const pageObjectId = this.getNodeParameter('pageObjectId', i) as string; + responseData = await googleApiRequest.call(this, 'GET', `/${presentationId}/pages/${pageObjectId}/thumbnail`); + + } + + } else if (resource === 'presentation') { + + // ********************************************************************* + // presentation + // ********************************************************************* + if (operation === 'create') { - const title = this.getNodeParameter('title', i) as string; - let body = { - "title": title + + // ---------------------------------- + // presentation: create + // ---------------------------------- + + const body = { + title: this.getNodeParameter('title', i) as string, }; - const response = await googleApiRequest.call(this, 'POST', `/v1/presentations`, body); - responseData.push(response); + responseData = await googleApiRequest.call(this, 'POST', '', body); + } else if (operation === 'get') { - const presentationId = this.getNodeParameter('presentationId', i) as string; - const response = await googleApiRequest.call(this, 'GET', `/v1/presentations/${presentationId}`, {}); - responseData.push(response); - } - } else if (resource === 'page') { - if (operation === 'get') { - const presentationId = this.getNodeParameter('presentationId', i) as string; - const pageObjectId = this.getNodeParameter('pageObjectId', i) as string; + // ---------------------------------- + // presentation: get + // ---------------------------------- - const response = await googleApiRequest.call(this, 'GET', `/v1/presentations/${presentationId}/pages/${pageObjectId}`, {}); - responseData.push(response); - } else if (operation === 'getThumbnail') { const presentationId = this.getNodeParameter('presentationId', i) as string; - const pageObjectId = this.getNodeParameter('pageObjectId', i) as string; + responseData = await googleApiRequest.call(this, 'GET', `/${presentationId}`); + + } else if (operation === 'getSlides') { + + // ---------------------------------- + // presentation: getSlides + // ---------------------------------- + + const presentationId = this.getNodeParameter('presentationId', i) as string; + responseData = await googleApiRequest.call(this, 'GET', `/${presentationId}`, {}, { fields: 'slides' }); - const response = await googleApiRequest.call(this, 'GET', `/v1/presentations/${presentationId}/pages/${pageObjectId}/thumbnail`, {}); - responseData.push(response); } + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + } return [this.helpers.returnJsonArray(responseData)]; } diff --git a/packages/nodes-base/nodes/Google/Slides/googleslides.svg b/packages/nodes-base/nodes/Google/Slides/googleslides.svg index 1153cb7183..d1fcb9c20b 100644 --- a/packages/nodes-base/nodes/Google/Slides/googleslides.svg +++ b/packages/nodes-base/nodes/Google/Slides/googleslides.svg @@ -1 +1 @@ - \ No newline at end of file +