Extended public settings to include portal settings (#14801)

refs https://github.com/TryGhost/Team/issues/1599

- adds `portal_*` settings to public settings endpoint
- adds calculated `firstpromoter_account` setting for public settings endpoint
- also adds Ghost `version` information
This commit is contained in:
Rishabh Garg 2022-05-12 19:54:45 +05:30 committed by GitHub
parent de118b0b04
commit f4066067e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 92 additions and 24 deletions

View File

@ -1,5 +1,6 @@
const settingsCache = require('../../../shared/settings-cache');
const urlUtils = require('../../../shared/url-utils');
const ghostVersion = require('@tryghost/version');
module.exports = {
docName: 'settings',
@ -10,7 +11,8 @@ module.exports = {
// @TODO: decouple settings cache from API knowledge
// The controller fetches models (or cached models) and the API frame for the target API version formats the response.
return Object.assign({}, settingsCache.getPublic(), {
url: urlUtils.urlFor('home', true)
url: urlUtils.urlFor('home', true),
version: ghostVersion.safe
});
}
}

View File

@ -122,6 +122,13 @@ module.exports = {
return this.isMembersEnabled() && this.getActiveStripeKeys() !== null;
},
getFirstpromoterId() {
if (!SettingsCache.get('firstpromoter')) {
return null;
}
return SettingsCache.get('firstpromoter_id');
},
/**
*
*/
@ -131,6 +138,7 @@ module.exports = {
fields.push(new CalculatedField({key: 'members_enabled', type: 'boolean', group: 'members', fn: this.isMembersEnabled.bind(this), dependents: ['members_signup_access']}));
fields.push(new CalculatedField({key: 'members_invite_only', type: 'boolean', group: 'members', fn: this.isMembersInviteOnly.bind(this), dependents: ['members_signup_access']}));
fields.push(new CalculatedField({key: 'paid_members_enabled', type: 'boolean', group: 'members', fn: this.isStripeConnected.bind(this), dependents: ['members_signup_access', 'stripe_secret_key', 'stripe_publishable_key', 'stripe_connect_secret_key', 'stripe_connect_publishable_key']}));
fields.push(new CalculatedField({key: 'firstpromoter_account', type: 'string', group: 'firstpromoter', fn: this.getFirstpromoterId.bind(this), dependents: ['firstpromoter', 'firstpromoter_id']}));
return fields;
},

View File

@ -29,5 +29,12 @@ module.exports = {
members_support_address: 'members_support_address',
members_enabled: 'members_enabled',
members_invite_only: 'members_invite_only',
paid_members_enabled: 'paid_members_enabled'
paid_members_enabled: 'paid_members_enabled',
firstpromoter_account: 'firstpromoter_account',
portal_button_style: 'portal_button_style',
portal_button_signup_text: 'portal_button_signup_text',
portal_button_icon: 'portal_button_icon',
portal_plans: 'portal_plans',
portal_name: 'portal_name',
portal_button: 'portal_button'
};

View File

@ -1082,6 +1082,15 @@ Object {
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"value": true,
},
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"group": "firstpromoter",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"key": "firstpromoter_account",
"type": "string",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"value": null,
},
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"flags": null,
@ -1112,7 +1121,7 @@ exports[`Settings API Can request all settings 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "18289",
"content-length": "18486",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",

View File

@ -2,7 +2,7 @@ const assert = require('assert');
const {agentProvider, fixtureManager, mockManager, matchers} = require('../../utils/e2e-framework');
const {stringMatching, anyEtag, anyObjectId, anyISODateTime} = matchers;
const CURRENT_SETTINGS_COUNT = 86;
const CURRENT_SETTINGS_COUNT = 87;
const settingsMatcher = {
id: anyObjectId,

View File

@ -10,6 +10,7 @@ Object {
"cover_image": "https://static.ghost.org/v4.0.0/images/publication-cover.jpg",
"description": "Thoughts, stories and ideas",
"facebook": "ghost",
"firstpromoter_account": null,
"icon": null,
"lang": "en",
"locale": "en",
@ -45,6 +46,14 @@ Object {
"og_image": null,
"og_title": null,
"paid_members_enabled": true,
"portal_button": true,
"portal_button_icon": null,
"portal_button_signup_text": "Subscribe",
"portal_button_style": "icon-and-text",
"portal_name": true,
"portal_plans": Array [
"free",
],
"secondary_navigation": Array [
Object {
"label": "Data & privacy",
@ -66,6 +75,7 @@ Object {
"twitter_image": null,
"twitter_title": null,
"url": "http://127.0.0.1:2369/",
"version": Any<String>,
},
}
`;
@ -86,7 +96,7 @@ exports[`Settings Content API Can request settings 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1021",
"content-length": "1235",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",

View File

@ -1,6 +1,10 @@
const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
const {anyEtag} = matchers;
const settingsMatcher = {
version: matchers.anyString
};
describe('Settings Content API', function () {
let agent;
@ -16,6 +20,8 @@ describe('Settings Content API', function () {
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot();
.matchBodySnapshot({
settings: settingsMatcher
});
});
});

View File

@ -488,6 +488,7 @@ Object {
"cover_image": "https://static.ghost.org/v4.0.0/images/publication-cover.jpg",
"description": "Thoughts, stories and ideas",
"facebook": "ghost",
"firstpromoter_account": null,
"icon": null,
"lang": "en",
"locale": "en",
@ -523,6 +524,14 @@ Object {
"og_image": null,
"og_title": null,
"paid_members_enabled": true,
"portal_button": true,
"portal_button_icon": null,
"portal_button_signup_text": "Subscribe",
"portal_button_style": "icon-and-text",
"portal_name": true,
"portal_plans": Array [
"free",
],
"secondary_navigation": Array [
Object {
"label": "Data & privacy",
@ -544,6 +553,7 @@ Object {
"twitter_image": null,
"twitter_title": null,
"url": "http://127.0.0.1:2369/",
"version": Any<String>,
},
}
`;
@ -552,10 +562,10 @@ exports[`API Versioning Content API responds with current content version header
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1021",
"content-length": "1235",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": "W/\\"3fd-PXBX1gYn1ftAeK+GoVbEnAsmVAE\\"",
"etag": "W/\\"4d3-mq2QrkgGnMZ8/BMmziwONEMrvLM\\"",
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
@ -571,6 +581,7 @@ Object {
"cover_image": "https://static.ghost.org/v4.0.0/images/publication-cover.jpg",
"description": "Thoughts, stories and ideas",
"facebook": "ghost",
"firstpromoter_account": null,
"icon": null,
"lang": "en",
"locale": "en",
@ -606,6 +617,14 @@ Object {
"og_image": null,
"og_title": null,
"paid_members_enabled": true,
"portal_button": true,
"portal_button_icon": null,
"portal_button_signup_text": "Subscribe",
"portal_button_style": "icon-and-text",
"portal_name": true,
"portal_plans": Array [
"free",
],
"secondary_navigation": Array [
Object {
"label": "Data & privacy",
@ -627,6 +646,7 @@ Object {
"twitter_image": null,
"twitter_title": null,
"url": "http://127.0.0.1:2369/",
"version": Any<String>,
},
}
`;
@ -635,9 +655,9 @@ exports[`API Versioning Content API responds with no content version header when
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1021",
"content-length": "1235",
"content-type": "application/json; charset=utf-8",
"etag": "W/\\"3fd-PXBX1gYn1ftAeK+GoVbEnAsmVAE\\"",
"etag": "W/\\"4d3-mq2QrkgGnMZ8/BMmziwONEMrvLM\\"",
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}

View File

@ -1,5 +1,11 @@
const {agentProvider, fixtureManager, matchers, mockManager} = require('../../utils/e2e-framework');
const {anyErrorId, stringMatching, anyObjectId, anyLocationFor, anyISODateTime, anyEtag} = matchers;
const {anyErrorId, stringMatching, anyObjectId, anyLocationFor, anyISODateTime, anyEtag, anyString} = matchers;
const settingsMatcher = {
settings: {
version: anyString
}
};
describe('API Versioning', function () {
describe('Admin API', function () {
@ -260,7 +266,7 @@ describe('API Versioning', function () {
await agentContentAPI.get('settings/')
.expectStatus(200)
.matchHeaderSnapshot()
.matchBodySnapshot();
.matchBodySnapshot(settingsMatcher);
});
it('responds with current content version header when requested version is BEHIND current version and CAN respond', async function () {
@ -270,7 +276,7 @@ describe('API Versioning', function () {
.matchHeaderSnapshot({
'content-version': stringMatching(/v\d+\.\d+/)
})
.matchBodySnapshot();
.matchBodySnapshot(settingsMatcher);
});
it('Does an internal rewrite with accept version set when version is included in the URL', async function () {

View File

@ -264,6 +264,16 @@ const defaultSettingsKeyTypes = [
type: 'string',
group: 'portal'
},
{
key: 'firstpromoter',
type: 'boolean',
group: 'firstpromoter'
},
{
key: 'firstpromoter_id',
type: 'string',
group: 'firstpromoter'
},
{
key: 'portal_button_icon',
type: 'string',
@ -384,16 +394,6 @@ const defaultSettingsKeyTypes = [
type: 'string',
group: 'newsletter'
},
{
key: 'firstpromoter',
type: 'boolean',
group: 'firstpromoter'
},
{
key: 'firstpromoter_id',
type: 'string',
group: 'firstpromoter'
},
{
key: 'oauth_client_id',
type: 'string',
@ -426,7 +426,7 @@ const defaultSettingsKeyTypes = [
}
];
const calculatedSettingsTypes = ['members_enabled', 'members_invite_only', 'paid_members_enabled'];
const calculatedSettingsTypes = ['members_enabled', 'members_invite_only', 'paid_members_enabled', 'firstpromoter_account'];
describe('Settings API (canary)', function () {
let request;