From a14d85ea481b8227ba306f07e13263f45eafa6ca Mon Sep 17 00:00:00 2001 From: Jonathan Bennetts Date: Sat, 14 May 2022 09:39:28 +0100 Subject: [PATCH] fix(Ghost Node): Fix post tags and add credential tests (#3278) * Renamed Tag IDs to Tags and changed the value to tag.name * Updated credentials to use new system * Nodelinter changes --- .../credentials/GhostAdminApi.credentials.ts | 27 +++++++++++++ .../GhostContentApi.credentials.ts | 17 ++++++++ .../nodes/Ghost/GenericFunctions.ts | 37 ++++++------------ packages/nodes-base/nodes/Ghost/Ghost.node.ts | 7 ++-- .../nodes-base/nodes/Ghost/PostDescription.ts | 36 +++++++++-------- packages/nodes-base/nodes/Ghost/ghost.png | Bin 1224 -> 0 bytes packages/nodes-base/nodes/Ghost/ghost.svg | 1 + 7 files changed, 79 insertions(+), 46 deletions(-) delete mode 100644 packages/nodes-base/nodes/Ghost/ghost.png create mode 100644 packages/nodes-base/nodes/Ghost/ghost.svg diff --git a/packages/nodes-base/credentials/GhostAdminApi.credentials.ts b/packages/nodes-base/credentials/GhostAdminApi.credentials.ts index d4472f61e7..13c6ebb793 100644 --- a/packages/nodes-base/credentials/GhostAdminApi.credentials.ts +++ b/packages/nodes-base/credentials/GhostAdminApi.credentials.ts @@ -1,8 +1,12 @@ import { + ICredentialDataDecryptedObject, + ICredentialTestRequest, ICredentialType, + IHttpRequestOptions, INodeProperties, } from 'n8n-workflow'; +import jwt from 'jsonwebtoken'; export class GhostAdminApi implements ICredentialType { name = 'ghostAdminApi'; displayName = 'Ghost Admin API'; @@ -22,4 +26,27 @@ export class GhostAdminApi implements ICredentialType { default: '', }, ]; + + async authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise { + const [id, secret] = (credentials.apiKey as string).split(':'); + const token = jwt.sign({}, Buffer.from(secret, 'hex'), { + keyid: id, + algorithm: 'HS256', + expiresIn: '5m', + audience: `/v2/admin/`, + }); + + requestOptions.headers = { + ...requestOptions.headers, + Authorization: `Ghost ${token}`, + }; + return requestOptions; + } + + test: ICredentialTestRequest = { + request: { + baseURL: '={{$credentials.url}}', + url: '/ghost/api/v2/admin/pages/', + }, + }; } diff --git a/packages/nodes-base/credentials/GhostContentApi.credentials.ts b/packages/nodes-base/credentials/GhostContentApi.credentials.ts index b484a07f05..9701880373 100644 --- a/packages/nodes-base/credentials/GhostContentApi.credentials.ts +++ b/packages/nodes-base/credentials/GhostContentApi.credentials.ts @@ -1,5 +1,8 @@ import { + ICredentialDataDecryptedObject, + ICredentialTestRequest, ICredentialType, + IHttpRequestOptions, INodeProperties, } from 'n8n-workflow'; @@ -22,4 +25,18 @@ export class GhostContentApi implements ICredentialType { default: '', }, ]; + async authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise { + requestOptions.qs = { + ...requestOptions.qs, + 'key': credentials.apiKey, + }; + return requestOptions; + } + test: ICredentialTestRequest = { + request: { + baseURL: '={{$credentials.url}}', + url: '/ghost/api/v3/content/settings/', + method: 'GET', + }, + }; } diff --git a/packages/nodes-base/nodes/Ghost/GenericFunctions.ts b/packages/nodes-base/nodes/Ghost/GenericFunctions.ts index 9ad1932f33..e00b8a28b9 100644 --- a/packages/nodes-base/nodes/Ghost/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Ghost/GenericFunctions.ts @@ -10,38 +10,30 @@ import { } from 'n8n-core'; import { - IDataObject, NodeApiError, + IDataObject, + JsonObject, + NodeApiError, } from 'n8n-workflow'; -import jwt from 'jsonwebtoken'; - export async function ghostApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any const source = this.getNodeParameter('source', 0) as string; let credentials; let version; - let token; + let credentialType; if (source === 'contentApi') { //https://ghost.org/faq/api-versioning/ version = 'v3'; - credentials = await this.getCredentials('ghostContentApi'); - query.key = credentials.apiKey as string; + credentialType = 'ghostContentApi'; } else { version = 'v2'; - credentials = await this.getCredentials('ghostAdminApi'); - // Create the token (including decoding secret) - const [id, secret] = (credentials.apiKey as string).split(':'); - - token = jwt.sign({}, Buffer.from(secret, 'hex'), { - keyid: id, - algorithm: 'HS256', - expiresIn: '5m', - audience: `/${version}/admin/`, - }); + credentialType = 'ghostAdminApi'; } + credentials = await this.getCredentials(credentialType); + const options: OptionsWithUri = { method, qs: query, @@ -50,17 +42,10 @@ export async function ghostApiRequest(this: IHookFunctions | IExecuteFunctions | json: true, }; - if (token) { - options.headers = { - Authorization: `Ghost ${token}`, - }; - } - try { - return await this.helpers.request!(options); - - } catch (error) { - throw new NodeApiError(this.getNode(), error); + return await this.helpers.requestWithAuthentication.call(this, credentialType, options); + } catch(error) { + throw new NodeApiError(this.getNode(), error as JsonObject); } } diff --git a/packages/nodes-base/nodes/Ghost/Ghost.node.ts b/packages/nodes-base/nodes/Ghost/Ghost.node.ts index a55d1fda6e..db7bbfded9 100644 --- a/packages/nodes-base/nodes/Ghost/Ghost.node.ts +++ b/packages/nodes-base/nodes/Ghost/Ghost.node.ts @@ -29,7 +29,7 @@ export class Ghost implements INodeType { description: INodeTypeDescription = { displayName: 'Ghost', name: 'ghost', - icon: 'file:ghost.png', + icon: 'file:ghost.svg', group: ['input'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', @@ -91,8 +91,9 @@ export class Ghost implements INodeType { value: 'post', }, ], + noDataExpression: true, default: 'post', - description: 'The resource to operate on.', + description: 'The resource to operate on', }, ...postOperations, ...postFields, @@ -137,7 +138,7 @@ export class Ghost implements INodeType { for (const tag of tags) { returnData.push({ name: tag.name, - value: tag.id, + value: tag.name, }); } return returnData; diff --git a/packages/nodes-base/nodes/Ghost/PostDescription.ts b/packages/nodes-base/nodes/Ghost/PostDescription.ts index 2da9b93bed..cf1d2e9d78 100644 --- a/packages/nodes-base/nodes/Ghost/PostDescription.ts +++ b/packages/nodes-base/nodes/Ghost/PostDescription.ts @@ -6,6 +6,7 @@ export const postOperations: INodeProperties[] = [ { displayName: 'Operation', name: 'operation', + noDataExpression: true, type: 'options', displayOptions: { show: { @@ -30,11 +31,12 @@ export const postOperations: INodeProperties[] = [ }, ], default: 'get', - description: 'The operation to perform.', + description: 'The operation to perform', }, { displayName: 'Operation', name: 'operation', + noDataExpression: true, type: 'options', displayOptions: { show: { @@ -74,7 +76,7 @@ export const postOperations: INodeProperties[] = [ }, ], default: 'get', - description: 'The operation to perform.', + description: 'The operation to perform', }, ]; @@ -256,12 +258,6 @@ export const postFields: INodeProperties[] = [ type: 'string', default: '', }, - { - displayName: 'Open Graph Title', - name: 'og_title', - type: 'string', - default: '', - }, { displayName: 'Open Graph Image', name: 'og_image', @@ -270,6 +266,12 @@ export const postFields: INodeProperties[] = [ description: 'URL of the image', }, + { + displayName: 'Open Graph Title', + name: 'og_title', + type: 'string', + default: '', + }, { displayName: 'Published At', name: 'published_at', @@ -303,7 +305,7 @@ export const postFields: INodeProperties[] = [ default: 'draft', }, { - displayName: 'Tags IDs', + displayName: 'Tags', name: 'tags', type: 'multiOptions', typeOptions: { @@ -365,7 +367,7 @@ export const postFields: INodeProperties[] = [ displayName: 'By', name: 'by', type: 'options', - default: '', + default: 'id', required: true, options: [ { @@ -846,12 +848,6 @@ export const postFields: INodeProperties[] = [ type: 'string', default: '', }, - { - displayName: 'Open Graph Title', - name: 'og_title', - type: 'string', - default: '', - }, { displayName: 'Open Graph Image', name: 'og_image', @@ -859,6 +855,12 @@ export const postFields: INodeProperties[] = [ default: '', description: 'URL of the image', }, + { + displayName: 'Open Graph Title', + name: 'og_title', + type: 'string', + default: '', + }, { displayName: 'Published At', name: 'published_at', @@ -892,7 +894,7 @@ export const postFields: INodeProperties[] = [ default: 'draft', }, { - displayName: 'Tags IDs', + displayName: 'Tags', name: 'tags', type: 'multiOptions', typeOptions: { diff --git a/packages/nodes-base/nodes/Ghost/ghost.png b/packages/nodes-base/nodes/Ghost/ghost.png deleted file mode 100644 index 6fdd457e466fa3039f39f59f9a2cd836d361c6ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1224 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bYNg$6c6wTaRqWk6}5nf!N?ZE5LEzi zMHMwg6ttltUuS^z+y6!U~$_FJ3OItkE*GkX6(B z|NsBr|Nnpg{jZ{9{Pp`!20qyjpT1PpHYjKszIgQp=!YxUZX7#tih*5n-@(HZr%YoI zR$yQd{PpMGfkQ`k@7 zj*3h2^bKMZQwj`?+|ZUU3UrocNswPK!{6U5-(L4!t5*MTZpX?}KW9@xZuZBIPTaq> zcV>NIUWTXCk@lw>7e>k(hc;F*Fff~Yx;Tb-9A7%UJ?WN#K%4639JiYS9t{(8PaaXt zOHL|NySZ4|^YZulzIhyGnJSlW{ghPiKR@Z|<)3_KMY$9M9KEkEUYVt|O@vchLi^!~ zAe}&8*VmbAAIwqhdVglsNu|UX5tn6;bdR}qd77T~pR&-`)U8n|f-&)hXt5YiPJv*Z z$uhIpnsd(jsw2yK|`_)x{^CvR8tff;#ECc1TS1c#qn#v#0D zp^0!r@s)SmWCN3;Hdq?5&RQDiyCe1TrTZsTj#+M>rg^8t_tEUwzi(cg_HhrNJn z?py7>w)#zB`4WEps=2|8$4nGI<}9u^{$q9d!>;^0r3e4Lk`{We$7wtJSf}0f6B{IE z2p<-y(o>8S*~k`B3=Lp+BkxeBXvv;4igGcVrv{*)ukOE@y3{~Jow|MPx)uzauX zI$o7;yVuXR+WdBfx?Q@!KY>3R6HlxAOsF~ZNGa!>oTx2J;Jr^(AxByLk{Wk>EtsSl z!Wi*x-eLF27k0a6RPRb}(u(6we*f~%lkX3hqAs-_Je84qaMGS@=jXHA-rJZZEQnEg@p_q%(}y)o2W>tnZISz;dy~OJIOE2F3ejHuYQ?GUn*z_={i=-A@WF0%3yL+yq=%vd>viUIY&(2nin3`VV*JDDEX-S z{ux*AsomLn;H1@ZV^_BDBmWzP#QgW`8*ynDx>e3p+}jlWJfLlQaZ+V-(O091Q{4^} z6i(O}6!Lk+nhDoDLPcJMq`rGJ?``Mw#WS1AeUJYyGwS`iHsz)+`{SctNx8+5ch5b$ cH@&%@;f=?4Hl68@K*a%rr>mdKI;Vst02 \ No newline at end of file