Added back compatible support for renamed settings types

refs https://github.com/TryGhost/Ghost/issues/10318
refs 1dc0405803

- Adds 1:1 mapping for filtering options  to renamed settings "type" to "gorup"
- Ignores the name changes and any old types
- Detailsed type -> group mappings can be checked in the refereneced migration commit
This commit is contained in:
Nazar Gargol 2020-06-25 00:55:40 +12:00
parent 1dc0405803
commit 8fc526ff6e
6 changed files with 154 additions and 14 deletions

View File

@ -17,7 +17,7 @@ module.exports = {
docName: 'settings',
browse: {
options: ['type'],
options: ['type', 'group'],
permissions: true,
query(frame) {
let settings = settingsCache.getAll();
@ -25,14 +25,14 @@ module.exports = {
// CASE: no context passed (functional call)
if (!frame.options.context) {
return Promise.resolve(settings.filter((setting) => {
return setting.type === 'site';
return setting.group === 'site';
}));
}
// CASE: omit core settings unless internal request
if (!frame.options.context.internal) {
settings = _.filter(settings, (setting) => {
const isCore = setting.type === 'core';
const isCore = setting.group === 'core';
const isBlacklisted = SETTINGS_BLACKLIST.includes(setting.key);
return !isBlacklisted && !isCore;
});
@ -68,7 +68,7 @@ module.exports = {
}
// @TODO: handle in settings model permissible fn
if (setting.type === 'core' && !(frame.options.context && frame.options.context.internal)) {
if (setting.group === 'core' && !(frame.options.context && frame.options.context.internal)) {
return Promise.reject(new NoPermissionError({
message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')
}));
@ -181,7 +181,7 @@ module.exports = {
return;
}
const firstCoreSetting = frame.data.settings.find(setting => setting.type === 'core');
const firstCoreSetting = frame.data.settings.find(setting => setting.group === 'core');
if (firstCoreSetting) {
throw new NoPermissionError({
message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')
@ -211,7 +211,7 @@ module.exports = {
}
if (!(frame.options.context && frame.options.context.internal)) {
const firstCoreSetting = settings.find(setting => getSetting(setting).type === 'core');
const firstCoreSetting = settings.find(setting => getSetting(setting).group === 'core');
if (firstCoreSetting) {
throw new NoPermissionError({
message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')

View File

@ -1,8 +1,21 @@
const _ = require('lodash');
const url = require('./utils/url');
const typeGroupMapper = require('./utils/settings-type-group-mapper');
const settingsCache = require('../../../../../services/settings/cache');
module.exports = {
browse(apiConfig, frame) {
if (frame.options.type) {
let mappedGroupOptions = typeGroupMapper(frame.options.type);
if (frame.options.group) {
frame.options.group = `${frame.options.group},${mappedGroupOptions}`;
} else {
frame.options.group = mappedGroupOptions;
}
}
},
read(apiConfig, frame) {
if (frame.options.key === 'ghost_head') {
frame.options.key = 'codeinjection_head';

View File

@ -0,0 +1,45 @@
const typeGroupMapping = {
core: [
'core'
],
blog: [
'site',
'amp',
'labs',
'slack',
'unsplash',
'views'
],
theme: [
'theme'
],
members: [
'members'
],
private: [
'private'
],
portal: [
'portal'
],
bulk_email: [
'email'
],
site: [
'site'
]
};
const mapTypeToGroup = (typeOptions) => {
const types = typeOptions.split(',');
const mappedTypes = types.map((type) => {
const sanitizedType = type ? type.trim() : null;
return typeGroupMapping[sanitizedType];
}).filter(type => !!type);
return mappedTypes.join(',');
};
module.exports = mapTypeToGroup;

View File

@ -12,10 +12,10 @@ const _private = {};
* @returns {*}
*/
_private.settingsFilter = (settings, filter) => {
let filteredTypes = filter ? filter.split(',') : false;
let filteredGroups = filter ? filter.split(',') : false;
return _.filter(settings, (setting) => {
if (filteredTypes) {
return _.includes(filteredTypes, setting.type);
if (filteredGroups) {
return _.includes(filteredGroups, setting.group);
}
return true;
@ -30,7 +30,7 @@ module.exports = {
if (utils.isContentAPI(frame)) {
filteredSettings = models;
} else {
filteredSettings = _.values(_private.settingsFilter(models, frame.options.type));
filteredSettings = _.values(_private.settingsFilter(models, frame.options.group));
}
frame.response = {
@ -38,10 +38,16 @@ module.exports = {
meta: {}
};
if (frame.options.type) {
frame.response.meta.filters = {
type: frame.options.type
};
if (frame.options.type || frame.options.group) {
frame.response.meta.filters = {};
if (frame.options.type) {
frame.response.meta.filters.type = frame.options.type;
}
if (frame.options.group) {
frame.response.meta.filters.group = frame.options.group;
}
}
},

View File

@ -114,6 +114,56 @@ describe('Settings API (canary)', function () {
});
});
it('Can request settings by group', function () {
return request.get(localUtils.API.getApiQuery(`settings/?group=theme`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.settings);
should.exist(jsonResponse.meta);
jsonResponse.settings.should.be.an.Object();
const settings = jsonResponse.settings;
Object.keys(settings).length.should.equal(1);
settings[0].key.should.equal('active_theme');
settings[0].value.should.equal('casper');
settings[0].type.should.equal('theme');
localUtils.API.checkResponse(jsonResponse, 'settings');
});
});
it('Can request settings by group and by deprecated type', function () {
return request.get(localUtils.API.getApiQuery(`settings/?group=theme&type=private`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.settings);
should.exist(jsonResponse.meta);
jsonResponse.settings.should.be.an.Object();
const settings = jsonResponse.settings;
Object.keys(settings).length.should.equal(4);
settings[0].key.should.equal('active_theme');
settings[0].value.should.equal('casper');
settings[0].type.should.equal('theme');
localUtils.API.checkResponse(jsonResponse, 'settings');
});
});
it('Requesting core settings type returns no results', function () {
return request.get(localUtils.API.getApiQuery(`settings/?type=core`))
.set('Origin', config.get('url'))

View File

@ -0,0 +1,26 @@
const should = require('should');
const mapper = require('../../../../../../../../core//server/api/canary/utils/serializers/input/utils/settings-type-group-mapper');
describe('Unit: canary/utils/serializers/input/utils/settings-type-group-mapper', function () {
describe('browse', function () {
it('maps type to group 1:1', function () {
mapper('theme').should.eql('theme');
});
it('maps type to multiple groups', function () {
mapper('blog').should.eql('site,amp,labs,slack,unsplash,views');
});
it('maps multiple types to multiple groups', function () {
mapper('bulk_email,portal').should.eql('email,portal');
});
it('skips unknown options for "bulk_email,unknown,portal" type to "bulk_email,portal', function () {
mapper('bulk_email,unknown,portal').should.eql('email,portal');
});
it('handles unexpected spacing', function () {
mapper(' bulk_email, portal ').should.eql('email,portal');
});
});
});