7cce71d997
closes #10266 - the Public API labs flag refers to the v0.1 API only - if it is disabled, the v0.1 API should be disabled - if the theme is using v2 API, then the get helper should be available regardless
205 lines
5.4 KiB
JavaScript
205 lines
5.4 KiB
JavaScript
// # Get Helper
|
|
// Usage: `{{#get "posts" limit="5"}}`, `{{#get "tags" limit="all"}}`
|
|
// Fetches data from the API
|
|
var proxy = require('./proxy'),
|
|
_ = require('lodash'),
|
|
Promise = require('bluebird'),
|
|
jsonpath = require('jsonpath'),
|
|
|
|
logging = proxy.logging,
|
|
i18n = proxy.i18n,
|
|
createFrame = proxy.hbs.handlebars.createFrame,
|
|
|
|
api = proxy.api,
|
|
labs = proxy.labs,
|
|
pathAliases,
|
|
get;
|
|
|
|
/**
|
|
* v0.1: users, posts, tags
|
|
* v2: authors, pages, posts, tags
|
|
*
|
|
* @NOTE: if you use "users" in v2, we should fallback to authors
|
|
*/
|
|
const RESOURCES = {
|
|
posts: {
|
|
alias: 'posts',
|
|
resource: 'posts'
|
|
},
|
|
tags: {
|
|
alias: 'tags',
|
|
resource: 'tags'
|
|
},
|
|
users: {
|
|
alias: 'authors',
|
|
resource: 'users'
|
|
},
|
|
pages: {
|
|
alias: 'pages',
|
|
resource: 'posts'
|
|
},
|
|
authors: {
|
|
alias: 'authors'
|
|
}
|
|
};
|
|
|
|
// Short forms of paths which we should understand
|
|
pathAliases = {
|
|
'post.tags': 'post.tags[*].slug',
|
|
'post.author': 'post.author.slug'
|
|
};
|
|
|
|
/**
|
|
* ## Is Browse
|
|
* Is this a Browse request or a Read request?
|
|
* @param {Object} resource
|
|
* @param {Object} options
|
|
* @returns {boolean}
|
|
*/
|
|
function isBrowse(options) {
|
|
var browse = true;
|
|
|
|
if (options.id || options.slug) {
|
|
browse = false;
|
|
}
|
|
|
|
return browse;
|
|
}
|
|
|
|
/**
|
|
* ## Resolve Paths
|
|
* Find and resolve path strings
|
|
*
|
|
* @param {Object} data
|
|
* @param {String} value
|
|
* @returns {String}
|
|
*/
|
|
function resolvePaths(data, value) {
|
|
var regex = /\{\{(.*?)\}\}/g;
|
|
|
|
value = value.replace(regex, function (match, path) {
|
|
var result;
|
|
|
|
// Handle aliases
|
|
path = pathAliases[path] ? pathAliases[path] : path;
|
|
// Handle Handlebars .[] style arrays
|
|
path = path.replace(/\.\[/g, '[');
|
|
|
|
// Do the query, which always returns an array of matches
|
|
result = jsonpath.query(data, path);
|
|
|
|
// Handle the case where the single data property we return is a Date
|
|
// Data.toString() is not DB compatible, so use `toISOString()` instead
|
|
if (_.isDate(result[0])) {
|
|
result[0] = result[0].toISOString();
|
|
}
|
|
|
|
// Concatenate the results with a comma, handles common case of multiple tag slugs
|
|
return result.join(',');
|
|
});
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* ## Parse Options
|
|
* Ensure options passed in make sense
|
|
*
|
|
* @param {Object} data
|
|
* @param {Object} options
|
|
* @returns {*}
|
|
*/
|
|
function parseOptions(data, options) {
|
|
if (_.isString(options.filter)) {
|
|
options.filter = resolvePaths(data, options.filter);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* ## Get
|
|
* @param {Object} resource
|
|
* @param {Object} options
|
|
* @returns {Promise}
|
|
*/
|
|
get = function get(resource, options) {
|
|
options = options || {};
|
|
options.hash = options.hash || {};
|
|
options.data = options.data || {};
|
|
|
|
const self = this;
|
|
const data = createFrame(options.data);
|
|
const apiVersion = _.get(data, 'root._locals.apiVersion');
|
|
let apiOptions = options.hash;
|
|
|
|
if (!options.fn) {
|
|
data.error = i18n.t('warnings.helpers.mustBeCalledAsBlock', {helperName: 'get'});
|
|
logging.warn(data.error);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
if (!RESOURCES[resource]) {
|
|
data.error = i18n.t('warnings.helpers.get.invalidResource');
|
|
logging.warn(data.error);
|
|
return Promise.resolve(options.inverse(self, {data: data}));
|
|
}
|
|
|
|
const controller = api[apiVersion][RESOURCES[resource].alias] ? RESOURCES[resource].alias : RESOURCES[resource].resource;
|
|
const action = isBrowse(apiOptions) ? 'browse' : 'read';
|
|
|
|
// CASE: no fallback defined e.g. v0.1 tries to fetch "authors"
|
|
if (!controller) {
|
|
data.error = i18n.t('warnings.helpers.get.invalidResource');
|
|
logging.warn(data.error);
|
|
return Promise.resolve(options.inverse(self, {data: data}));
|
|
}
|
|
|
|
// Parse the options we're going to pass to the API
|
|
apiOptions = parseOptions(this, apiOptions);
|
|
|
|
return api[apiVersion][controller][action](apiOptions).then(function success(result) {
|
|
var blockParams;
|
|
|
|
// block params allows the theme developer to name the data using something like
|
|
// `{{#get "posts" as |result pageInfo|}}`
|
|
blockParams = [result[resource]];
|
|
if (result.meta && result.meta.pagination) {
|
|
result.pagination = result.meta.pagination;
|
|
blockParams.push(result.meta.pagination);
|
|
}
|
|
|
|
// Call the main template function
|
|
return options.fn(result, {
|
|
data: data,
|
|
blockParams: blockParams
|
|
});
|
|
}).catch(function error(err) {
|
|
logging.error(err);
|
|
data.error = err.message;
|
|
return options.inverse(self, {data: data});
|
|
});
|
|
};
|
|
|
|
module.exports = function getLabsWrapper() {
|
|
const self = this;
|
|
const args = arguments;
|
|
const apiVersion = _.get(args, '[1].data.root._locals.apiVersion');
|
|
|
|
// If the API version is v0.1 return the labs enabled version of the helper
|
|
if (apiVersion === 'v0.1') {
|
|
return labs.enabledHelper({
|
|
flagKey: 'publicAPI',
|
|
flagName: 'Public API',
|
|
helperName: 'get',
|
|
helpUrl: 'https://help.ghost.org/hc/en-us/articles/115000301672-Public-API-Beta',
|
|
async: true
|
|
}, function executeHelper() {
|
|
return get.apply(self, args);
|
|
});
|
|
}
|
|
|
|
// Else, we just apply the helper normally
|
|
return get.apply(self, args);
|
|
};
|