From e394b5ad9c473e4d57ae08b77561a6ec718a42e2 Mon Sep 17 00:00:00 2001 From: Nazar Gargol Date: Thu, 25 Jun 2020 16:32:16 +1200 Subject: [PATCH] Added naive settings type options parameter support to settings API v2 refs TryGhost/Ghost#10318 refs 8fc526ff6 - This is symetric change to one done for v3 API (commited as 8fc526ff6) - Added 'core' filtering for v2 API controller --- core/server/api/v2/settings.js | 10 ++-- .../v2/utils/serializers/input/settings.js | 9 +++ .../input/utils/settings-type-group-mapper.js | 45 +++++++++++++++ .../v2/utils/serializers/output/settings.js | 6 +- .../serializers/output/utils/extra-attrs.js | 6 ++ .../utils/settings-type-group-mapper.js | 20 +++++++ test/regression/api/v2/admin/settings_spec.js | 57 +++++++++++++++++-- 7 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js create mode 100644 core/server/api/v2/utils/serializers/output/utils/settings-type-group-mapper.js diff --git a/core/server/api/v2/settings.js b/core/server/api/v2/settings.js index f25ec419c4..3c320b798e 100644 --- a/core/server/api/v2/settings.js +++ b/core/server/api/v2/settings.js @@ -18,14 +18,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'; return !isCore; }); } @@ -60,7 +60,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') })); @@ -84,7 +84,7 @@ module.exports = { const errors = []; frame.data.settings.map((setting) => { - if (setting.type === 'core' && !(frame.options.context && frame.options.context.internal)) { + if (setting.group === 'core' && !(frame.options.context && frame.options.context.internal)) { errors.push(new NoPermissionError({ message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq') })); @@ -120,7 +120,7 @@ module.exports = { key: setting.key }) })); - } else if (settingFromCache.type === 'core' && !(frame.options.context && frame.options.context.internal)) { + } else if (settingFromCache.core === 'core' && !(frame.options.context && frame.options.context.internal)) { // @TODO: handle in settings model permissible fn errors.push(new NoPermissionError({ message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq') diff --git a/core/server/api/v2/utils/serializers/input/settings.js b/core/server/api/v2/utils/serializers/input/settings.js index 84d906cdc9..529da4dfee 100644 --- a/core/server/api/v2/utils/serializers/input/settings.js +++ b/core/server/api/v2/utils/serializers/input/settings.js @@ -1,7 +1,16 @@ const _ = require('lodash'); const url = require('./utils/url'); +const typeGroupMapper = require('./utils/settings-type-group-mapper'); module.exports = { + browse(apiConfig, frame) { + if (frame.options.type) { + let mappedGroupOptions = typeGroupMapper(frame.options.type); + + frame.options.group = mappedGroupOptions; + } + }, + read(apiConfig, frame) { if (frame.options.key === 'ghost_head') { frame.options.key = 'codeinjection_head'; diff --git a/core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js b/core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js new file mode 100644 index 0000000000..4534377a21 --- /dev/null +++ b/core/server/api/v2/utils/serializers/input/utils/settings-type-group-mapper.js @@ -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; diff --git a/core/server/api/v2/utils/serializers/output/settings.js b/core/server/api/v2/utils/serializers/output/settings.js index f67e615095..058cefb208 100644 --- a/core/server/api/v2/utils/serializers/output/settings.js +++ b/core/server/api/v2/utils/serializers/output/settings.js @@ -13,10 +13,10 @@ const deprecatedSettings = ['secondary_nav']; * @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) && !_.includes(deprecatedSettings, setting.key); + if (filteredGroups) { + return _.includes(filteredGroups, setting.group) && !_.includes(deprecatedSettings, setting.key); } return !_.includes(deprecatedSettings, setting.key); diff --git a/core/server/api/v2/utils/serializers/output/utils/extra-attrs.js b/core/server/api/v2/utils/serializers/output/utils/extra-attrs.js index 140aee1451..ed69394bbf 100644 --- a/core/server/api/v2/utils/serializers/output/utils/extra-attrs.js +++ b/core/server/api/v2/utils/serializers/output/utils/extra-attrs.js @@ -19,10 +19,16 @@ module.exports.forPost = (frame, model, attrs) => { module.exports.forSettings = (attrs, frame) => { const _ = require('lodash'); + const mapGroupToType = require('./settings-type-group-mapper'); // @TODO: https://github.com/TryGhost/Ghost/issues/10106 // @NOTE: Admin & Content API return a different format, needs two mappers if (_.isArray(attrs)) { + attrs.forEach((attr) => { + attr.type = mapGroupToType(attr.group); + delete attr.group; + }); + // CASE: read single setting if (frame.original.params && frame.original.params.key) { if (frame.original.params.key === 'ghost_head') { diff --git a/core/server/api/v2/utils/serializers/output/utils/settings-type-group-mapper.js b/core/server/api/v2/utils/serializers/output/utils/settings-type-group-mapper.js new file mode 100644 index 0000000000..b59e39c818 --- /dev/null +++ b/core/server/api/v2/utils/serializers/output/utils/settings-type-group-mapper.js @@ -0,0 +1,20 @@ +const groupTypeMapping = { + core: 'core', + amp: 'blog', + labs: 'blog', + slack: 'blog', + unsplash: 'blog', + views: 'blog', + theme: 'theme', + members: 'members', + private: 'private', + portal: 'portal', + email: 'bulk_email', + site: 'site' +}; + +const mapGroupToType = (group) => { + return groupTypeMapping[group]; +}; + +module.exports = mapGroupToType; diff --git a/test/regression/api/v2/admin/settings_spec.js b/test/regression/api/v2/admin/settings_spec.js index 83b5b5a69c..373cdc4915 100644 --- a/test/regression/api/v2/admin/settings_spec.js +++ b/test/regression/api/v2/admin/settings_spec.js @@ -113,6 +113,55 @@ describe('Settings API (v2)', function () { }); }); + it('Can not request settings by group, returns all settings instead', 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(39); + settings.map(s => s.key).should.deepEqual(defaultSettingsKeys); + + localUtils.API.checkResponse(jsonResponse, 'settings'); + }); + }); + + it('Can request settings by type and ignores group ', 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(3); + settings[0].key.should.equal('is_private'); + settings[0].value.should.equal(false); + settings[0].type.should.equal('private'); + + testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); + 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')) @@ -178,7 +227,7 @@ describe('Settings API (v2)', function () { jsonResponse.settings.length.should.eql(1); - testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); + testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); jsonResponse.settings[0].key.should.eql('default_locale'); done(); }); @@ -203,7 +252,7 @@ describe('Settings API (v2)', function () { jsonResponse.settings.length.should.eql(1); - testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); + testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); jsonResponse.settings[0].key.should.eql('active_timezone'); done(); }); @@ -228,7 +277,7 @@ describe('Settings API (v2)', function () { jsonResponse.settings.length.should.eql(1); - testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); + testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); jsonResponse.settings[0].key.should.eql('ghost_head'); done(); }); @@ -253,7 +302,7 @@ describe('Settings API (v2)', function () { jsonResponse.settings.length.should.eql(1); - testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); + testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']); jsonResponse.settings[0].key.should.eql('codeinjection_foot'); done(); });