2015-06-27 18:42:10 +03:00
|
|
|
// # Get Helper
|
|
|
|
// Usage: `{{#get "posts" limit="5"}}`, `{{#get "tags" limit="all"}}`
|
|
|
|
// Fetches data from the API
|
2021-09-28 17:06:33 +03:00
|
|
|
const {config, api, prepareContextResource} = require('../services/proxy');
|
|
|
|
const {hbs} = require('../services/rendering');
|
2021-09-26 23:01:13 +03:00
|
|
|
|
|
|
|
const logging = require('@tryghost/logging');
|
|
|
|
const errors = require('@tryghost/errors');
|
|
|
|
const tpl = require('@tryghost/tpl');
|
|
|
|
|
2020-03-30 23:23:02 +03:00
|
|
|
const _ = require('lodash');
|
|
|
|
const Promise = require('bluebird');
|
|
|
|
const jsonpath = require('jsonpath');
|
2017-03-23 22:00:58 +03:00
|
|
|
|
2021-09-23 22:46:22 +03:00
|
|
|
const messages = {
|
2021-10-04 18:50:07 +03:00
|
|
|
mustBeCalledAsBlock: 'The {\\{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}',
|
2021-09-23 22:46:22 +03:00
|
|
|
invalidResource: 'Invalid resource given to get helper'
|
|
|
|
};
|
|
|
|
|
2020-03-30 23:23:02 +03:00
|
|
|
const createFrame = hbs.handlebars.createFrame;
|
2015-06-27 18:42:10 +03:00
|
|
|
|
2018-11-06 16:08:13 +03:00
|
|
|
const RESOURCES = {
|
|
|
|
posts: {
|
2019-09-12 18:47:01 +03:00
|
|
|
alias: 'postsPublic'
|
2018-11-06 16:08:13 +03:00
|
|
|
},
|
|
|
|
tags: {
|
2019-09-12 18:47:01 +03:00
|
|
|
alias: 'tagsPublic'
|
2018-11-06 16:08:13 +03:00
|
|
|
},
|
|
|
|
pages: {
|
2019-09-12 18:47:01 +03:00
|
|
|
alias: 'pagesPublic'
|
2018-11-07 17:29:37 +03:00
|
|
|
},
|
|
|
|
authors: {
|
2019-02-25 21:52:17 +03:00
|
|
|
alias: 'authorsPublic'
|
2022-03-02 12:34:34 +03:00
|
|
|
},
|
|
|
|
tiers: {
|
|
|
|
alias: 'tiersPublic'
|
2018-11-06 16:08:13 +03:00
|
|
|
}
|
|
|
|
};
|
2015-06-27 18:42:10 +03:00
|
|
|
|
2015-10-22 13:12:21 +03:00
|
|
|
// Short forms of paths which we should understand
|
2020-03-30 23:23:02 +03:00
|
|
|
const pathAliases = {
|
2015-10-22 13:12:21 +03:00
|
|
|
'post.tags': 'post.tags[*].slug',
|
|
|
|
'post.author': 'post.author.slug'
|
|
|
|
};
|
|
|
|
|
2015-06-27 18:42:10 +03:00
|
|
|
/**
|
|
|
|
* ## Is Browse
|
|
|
|
* Is this a Browse request or a Read request?
|
2016-02-21 21:48:44 +03:00
|
|
|
* @param {Object} resource
|
2015-06-27 18:42:10 +03:00
|
|
|
* @param {Object} options
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2018-11-06 16:08:13 +03:00
|
|
|
function isBrowse(options) {
|
2020-04-29 18:44:27 +03:00
|
|
|
let browse = true;
|
2015-06-27 18:42:10 +03:00
|
|
|
|
|
|
|
if (options.id || options.slug) {
|
|
|
|
browse = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return browse;
|
|
|
|
}
|
|
|
|
|
2015-10-22 13:12:21 +03:00
|
|
|
/**
|
|
|
|
* ## Resolve Paths
|
|
|
|
* Find and resolve path strings
|
|
|
|
*
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {String} value
|
|
|
|
* @returns {String}
|
|
|
|
*/
|
2019-02-04 18:19:00 +03:00
|
|
|
function resolvePaths(globals, data, value) {
|
2020-04-29 18:44:27 +03:00
|
|
|
const regex = /\{\{(.*?)\}\}/g;
|
2015-10-22 13:12:21 +03:00
|
|
|
|
|
|
|
value = value.replace(regex, function (match, path) {
|
2020-04-29 18:44:27 +03:00
|
|
|
let result;
|
2015-10-22 13:12:21 +03:00
|
|
|
|
|
|
|
// Handle aliases
|
|
|
|
path = pathAliases[path] ? pathAliases[path] : path;
|
|
|
|
// Handle Handlebars .[] style arrays
|
|
|
|
path = path.replace(/\.\[/g, '[');
|
|
|
|
|
2019-02-04 18:19:00 +03:00
|
|
|
if (path.charAt(0) === '@') {
|
|
|
|
result = jsonpath.query(globals, path.substr(1));
|
|
|
|
} else {
|
|
|
|
// Do the query, which always returns an array of matches
|
|
|
|
result = jsonpath.query(data, path);
|
|
|
|
}
|
2015-10-22 13:12:21 +03:00
|
|
|
|
2018-02-14 20:33:07 +03:00
|
|
|
// 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(',');
|
2015-10-22 13:12:21 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2015-06-27 18:42:10 +03:00
|
|
|
/**
|
|
|
|
* ## Parse Options
|
|
|
|
* Ensure options passed in make sense
|
|
|
|
*
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {Object} options
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2019-02-04 18:19:00 +03:00
|
|
|
function parseOptions(globals, data, options) {
|
2015-10-22 13:12:21 +03:00
|
|
|
if (_.isString(options.filter)) {
|
2019-02-04 18:19:00 +03:00
|
|
|
options.filter = resolvePaths(globals, data, options.filter);
|
2015-10-22 13:12:21 +03:00
|
|
|
}
|
|
|
|
|
2021-12-16 14:59:39 +03:00
|
|
|
if (options.limit === 'all' && config.get('getHelperLimitAllMax')) {
|
|
|
|
options.limit = config.get('getHelperLimitAllMax');
|
|
|
|
}
|
|
|
|
|
2015-06-27 18:42:10 +03:00
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ## Get
|
2016-02-21 21:48:44 +03:00
|
|
|
* @param {Object} resource
|
2015-06-27 18:42:10 +03:00
|
|
|
* @param {Object} options
|
|
|
|
* @returns {Promise}
|
|
|
|
*/
|
2020-03-30 23:23:02 +03:00
|
|
|
module.exports = function get(resource, options) {
|
2015-06-27 18:42:10 +03:00
|
|
|
options = options || {};
|
|
|
|
options.hash = options.hash || {};
|
|
|
|
options.data = options.data || {};
|
|
|
|
|
2018-10-17 10:23:59 +03:00
|
|
|
const self = this;
|
2019-06-07 16:54:55 +03:00
|
|
|
const start = Date.now();
|
2018-10-17 10:23:59 +03:00
|
|
|
const data = createFrame(options.data);
|
2019-02-04 18:19:00 +03:00
|
|
|
const ghostGlobals = _.omit(data, ['_parent', 'root']);
|
2018-12-12 18:38:35 +03:00
|
|
|
const apiVersion = _.get(data, 'root._locals.apiVersion');
|
2018-10-17 10:23:59 +03:00
|
|
|
let apiOptions = options.hash;
|
2019-06-07 16:54:55 +03:00
|
|
|
let returnedRowsCount;
|
2015-06-27 18:42:10 +03:00
|
|
|
|
|
|
|
if (!options.fn) {
|
2021-09-23 22:46:22 +03:00
|
|
|
data.error = tpl(messages.mustBeCalledAsBlock, {helperName: 'get'});
|
2016-10-04 18:33:43 +03:00
|
|
|
logging.warn(data.error);
|
2015-06-27 18:42:10 +03:00
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2018-11-06 16:08:13 +03:00
|
|
|
if (!RESOURCES[resource]) {
|
2021-09-23 22:46:22 +03:00
|
|
|
data.error = tpl(messages.invalidResource);
|
2016-10-04 18:33:43 +03:00
|
|
|
logging.warn(data.error);
|
2015-06-27 18:42:10 +03:00
|
|
|
return Promise.resolve(options.inverse(self, {data: data}));
|
|
|
|
}
|
|
|
|
|
2020-01-07 13:25:51 +03:00
|
|
|
const controllerName = RESOURCES[resource].alias;
|
|
|
|
const controller = api[apiVersion][controllerName];
|
2018-11-06 16:08:13 +03:00
|
|
|
const action = isBrowse(apiOptions) ? 'browse' : 'read';
|
|
|
|
|
2015-06-27 18:42:10 +03:00
|
|
|
// Parse the options we're going to pass to the API
|
2019-02-04 18:19:00 +03:00
|
|
|
apiOptions = parseOptions(ghostGlobals, this, apiOptions);
|
2022-03-03 18:18:05 +03:00
|
|
|
apiOptions.context = {member: data.member};
|
2015-06-27 18:42:10 +03:00
|
|
|
|
2019-02-27 20:08:36 +03:00
|
|
|
// @TODO: https://github.com/TryGhost/Ghost/issues/10548
|
2019-09-12 18:48:29 +03:00
|
|
|
return controller[action](apiOptions).then(function success(result) {
|
2021-07-01 19:55:44 +03:00
|
|
|
// prepare data properties for use with handlebars
|
|
|
|
if (result[resource] && result[resource].length) {
|
|
|
|
result[resource].forEach(prepareContextResource);
|
|
|
|
}
|
2015-10-23 04:02:26 +03:00
|
|
|
|
2019-06-07 16:54:55 +03:00
|
|
|
// used for logging details of slow requests
|
|
|
|
returnedRowsCount = result[resource] && result[resource].length;
|
|
|
|
|
2015-10-23 04:02:26 +03:00
|
|
|
// block params allows the theme developer to name the data using something like
|
2015-12-14 16:19:38 +03:00
|
|
|
// `{{#get "posts" as |result pageInfo|}}`
|
2021-07-01 19:55:44 +03:00
|
|
|
const blockParams = [result[resource]];
|
2015-10-23 04:02:26 +03:00
|
|
|
if (result.meta && result.meta.pagination) {
|
2015-12-14 16:19:38 +03:00
|
|
|
result.pagination = result.meta.pagination;
|
2015-10-23 04:02:26 +03:00
|
|
|
blockParams.push(result.meta.pagination);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the main template function
|
2015-06-27 18:42:10 +03:00
|
|
|
return options.fn(result, {
|
|
|
|
data: data,
|
2015-10-23 04:02:26 +03:00
|
|
|
blockParams: blockParams
|
2015-06-27 18:42:10 +03:00
|
|
|
});
|
|
|
|
}).catch(function error(err) {
|
2017-08-27 19:00:32 +03:00
|
|
|
logging.error(err);
|
2015-06-27 18:42:10 +03:00
|
|
|
data.error = err.message;
|
|
|
|
return options.inverse(self, {data: data});
|
2019-06-07 16:54:55 +03:00
|
|
|
}).finally(function () {
|
|
|
|
const totalMs = Date.now() - start;
|
|
|
|
const logLevel = config.get('logging:slowHelper:level');
|
|
|
|
const threshold = config.get('logging:slowHelper:threshold');
|
|
|
|
if (totalMs > threshold) {
|
|
|
|
logging[logLevel](new errors.HelperWarning({
|
|
|
|
message: `{{#get}} helper took ${totalMs}ms to complete`,
|
|
|
|
code: 'SLOW_GET_HELPER',
|
|
|
|
errorDetails: {
|
2020-01-07 13:25:51 +03:00
|
|
|
api: `${apiVersion}.${controllerName}.${action}`,
|
2019-06-07 16:54:55 +03:00
|
|
|
apiOptions,
|
|
|
|
returnedRows: returnedRowsCount
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
2015-06-27 18:42:10 +03:00
|
|
|
});
|
|
|
|
};
|
2021-10-04 18:50:07 +03:00
|
|
|
|
|
|
|
module.exports.async = true;
|