From cddf786424af4d4540b7265379b07dca1f1fb00d Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 20 Apr 2023 17:23:06 +0200 Subject: [PATCH] Added filtering of announcement bar content refs https://github.com/TryGhost/Team/issues/3051 - We need to show the announcement_content to specific audiences based on the announcement_visibility filter --- ghost/announcement-bar-settings/.eslintrc.js | 6 + ghost/announcement-bar-settings/README.md | 23 ++++ ghost/announcement-bar-settings/index.js | 1 + .../lib/AnnouncementBarSettings.js | 54 +++++++++ ghost/announcement-bar-settings/package.json | 26 +++++ .../test/.eslintrc.js | 6 + .../test/AnnouncementBarSettings.test.js | 105 ++++++++++++++++++ .../server/api/endpoints/settings-public.js | 16 ++- .../announcement-bar-service/index.js | 12 ++ .../web/api/endpoints/content/routes.js | 3 +- .../core/core/shared/settings-cache/cache.js | 2 +- .../core/core/shared/settings-cache/public.js | 4 +- ghost/core/package.json | 1 + .../__snapshots__/settings.test.js.snap | 2 - .../shared/__snapshots__/version.test.js.snap | 4 - yarn.lock | 12 ++ 16 files changed, 261 insertions(+), 16 deletions(-) create mode 100644 ghost/announcement-bar-settings/.eslintrc.js create mode 100644 ghost/announcement-bar-settings/README.md create mode 100644 ghost/announcement-bar-settings/index.js create mode 100644 ghost/announcement-bar-settings/lib/AnnouncementBarSettings.js create mode 100644 ghost/announcement-bar-settings/package.json create mode 100644 ghost/announcement-bar-settings/test/.eslintrc.js create mode 100644 ghost/announcement-bar-settings/test/AnnouncementBarSettings.test.js create mode 100644 ghost/core/core/server/services/announcement-bar-service/index.js diff --git a/ghost/announcement-bar-settings/.eslintrc.js b/ghost/announcement-bar-settings/.eslintrc.js new file mode 100644 index 0000000000..c9c1bcb522 --- /dev/null +++ b/ghost/announcement-bar-settings/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: ['ghost'], + extends: [ + 'plugin:ghost/node' + ] +}; diff --git a/ghost/announcement-bar-settings/README.md b/ghost/announcement-bar-settings/README.md new file mode 100644 index 0000000000..8343ce5a82 --- /dev/null +++ b/ghost/announcement-bar-settings/README.md @@ -0,0 +1,23 @@ +# Announcement Bar Settings + +Announcement Bar settings logic + + +## Usage + + +## Develop + +This is a monorepo package. + +Follow the instructions for the top-level repo. +1. `git clone` this repo & `cd` into it as usual +2. Run `yarn` to install top-level dependencies. + + + +## Test + +- `yarn lint` run just eslint +- `yarn test` run lint and tests + diff --git a/ghost/announcement-bar-settings/index.js b/ghost/announcement-bar-settings/index.js new file mode 100644 index 0000000000..58a1c41d4a --- /dev/null +++ b/ghost/announcement-bar-settings/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/AnnouncementBarSettings'); diff --git a/ghost/announcement-bar-settings/lib/AnnouncementBarSettings.js b/ghost/announcement-bar-settings/lib/AnnouncementBarSettings.js new file mode 100644 index 0000000000..b5bcf61e5e --- /dev/null +++ b/ghost/announcement-bar-settings/lib/AnnouncementBarSettings.js @@ -0,0 +1,54 @@ +class AnnouncementBarSettings { + #getAnnouncementSettings; + + /** + * + * @param {Object} deps + * @param {() => {announcement: string, announcement_visibility: string[], announcement_background: string}} deps.getAnnouncementSettings + */ + constructor(deps) { + this.#getAnnouncementSettings = deps.getAnnouncementSettings; + } + + /** + * @param {Object} [member] + * @param {string} member.status + * @returns {{announcement: string, announcement_background: string}} + */ + getAnnouncementSettings(member) { + let announcement = undefined; + + // NOTE: combination of 'free_members' & 'paid_members' makes just a 'members' filter + const announcementSettings = this.#getAnnouncementSettings(); + + if (announcementSettings.announcement) { + const visibilities = announcementSettings.announcement_visibility; + const announcementContent = announcementSettings.announcement; + + // Available visibilities: + // 'visitors', // Logged out visitors + // 'free_members', // Free members + // 'paid_members' // Paid members (aka non-free members) + if (visibilities.length === 0) { + announcement = undefined; + } else { + if (visibilities.includes('visitors') && !member) { + announcement = announcementContent; + } else if (visibilities.includes('free_members') && (member?.status === 'free')) { + announcement = announcementContent; + } else if (visibilities.includes('paid_members') && (member?.status !== 'free')) { + announcement = announcementContent; + } + } + } + + if (announcement !== undefined) { + return { + announcement, + announcement_background: announcementSettings.announcement_background + }; + } + } +} + +module.exports = AnnouncementBarSettings; diff --git a/ghost/announcement-bar-settings/package.json b/ghost/announcement-bar-settings/package.json new file mode 100644 index 0000000000..c3b7ad17fb --- /dev/null +++ b/ghost/announcement-bar-settings/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tryghost/announcement-bar-settings", + "version": "0.0.0", + "repository": "https://github.com/TryGhost/Ghost/tree/main/packages/announcement-bar-settings", + "author": "Ghost Foundation", + "private": true, + "main": "index.js", + "scripts": { + "dev": "echo \"Implement me!\"", + "test:unit": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "yarn test:unit", + "lint:code": "eslint *.js lib/ --ext .js --cache", + "lint": "yarn lint:code && yarn lint:test", + "lint:test": "eslint -c test/.eslintrc.js test/ --ext .js --cache" + }, + "files": [ + "index.js", + "lib" + ], + "devDependencies": { + "c8": "7.13.0", + "mocha": "10.2.0", + "sinon": "15.0.4" + }, + "dependencies": {} +} diff --git a/ghost/announcement-bar-settings/test/.eslintrc.js b/ghost/announcement-bar-settings/test/.eslintrc.js new file mode 100644 index 0000000000..829b601eb0 --- /dev/null +++ b/ghost/announcement-bar-settings/test/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: ['ghost'], + extends: [ + 'plugin:ghost/test' + ] +}; diff --git a/ghost/announcement-bar-settings/test/AnnouncementBarSettings.test.js b/ghost/announcement-bar-settings/test/AnnouncementBarSettings.test.js new file mode 100644 index 0000000000..911cedaaa1 --- /dev/null +++ b/ghost/announcement-bar-settings/test/AnnouncementBarSettings.test.js @@ -0,0 +1,105 @@ +const assert = require('assert'); +const AnnouncementBarSettings = require('../index'); + +describe('AnnouncementBarSettings', function () { + it('can initialize', function () { + const announcementBarSettings = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: 'Hello world', + announcement_visibility: ['visitors'], + announcement_background: 'dark' + }) + }); + + assert.ok(announcementBarSettings); + }); + + describe('getAnnouncementSettings', function () { + it('returns undefined if there is no announcement content', function () { + const announcementBarSettings = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: null, + announcement_visibility: [], + announcement_background: 'dark' + }) + }); + + const settings = announcementBarSettings.getAnnouncementSettings(); + + assert.equal(settings, undefined); + }); + + it('returns undefined announcement settings if there is no announcement visibility', function () { + const announcementBarSettings = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: 'Hello world', + announcement_visibility: [], + announcement_background: 'dark' + }) + }); + + const settings = announcementBarSettings.getAnnouncementSettings(); + + assert.equal(settings, undefined); + }); + + it('returns announcement if visibility is set to visitors and there is no logged in member', function () { + const announcementBarSettings = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: 'Hello world', + announcement_visibility: ['visitors'], + announcement_background: 'dark' + }) + }); + + const settings = announcementBarSettings.getAnnouncementSettings(); + + assert.equal(settings.announcement, 'Hello world'); + }); + + it('returns announcement if visibility is set to free members and member is free', function () { + const announcementBarSettings = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: 'Hello world', + announcement_visibility: ['free_members'], + announcement_background: 'dark' + }) + }); + + const settings = announcementBarSettings.getAnnouncementSettings({ + status: 'free' + }); + assert.equal(settings.announcement, 'Hello world'); + }); + + it('returns announcement if visibility is set to paid members and member is paid', function () { + const announcementBarSettings = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: 'Hello world', + announcement_visibility: ['paid_members'], + announcement_background: 'dark' + }) + }); + + const settings = announcementBarSettings.getAnnouncementSettings({ + status: 'paid' + }); + assert.equal(settings.announcement, 'Hello world'); + }); + + it('returns announcement if visibility is set to paid and paid members and member is comped', function () { + const announcementBarSettings = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: 'Hello world', + announcement_visibility: ['paid_members'], + announcement_background: 'dark' + }) + }); + + const settings = announcementBarSettings.getAnnouncementSettings({ + status: 'comped' + }); + assert.equal(settings.announcement, 'Hello world'); + }); + }); +}); diff --git a/ghost/core/core/server/api/endpoints/settings-public.js b/ghost/core/core/server/api/endpoints/settings-public.js index 74c0bbf5f4..9c4d3c3b30 100644 --- a/ghost/core/core/server/api/endpoints/settings-public.js +++ b/ghost/core/core/server/api/endpoints/settings-public.js @@ -1,19 +1,25 @@ const settingsCache = require('../../../shared/settings-cache'); const urlUtils = require('../../../shared/url-utils'); const ghostVersion = require('@tryghost/version'); +const announcementBarSettings = require('../../services/announcement-bar-service'); module.exports = { docName: 'settings', browse: { permissions: true, - query() { + query(frame) { + const announcementSettings = announcementBarSettings.getAnnouncementSettings(frame.options.context?.member); + // @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), - version: ghostVersion.safe - }); + return Object.assign({}, + settingsCache.getPublic(), + announcementSettings, { + url: urlUtils.urlFor('home', true), + version: ghostVersion.safe + } + ); } } }; diff --git a/ghost/core/core/server/services/announcement-bar-service/index.js b/ghost/core/core/server/services/announcement-bar-service/index.js new file mode 100644 index 0000000000..a2aa8a66ea --- /dev/null +++ b/ghost/core/core/server/services/announcement-bar-service/index.js @@ -0,0 +1,12 @@ +const settingsCache = require('../../../shared/settings-cache'); +const AnnouncementBarSettings = require('@tryghost/announcement-bar-settings'); + +const announcementBarService = new AnnouncementBarSettings({ + getAnnouncementSettings: () => ({ + announcement: settingsCache.get('announcement_content'), + announcement_background: settingsCache.get('announcement_background'), + announcement_visibility: settingsCache.get('announcement_visibility') + }) +}); + +module.exports = announcementBarService; diff --git a/ghost/core/core/server/web/api/endpoints/content/routes.js b/ghost/core/core/server/web/api/endpoints/content/routes.js index bc15a0823d..de40d537c5 100644 --- a/ghost/core/core/server/web/api/endpoints/content/routes.js +++ b/ghost/core/core/server/web/api/endpoints/content/routes.js @@ -4,6 +4,7 @@ const api = require('../../../../api').endpoints; const {http} = require('@tryghost/api-framework'); const mw = require('./middleware'); const config = require('../../../../../shared/config'); +const membersService = require('../../../../../server/services/members'); module.exports = function apiRoutes() { const router = express.Router('content api'); @@ -31,7 +32,7 @@ module.exports = function apiRoutes() { router.get('/tags/slug/:slug', mw.authenticatePublic, http(api.tagsPublic.read)); // ## Settings - router.get('/settings', mw.authenticatePublic, http(api.publicSettings.browse)); + router.get('/settings', mw.authenticatePublic, membersService.middleware.loadMemberSession, http(api.publicSettings.browse)); // ## Members router.get('/newsletters', mw.authenticatePublic, http(api.newslettersPublic.browse)); diff --git a/ghost/core/core/shared/settings-cache/cache.js b/ghost/core/core/shared/settings-cache/cache.js index 9dade7f7ad..1e38b87130 100644 --- a/ghost/core/core/shared/settings-cache/cache.js +++ b/ghost/core/core/shared/settings-cache/cache.js @@ -135,7 +135,7 @@ class CacheManager { } /** - * Get all the publically accessible cache entries with their correct names + * Get all the publicly accessible cache entries with their correct names * Uses clone to prevent modifications from being reflected * @return {object} cache */ diff --git a/ghost/core/core/shared/settings-cache/public.js b/ghost/core/core/shared/settings-cache/public.js index 8f432024a9..2098bb4ac9 100644 --- a/ghost/core/core/shared/settings-cache/public.js +++ b/ghost/core/core/shared/settings-cache/public.js @@ -39,7 +39,5 @@ module.exports = { portal_plans: 'portal_plans', portal_name: 'portal_name', portal_button: 'portal_button', - comments_enabled: 'comments_enabled', - announcement: 'announcement_content', - announcement_background: 'announcement_background' + comments_enabled: 'comments_enabled' }; diff --git a/ghost/core/package.json b/ghost/core/package.json index 05576406cd..b8349d56e7 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -63,6 +63,7 @@ "@tryghost/adapter-cache-redis": "0.0.0", "@tryghost/adapter-manager": "0.0.0", "@tryghost/admin-api-schema": "4.3.0", + "@tryghost/announcement-bar-settings": "0.0.0", "@tryghost/api-framework": "0.0.0", "@tryghost/api-version-compatibility-service": "0.0.0", "@tryghost/audience-feedback": "0.0.0", diff --git a/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap b/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap index 44b45e78ea..6a14321368 100644 --- a/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap +++ b/ghost/core/test/e2e-api/content/__snapshots__/settings.test.js.snap @@ -5,8 +5,6 @@ Object { "meta": Object {}, "settings": Object { "accent_color": "#FF1A75", - "announcement": null, - "announcement_background": "dark", "codeinjection_foot": null, "codeinjection_head": null, "comments_enabled": "off", diff --git a/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap b/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap index 82b88656ad..b4acfca7d9 100644 --- a/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap +++ b/ghost/core/test/e2e-api/shared/__snapshots__/version.test.js.snap @@ -1339,8 +1339,6 @@ Object { "meta": Object {}, "settings": Object { "accent_color": "#FF1A75", - "announcement": null, - "announcement_background": "dark", "codeinjection_foot": null, "codeinjection_head": null, "comments_enabled": "off", @@ -1437,8 +1435,6 @@ Object { "meta": Object {}, "settings": Object { "accent_color": "#FF1A75", - "announcement": null, - "announcement_background": "dark", "codeinjection_foot": null, "codeinjection_head": null, "comments_enabled": "off", diff --git a/yarn.lock b/yarn.lock index 0848b850e8..c3072e062f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27498,6 +27498,18 @@ sinon@15.0.3: nise "^5.1.4" supports-color "^7.2.0" +sinon@15.0.4: + version "15.0.4" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-15.0.4.tgz#bcca6fef19b14feccc96473f0d7adc81e0bc5268" + integrity sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^10.0.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.1.0" + nise "^5.1.4" + supports-color "^7.2.0" + sinon@^9.0.0: version "9.2.4" resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b"