From 08ba9a36a43b6c84f69bb04fa4d6419a7a4adddf Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 23 Sep 2024 21:19:16 +0100 Subject: [PATCH] feat(Brandfetch Node): Update to use new API (#10877) --- .../credentials/BrandfetchApi.credentials.ts | 23 ++++- .../nodes/Brandfetch/Brandfetch.node.ts | 87 +++++++------------ .../nodes/Brandfetch/GenericFunctions.ts | 39 ++++++--- .../Brandfetch/test/GenericFunctions.test.ts | 48 ++++++++++ 4 files changed, 129 insertions(+), 68 deletions(-) create mode 100644 packages/nodes-base/nodes/Brandfetch/test/GenericFunctions.test.ts diff --git a/packages/nodes-base/credentials/BrandfetchApi.credentials.ts b/packages/nodes-base/credentials/BrandfetchApi.credentials.ts index fe6fe4b620159..263cd4a114749 100644 --- a/packages/nodes-base/credentials/BrandfetchApi.credentials.ts +++ b/packages/nodes-base/credentials/BrandfetchApi.credentials.ts @@ -1,4 +1,9 @@ -import type { ICredentialType, INodeProperties } from 'n8n-workflow'; +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; export class BrandfetchApi implements ICredentialType { name = 'brandfetchApi'; @@ -16,4 +21,20 @@ export class BrandfetchApi implements ICredentialType { default: '', }, ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=Bearer {{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api.brandfetch.io', + url: '/v2/brands/brandfetch.com', + }, + }; } diff --git a/packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts b/packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts index 344c5f81d983f..237f39b9bfc8a 100644 --- a/packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts +++ b/packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts @@ -7,7 +7,7 @@ import type { } from 'n8n-workflow'; import { NodeConnectionType } from 'n8n-workflow'; -import { brandfetchApiRequest } from './GenericFunctions'; +import { brandfetchApiRequest, fetchAndPrepareBinaryData } from './GenericFunctions'; export class Brandfetch implements INodeType { description: INodeTypeDescription = { @@ -155,15 +155,11 @@ export class Brandfetch implements INodeType { const responseData: INodeExecutionData[] = []; for (let i = 0; i < length; i++) { try { + const domain = this.getNodeParameter('domain', i) as string; if (operation === 'logo') { - const domain = this.getNodeParameter('domain', i) as string; const download = this.getNodeParameter('download', i); - const body: IDataObject = { - domain, - }; - - const response = await brandfetchApiRequest.call(this, 'POST', '/logo', body); + const response = await brandfetchApiRequest.call(this, 'GET', `/brands/${domain}`); if (download) { const imageTypes = this.getNodeParameter('imageTypes', i) as string[]; @@ -182,29 +178,30 @@ export class Brandfetch implements INodeType { Object.assign(newItem.binary!, items[i].binary); } - newItem.json = response.response; + newItem.json = response.logos; for (const imageType of imageTypes) { for (const imageFormat of imageFormats) { - const url = response.response[imageType][ - imageFormat === 'png' ? 'image' : imageFormat - ] as string; - - if (url !== null) { - const data = await brandfetchApiRequest.call(this, 'GET', '', {}, {}, url, { - json: false, - encoding: null, - }); + const logoUrls = response.logos; - newItem.binary![`${imageType}_${imageFormat}`] = - await this.helpers.prepareBinaryData( - data as Buffer, - `${imageType}_${domain}.${imageFormat}`, - ); - - items[i] = newItem; + for (const logoUrl of logoUrls) { + if (logoUrl.type !== imageType) { + continue; + } + for (const logoFormats of logoUrl.formats) { + if (logoFormats.format === imageFormat && logoFormats.src !== null) { + await fetchAndPrepareBinaryData.call( + this, + imageType, + imageFormat, + logoFormats, + domain, + newItem, + ); + items[i] = newItem; + } + } } - items[i] = newItem; } } if (Object.keys(items[i].binary!).length === 0) { @@ -212,62 +209,38 @@ export class Brandfetch implements INodeType { } } else { const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response.response as IDataObject), + this.helpers.returnJsonArray(response.logos as IDataObject), { itemData: { item: i } }, ); responseData.push(...executionData); } } if (operation === 'color') { - const domain = this.getNodeParameter('domain', i) as string; - - const body: IDataObject = { - domain, - }; - - const response = await brandfetchApiRequest.call(this, 'POST', '/color', body); + const response = await brandfetchApiRequest.call(this, 'GET', `/brands/${domain}`); const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response as IDataObject), + this.helpers.returnJsonArray(response.colors as IDataObject), { itemData: { item: i } }, ); responseData.push(...executionData); } if (operation === 'font') { - const domain = this.getNodeParameter('domain', i) as string; - - const body: IDataObject = { - domain, - }; - - const response = await brandfetchApiRequest.call(this, 'POST', '/font', body); + const response = await brandfetchApiRequest.call(this, 'GET', `/brands/${domain}`); const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response as IDataObject), + this.helpers.returnJsonArray(response.fonts as IDataObject), { itemData: { item: i } }, ); responseData.push(...executionData); } if (operation === 'company') { - const domain = this.getNodeParameter('domain', i) as string; - - const body: IDataObject = { - domain, - }; - - const response = await brandfetchApiRequest.call(this, 'POST', '/company', body); + const response = await brandfetchApiRequest.call(this, 'GET', `/brands/${domain}`); const executionData = this.helpers.constructExecutionMetaData( - this.helpers.returnJsonArray(response as IDataObject), + this.helpers.returnJsonArray(response.company as IDataObject), { itemData: { item: i } }, ); responseData.push(...executionData); } if (operation === 'industry') { - const domain = this.getNodeParameter('domain', i) as string; - - const body: IDataObject = { - domain, - }; - - const response = await brandfetchApiRequest.call(this, 'POST', '/industry', body); + const response = await brandfetchApiRequest.call(this, 'GET', `/brands/${domain}`); const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(response as IDataObject), diff --git a/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts b/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts index 6a18a4579fc4b..654b0033ed15d 100644 --- a/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts @@ -2,10 +2,11 @@ import type { IDataObject, IExecuteFunctions, IHookFunctions, + IHttpRequestMethods, + IRequestOptions, ILoadOptionsFunctions, + INodeExecutionData, JsonObject, - IRequestOptions, - IHttpRequestMethods, } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow'; @@ -13,22 +14,17 @@ export async function brandfetchApiRequest( this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, resource: string, - body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}, ): Promise { try { - const credentials = await this.getCredentials('brandfetchApi'); let options: IRequestOptions = { - headers: { - 'x-api-key': credentials.apiKey, - }, - method, + method: method as IHttpRequestMethods, qs, body, - uri: uri || `https://api.brandfetch.io/v1${resource}`, + url: uri || `https://api.brandfetch.io/v2${resource}`, json: true, }; @@ -45,7 +41,11 @@ export async function brandfetchApiRequest( delete options.qs; } - const response = await this.helpers.request(options); + const response = await this.helpers.requestWithAuthentication.call( + this, + 'brandfetchApi', + options, + ); if (response.statusCode && response.statusCode !== 200) { throw new NodeApiError(this.getNode(), response as JsonObject); @@ -56,3 +56,22 @@ export async function brandfetchApiRequest( throw new NodeApiError(this.getNode(), error as JsonObject); } } + +export async function fetchAndPrepareBinaryData( + this: IExecuteFunctions, + imageType: string, + imageFormat: string, + logoFormats: IDataObject, + domain: string, + newItem: INodeExecutionData, +) { + const data = await brandfetchApiRequest.call(this, 'GET', '', {}, {}, logoFormats.src as string, { + json: false, + encoding: null, + }); + + newItem.binary![`${imageType}_${imageFormat}`] = await this.helpers.prepareBinaryData( + Buffer.from(data), + `${imageType}_${domain}.${imageFormat}`, + ); +} diff --git a/packages/nodes-base/nodes/Brandfetch/test/GenericFunctions.test.ts b/packages/nodes-base/nodes/Brandfetch/test/GenericFunctions.test.ts new file mode 100644 index 0000000000000..fbef339b58907 --- /dev/null +++ b/packages/nodes-base/nodes/Brandfetch/test/GenericFunctions.test.ts @@ -0,0 +1,48 @@ +import type { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IHttpRequestMethods, + INode, +} from 'n8n-workflow'; + +import { brandfetchApiRequest } from '../GenericFunctions'; + +export const node: INode = { + id: 'c4a5ca75-18c7-4cc8-bf7d-5d57bb7d84da', + name: 'Brandfetch', + type: 'n8n-nodes-base.Brandfetch', + typeVersion: 1, + position: [0, 0], + parameters: { + operation: 'font', + domain: 'n8n.io', + }, +}; + +describe('Brandfetch', () => { + describe('brandfetchApiRequest', () => { + const mockThis = { + helpers: { + requestWithAuthentication: jest.fn().mockResolvedValue({ statusCode: 200 }), + }, + getNode() { + return node; + }, + getNodeParameter: jest.fn(), + } as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions; + + it('should make an authenticated API request to Brandfetch', async () => { + const method: IHttpRequestMethods = 'GET'; + const resource = '/brands/n8n.io'; + + await brandfetchApiRequest.call(mockThis, method, resource); + + expect(mockThis.helpers.requestWithAuthentication).toHaveBeenCalledWith('brandfetchApi', { + method: 'GET', + url: 'https://api.brandfetch.io/v2/brands/n8n.io', + json: true, + }); + }); + }); +});