const _ = require('lodash'); const orderPlugin = function orderPlugin(Bookshelf) { Bookshelf.Model = Bookshelf.Model.extend({ orderAttributes() {}, orderRawQuery() {}, parseOrderOption: function (orderQueryString, withRelated) { const order = {}; const orderRaw = []; const eagerLoadArray = []; const orderAttributes = this.orderAttributes(); if (withRelated && withRelated.indexOf('count.posts') > -1) { orderAttributes.push('count.posts'); } let rules = []; // CASE: repeat order query parameter keys are present if (_.isArray(orderQueryString)) { orderQueryString.forEach((qs) => { rules.push(...qs.split(',')); }); } else { rules = orderQueryString.split(','); } _.each(rules, (rule) => { let match; let field; let direction; match = /^([a-z0-9_.]+)\s+(asc|desc)$/i.exec(rule.trim()); // invalid order syntax if (!match) { return; } field = match[1].toLowerCase(); direction = match[2].toUpperCase(); const orderRawQuery = this.orderRawQuery(field, direction, withRelated); if (orderRawQuery) { orderRaw.push(orderRawQuery.orderByRaw); if (orderRawQuery.eagerLoad) { eagerLoadArray.push(orderRawQuery.eagerLoad); } return; } const matchingOrderAttribute = orderAttributes.find((orderAttribute) => { // NOTE: this logic assumes we use different field names for "parent" and "child" relations. // E.g.: ['parent.title', 'child.title'] and ['child.title', 'parent.title'] - would not // distinguish on which relation to sort neither which order to pick the fields on. // For more context see: https://github.com/TryGhost/Ghost/pull/12226#discussion_r493085098 return orderAttribute.endsWith(field); }); if (!matchingOrderAttribute) { return; } order[matchingOrderAttribute] = direction; }); return { order, orderRaw: orderRaw.join(', '), eagerLoad: _.uniq(eagerLoadArray) }; } }); }; module.exports = orderPlugin;