Skip to content

Commit

Permalink
ft-option-skip-formattin-response
Browse files Browse the repository at this point in the history
  • Loading branch information
phawazzzy committed May 8, 2020
1 parent 0e6ed7f commit 4f3610b
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 25 deletions.
110 changes: 92 additions & 18 deletions packages/admin-api/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@ const FormData = require('form-data');
const fs = require('fs');
const token = require('./token');

// supported Versions
const supportedVersions = ['v2', 'v3', 'canary'];
const name = '@tryghost/admin-api';

// Export the GhostAdminAPI instance
module.exports = function GhostAdminAPI(options) {
// test if the prototype property of the constructor appears anywhere in the prototype chain of GhostAdminAPI.
if (this instanceof GhostAdminAPI) {
return GhostAdminAPI(options);
}

// defaultConfig (Object)
const defaultConfig = {
// ghostPath (property)
ghostPath: 'ghost',
// makeRequest (method)
// using es5 destructured parameters
// params and headers are optional parameters with an empty object as default value
makeRequest({url, method, data, params = {}, headers = {}}) {
// axios: Promise based HTTP client for the browser and node.js
// Requests can be made by passing the relevant config to axios i.e axios(config)
return axios({
url,
method,
Expand All @@ -33,42 +43,64 @@ module.exports = function GhostAdminAPI(options) {
}
};

//copy all enumerable own properties from defaultConfig and options to an empty object and assign it to config.
const config = Object.assign({}, defaultConfig, options);

// new GhostAdminAPI({host: '...'}) is deprecated

if (config.host) {
// eslint-disable-next-line
console.warn(`${name}: The 'host' parameter is deprecated, please use 'url' instead`);
if (!config.url) {
config.url = config.host;
}
}

//set formatResponse to true if not added to the option
if (config.formatResponse === {} || config.formatResponse === undefined) {
config.formatResponse = true;
}
// Ensure a version is supplied
if (!config.version) {
throw new Error(`${name} Config Missing: 'version' is required. E.g. ${supportedVersions.join(',')}`);
}

// Ensure supplied version is supported
if (!supportedVersions.includes(config.version)) {
throw new Error(`${name} Config Invalid: 'version' ${config.version} is not supported`);
}

// Ensure config is url specified
if (!config.url) {
throw new Error(`${name} Config Missing: 'url' is required. E.g. 'https://site.com'`);
}

// regex test if specified config url is valid
if (!/https?:\/\//.test(config.url)) {
throw new Error(`${name} Config Invalid: 'url' ${config.url} requires a protocol. E.g. 'https://site.com'`);
}

// check for trailing slash in url
if (config.url.endsWith('/')) {
throw new Error(`${name} Config Invalid: 'url' ${config.url} must not have a trailing slash. E.g. 'https://site.com'`);
}

// check for leading or trailing slash in url
if (config.ghostPath.endsWith('/') || config.ghostPath.startsWith('/')) {
throw new Error(`${name} Config Invalid: 'ghostPath' ${config.ghostPath} must not have a leading or trailing slash. E.g. 'ghost'`);
}

// Ensure key is supplied to config
if (!config.key) {
throw new Error(`${name} Config Invalid: 'key' ${config.key} must have 26 hex characters`);
}

// validate key format
if (!/[0-9a-f]{24}:[0-9a-f]{64}/.test(config.key)) {
throw new Error(`${name} Config Invalid: 'key' ${config.key} must have the following format {A}:{B}, where A is 24 hex characters and B is 64 hex characters`);
}

// resources currently supported
// stable and experimental resources
const resources = [
// @NOTE: stable
'posts',
Expand All @@ -81,15 +113,26 @@ module.exports = function GhostAdminAPI(options) {
'members'
];

/**
* The api has 9 methods to for making api calls
*
*/

//copy all enumerable own properties from each resource and options apiObject
const api = resources.reduce((apiObject, resourceType) => {
// add function with params data, and queryParams default to an object
function add(data, queryParams = {}) {
// test if data is undefined or empty
if (!data || !Object.keys(data).length) {
// returns a Promise that is rejected with
return Promise.reject(new Error('Missing data'));
}

const mapped = {};
// assign data as an array to mapped object on the resourceType index
mapped[resourceType] = [data];

// finally make the request calling makeResourceRequest()
return makeResourceRequest(resourceType, queryParams, mapped, 'POST');
}

Expand Down Expand Up @@ -157,6 +200,8 @@ module.exports = function GhostAdminAPI(options) {
return makeResourceRequest(resourceType, queryParams, {}, 'GET', urlParams);
}

// assigns the current resource from the resources array, as an array with the functions declared above as methods to the apiObject i.e the accumulator
// [resourceType] is a variable, from resources i.e the previous value
return Object.assign(apiObject, {
[resourceType]: {
read,
Expand Down Expand Up @@ -216,18 +261,21 @@ module.exports = function GhostAdminAPI(options) {
}
};

// method to read config
api.config = {
read() {
return makeResourceRequest('config', {}, {});
}
};

// method to read site
api.site = {
read() {
return makeResourceRequest('site', {}, {});
}
};

// method to upload themes
api.themes = {
upload(data) {
if (!data) {
Expand All @@ -253,59 +301,85 @@ module.exports = function GhostAdminAPI(options) {

return api;

//this function is used to upload a resource to an endpoint
//the resource type can be a post, page, an image, a theme,
//the function is taking in three parameters, which are the resourcetype(the type of what you want to upload), data(the payload i.e file), endpoint(the route you want to upload to)
function makeUploadRequest(resourceType, data, endpoint) {
//the headers which is the content type with a boundaryUnauthorized
const headers = {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`
};

//the makeAPIREQUEST is been called here which take the parameters of makeUploadRequest as an arguement
return makeApiRequest({
endpoint: endpoint,
method: 'POST',
body: data,
headers
}).then((data) => {
if (!Array.isArray(data[resourceType])) {
return data[resourceType];
}
if (data[resourceType].length === 1 && !data.meta) {
return data[resourceType][0];
}
});
})
//return a promise data
.then((data) => {
if (config.formatResponse === true) {
if (!Array.isArray(data[resourceType])) {
return data[resourceType];
}

if (data[resourceType].length === 1 && !data.meta) {
return data[resourceType][0];
}
} else {
return data;
}
});
}

// function makeResourceRequest
function makeResourceRequest(resourceType, queryParams = {}, body = {}, method = 'GET', urlParams = {}) {
// make the api request
return makeApiRequest({
endpoint: endpointFor(resourceType, urlParams),
method,
queryParams,
body
}).then((data) => {
// return data if method is 'DELETE', 'GET' in the default
if (method === 'DELETE') {
return data;
}

if (!Array.isArray(data[resourceType])) {
return data[resourceType];
}
if (data[resourceType].length === 1 && !data.meta) {
return data[resourceType][0];
if (config.formatResponse === true) {
// return data[resourceType] if it is not an array
if (!Array.isArray(data[resourceType])) {
return data[resourceType];
}
// return data[resourceType[0] index of data if data[resourceType] length equals 1 ans data.meta is undefined
if (data[resourceType].length === 1 && !data.meta) {
return data[resourceType][0];
}
//copy the values of all of the enumerable own properties from data.meta as meta data[resourceType]. Returns data[resourceType]
return Object.assign(data[resourceType], {meta: data.meta});
} else {
return Object.assign(data, {meta: data.meta});
}
return Object.assign(data[resourceType], {meta: data.meta});
});
}

// function endpointFor: returns an endpoint for api request
function endpointFor(resource, {id, slug, email} = {}) {
// destructure values from config
const {ghostPath, version} = config;
// default endpoint
let endpoint = `/${ghostPath}/api/${version}/admin/${resource}/`;

// if id is supplied then endpoint points to the supplied id
if (id) {
endpoint = `${endpoint}${id}/`;
// if id is not supplied check if slug is supplied and point to the supplied slug
} else if (slug) {
endpoint = `${endpoint}slug/${slug}/`;
// if id and slug are not supplied check if email is supplied and point to the supplied email
} else if (email) {
endpoint = `${endpoint}email/${email}/`;
}

// return endpoint, default endpoint is returned if neither id, slug, nor email is supplied
return endpoint;
}

Expand Down
17 changes: 10 additions & 7 deletions packages/content-api/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import axios from 'axios';
const supportedVersions = ['v2', 'v3', 'canary'];
const name = '@tryghost/content-api';

export default function GhostContentAPI({url, host, ghostPath = 'ghost', version, key}) {
export default function GhostContentAPI({url, host, ghostPath = 'ghost', version, key, formatResponse = true}) {
// host parameter is deprecated
if (host) {
// eslint-disable-next-line
Expand Down Expand Up @@ -86,13 +86,16 @@ export default function GhostContentAPI({url, host, ghostPath = 'ghost', version
},
headers
}).then((res) => {
if (!Array.isArray(res.data[resourceType])) {
return res.data[resourceType];
if (formatResponse) {
if (!Array.isArray(res.data[resourceType])) {
return res.data[resourceType];
}
if (res.data[resourceType].length === 1 && !res.data.meta) {
return res.data[resourceType][0];
}
return Object.assign(res.data[resourceType], {meta: res.data.meta});
}
if (res.data[resourceType].length === 1 && !res.data.meta) {
return res.data[resourceType][0];
}
return Object.assign(res.data[resourceType], {meta: res.data.meta});
return res;
}).catch((err) => {
if (err.response && err.response.data && err.response.data.errors) {
const props = err.response.data.errors[0];
Expand Down

0 comments on commit 4f3610b

Please sign in to comment.