diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js b/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js index 19c58aa306..afd6da5745 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js @@ -7,6 +7,7 @@ const slugFilterOrder = require('./utils/slug-filter-order'); const localUtils = require('../../index'); const mobiledoc = require('../../../../../lib/mobiledoc'); const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta; +const postsSchema = require('../../../../../data/schema').tables.posts; const clean = require('./utils/clean'); const lexical = require('../../../../../lib/lexical'); const sentry = require('../../../../../../shared/sentry'); @@ -24,6 +25,33 @@ function removeSourceFormats(frame) { } } +/** + * Selects all allowed columns for the given frame. + * + * This removes the lexical and mobiledoc columns from the query. This is a performance improvement as we never intend + * to expose those columns in the content API and they are very large datasets to be passing around and de/serializing. + * + * NOTE: This is only intended for the Content API. We need these fields within Admin API responses. + * + * @param {Object} frame - The frame object. + */ +function selectAllAllowedColumns(frame) { + if (!frame.options.columns && !frame.options.selectRaw) { + // Because we're returning columns directly from the schema we need to remove info columns like @@UNIQUE_CONSTRAINTS@@ and @@INDEXES@@ + frame.options.selectRaw = _.keys(_.omit(postsSchema, ['lexical','mobiledoc','@@INDEXES@@','@@UNIQUE_CONSTRAINTS@@'])).join(','); + } else if (frame.options.columns) { + frame.options.columns = frame.options.columns.filter((column) => { + return !['mobiledoc', 'lexical'].includes(column); + }); + } else if (frame.options.selectRaw) { + frame.options.selectRaw = frame.options.selectRaw.split(',').map((column) => { + return column.trim(); + }).filter((column) => { + return !['mobiledoc', 'lexical'].includes(column); + }).join(','); + } +} + function defaultRelations(frame) { if (frame.options.withRelated) { return; @@ -97,7 +125,10 @@ module.exports = { forcePageFilter(frame); if (localUtils.isContentAPI(frame)) { - removeSourceFormats(frame); + // CASE: the content api endpoint for posts should not return mobiledoc or lexical + removeSourceFormats(frame); // remove from the format field + selectAllAllowedColumns(frame); // remove from any specified column or selectRaw options + setDefaultOrder(frame); forceVisibilityColumn(frame); } diff --git a/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js b/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js index 6a30fd39ca..ed8ed3cf78 100644 --- a/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js +++ b/ghost/core/test/unit/api/canary/utils/serializers/input/pages.test.js @@ -1,6 +1,7 @@ const should = require('should'); const sinon = require('sinon'); const serializers = require('../../../../../../../core/server/api/endpoints/utils/serializers'); +const postsSchema = require('../../../../../../../core/server/data/schema').tables.posts; const mobiledocLib = require('@tryghost/html-to-mobiledoc'); @@ -67,6 +68,65 @@ describe('Unit: endpoints/utils/serializers/input/pages', function () { frame.options.formats.should.containEql('html'); frame.options.formats.should.containEql('plaintext'); }); + + describe('Content API', function () { + it('selects all columns from the posts schema but mobiledoc and lexical when no columns are specified', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {} + } + }; + + serializers.input.pages.browse(apiConfig, frame); + const columns = Object.keys(postsSchema); + const parsedSelectRaw = frame.options.selectRaw.split(',').map(column => column.trim()); + parsedSelectRaw.should.eql(columns.filter(column => !['mobiledoc', 'lexical','@@UNIQUE_CONSTRAINTS@@','@@INDEXES@@'].includes(column))); + }); + + it('strips mobiledoc and lexical columns from a specified columns option', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {}, + columns: ['id', 'mobiledoc', 'lexical', 'visibility'] + } + }; + + serializers.input.pages.browse(apiConfig, frame); + frame.options.columns.should.eql(['id', 'visibility']); + }); + + it('forces visibility column if columns are specified', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {}, + columns: ['id'] + } + }; + + serializers.input.pages.browse(apiConfig, frame); + frame.options.columns.should.eql(['id', 'visibility']); + }); + + it('strips mobiledoc and lexical columns from a specified selectRaw option', function () { + const apiConfig = {}; + const frame = { + apiType: 'content', + options: { + context: {}, + selectRaw: 'id, mobiledoc, lexical' + } + }; + + serializers.input.posts.browse(apiConfig, frame); + frame.options.selectRaw.should.eql('id'); + }); + }); }); describe('read', function () {