Prevented pages content api queries from returning mobiledoc or lexical fields (#20454)

ref https://linear.app/tryghost/issue/CFR-43/
ref 9d9a421

We recently stopped `select *` from posts when making Content API
requests. This is now being applied to the pages endpoint to help
improve performance. These fields were already being stripped out in the
output serializer, and they will now no longer be returned from the db
at all, reducing the amount of data transferred.
This commit is contained in:
Steve Larson 2024-06-24 10:17:45 -05:00 committed by GitHub
parent b9240271fe
commit b10b81b7d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 92 additions and 1 deletions

View File

@ -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);
}

View File

@ -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 () {