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
This commit is contained in:
Jonathan Bennetts 2022-05-14 09:39:28 +01:00 committed by GitHub
parent 7090a79b5d
commit a14d85ea48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 46 deletions

View file

@ -1,8 +1,12 @@
import { import {
ICredentialDataDecryptedObject,
ICredentialTestRequest,
ICredentialType, ICredentialType,
IHttpRequestOptions,
INodeProperties, INodeProperties,
} from 'n8n-workflow'; } from 'n8n-workflow';
import jwt from 'jsonwebtoken';
export class GhostAdminApi implements ICredentialType { export class GhostAdminApi implements ICredentialType {
name = 'ghostAdminApi'; name = 'ghostAdminApi';
displayName = 'Ghost Admin API'; displayName = 'Ghost Admin API';
@ -22,4 +26,27 @@ export class GhostAdminApi implements ICredentialType {
default: '', default: '',
}, },
]; ];
async authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
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/',
},
};
} }

View file

@ -1,5 +1,8 @@
import { import {
ICredentialDataDecryptedObject,
ICredentialTestRequest,
ICredentialType, ICredentialType,
IHttpRequestOptions,
INodeProperties, INodeProperties,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -22,4 +25,18 @@ export class GhostContentApi implements ICredentialType {
default: '', default: '',
}, },
]; ];
async authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
requestOptions.qs = {
...requestOptions.qs,
'key': credentials.apiKey,
};
return requestOptions;
}
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials.url}}',
url: '/ghost/api/v3/content/settings/',
method: 'GET',
},
};
} }

View file

@ -10,38 +10,30 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, NodeApiError, IDataObject,
JsonObject,
NodeApiError,
} from 'n8n-workflow'; } 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<any> { // tslint:disable-line:no-any export async function ghostApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
const source = this.getNodeParameter('source', 0) as string; const source = this.getNodeParameter('source', 0) as string;
let credentials; let credentials;
let version; let version;
let token; let credentialType;
if (source === 'contentApi') { if (source === 'contentApi') {
//https://ghost.org/faq/api-versioning/ //https://ghost.org/faq/api-versioning/
version = 'v3'; version = 'v3';
credentials = await this.getCredentials('ghostContentApi'); credentialType = 'ghostContentApi';
query.key = credentials.apiKey as string;
} else { } else {
version = 'v2'; version = 'v2';
credentials = await this.getCredentials('ghostAdminApi'); credentialType = '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/`,
});
} }
credentials = await this.getCredentials(credentialType);
const options: OptionsWithUri = { const options: OptionsWithUri = {
method, method,
qs: query, qs: query,
@ -50,17 +42,10 @@ export async function ghostApiRequest(this: IHookFunctions | IExecuteFunctions |
json: true, json: true,
}; };
if (token) {
options.headers = {
Authorization: `Ghost ${token}`,
};
}
try { try {
return await this.helpers.request!(options); return await this.helpers.requestWithAuthentication.call(this, credentialType, options);
} catch(error) { } catch(error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), error as JsonObject);
} }
} }

View file

@ -29,7 +29,7 @@ export class Ghost implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'Ghost', displayName: 'Ghost',
name: 'ghost', name: 'ghost',
icon: 'file:ghost.png', icon: 'file:ghost.svg',
group: ['input'], group: ['input'],
version: 1, version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
@ -91,8 +91,9 @@ export class Ghost implements INodeType {
value: 'post', value: 'post',
}, },
], ],
noDataExpression: true,
default: 'post', default: 'post',
description: 'The resource to operate on.', description: 'The resource to operate on',
}, },
...postOperations, ...postOperations,
...postFields, ...postFields,
@ -137,7 +138,7 @@ export class Ghost implements INodeType {
for (const tag of tags) { for (const tag of tags) {
returnData.push({ returnData.push({
name: tag.name, name: tag.name,
value: tag.id, value: tag.name,
}); });
} }
return returnData; return returnData;

View file

@ -6,6 +6,7 @@ export const postOperations: INodeProperties[] = [
{ {
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
noDataExpression: true,
type: 'options', type: 'options',
displayOptions: { displayOptions: {
show: { show: {
@ -30,11 +31,12 @@ export const postOperations: INodeProperties[] = [
}, },
], ],
default: 'get', default: 'get',
description: 'The operation to perform.', description: 'The operation to perform',
}, },
{ {
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
noDataExpression: true,
type: 'options', type: 'options',
displayOptions: { displayOptions: {
show: { show: {
@ -74,7 +76,7 @@ export const postOperations: INodeProperties[] = [
}, },
], ],
default: 'get', default: 'get',
description: 'The operation to perform.', description: 'The operation to perform',
}, },
]; ];
@ -256,12 +258,6 @@ export const postFields: INodeProperties[] = [
type: 'string', type: 'string',
default: '', default: '',
}, },
{
displayName: 'Open Graph Title',
name: 'og_title',
type: 'string',
default: '',
},
{ {
displayName: 'Open Graph Image', displayName: 'Open Graph Image',
name: 'og_image', name: 'og_image',
@ -270,6 +266,12 @@ export const postFields: INodeProperties[] = [
description: 'URL of the image', description: 'URL of the image',
}, },
{
displayName: 'Open Graph Title',
name: 'og_title',
type: 'string',
default: '',
},
{ {
displayName: 'Published At', displayName: 'Published At',
name: 'published_at', name: 'published_at',
@ -303,7 +305,7 @@ export const postFields: INodeProperties[] = [
default: 'draft', default: 'draft',
}, },
{ {
displayName: 'Tags IDs', displayName: 'Tags',
name: 'tags', name: 'tags',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
@ -365,7 +367,7 @@ export const postFields: INodeProperties[] = [
displayName: 'By', displayName: 'By',
name: 'by', name: 'by',
type: 'options', type: 'options',
default: '', default: 'id',
required: true, required: true,
options: [ options: [
{ {
@ -846,12 +848,6 @@ export const postFields: INodeProperties[] = [
type: 'string', type: 'string',
default: '', default: '',
}, },
{
displayName: 'Open Graph Title',
name: 'og_title',
type: 'string',
default: '',
},
{ {
displayName: 'Open Graph Image', displayName: 'Open Graph Image',
name: 'og_image', name: 'og_image',
@ -859,6 +855,12 @@ export const postFields: INodeProperties[] = [
default: '', default: '',
description: 'URL of the image', description: 'URL of the image',
}, },
{
displayName: 'Open Graph Title',
name: 'og_title',
type: 'string',
default: '',
},
{ {
displayName: 'Published At', displayName: 'Published At',
name: 'published_at', name: 'published_at',
@ -892,7 +894,7 @@ export const postFields: INodeProperties[] = [
default: 'draft', default: 'draft',
}, },
{ {
displayName: 'Tags IDs', displayName: 'Tags',
name: 'tags', name: 'tags',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 151 151" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x=".5" y=".5"/><symbol id="A" overflow="visible"><g fill-rule="nonzero"><path d="M0 22.5C0 10.035 10.035 0 22.5 0h105C139.965 0 150 10.035 150 22.5v105c0 12.465-10.035 22.5-22.5 22.5h-105C10.035 150 0 139.965 0 127.5z" stroke="none" fill="#e8e9eb"/><path d="M29.59 39.258h54.785zm72.07 0h17.871zM29.59 75.293h90.82zm0 35.449h36.035zm54.785 0h36.035z" stroke="#3d515b" stroke-linejoin="miter" fill="#000" stroke-width="17.871"/></g></symbol></svg>

After

Width:  |  Height:  |  Size: 665 B