From 7ab4c444757f32a20cb6bdce3c41ad6cc0e4e829 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Wed, 26 Jan 2022 16:50:52 +0530 Subject: [PATCH] Updated serialization for handling tiers visibility refs https://github.com/TryGhost/Team/issues/1071 Going forward, if the visibility of a page/post is set for specific tiers, we send a `tiers` array in API response that contains list of tiers with access. This change - - updates post/page mapper to transform existing data where `visibility` is a custom nql string to tiers array - updates default include for post/pages to include `products`, which allows attaching relevant tiers from the pivot table - cleans up usage of `visibility_filter` in serialization --- core/server/api/canary/email-post.js | 2 +- core/server/api/canary/pages-public.js | 2 +- core/server/api/canary/pages.js | 2 +- core/server/api/canary/posts-public.js | 2 +- core/server/api/canary/posts.js | 2 +- .../canary/utils/serializers/input/pages.js | 10 +------ .../canary/utils/serializers/input/posts.js | 10 +------ .../utils/serializers/output/email-posts.js | 4 +-- .../canary/utils/serializers/output/pages.js | 14 ++++++---- .../canary/utils/serializers/output/posts.js | 14 ++++++---- .../utils/serializers/output/preview.js | 5 ++-- .../utils/serializers/output/utils/clean.js | 9 ------- .../utils/serializers/output/utils/mapper.js | 21 ++++++++++++--- core/server/services/posts/posts-service.js | 26 ++++++++++++++++++- 14 files changed, 73 insertions(+), 50 deletions(-) diff --git a/core/server/api/canary/email-post.js b/core/server/api/canary/email-post.js index 20dc3c21f5..089fc852cf 100644 --- a/core/server/api/canary/email-post.js +++ b/core/server/api/canary/email-post.js @@ -1,7 +1,7 @@ const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); const models = require('../../models'); -const ALLOWED_INCLUDES = ['authors', 'tags']; +const ALLOWED_INCLUDES = ['authors', 'tags', 'tiers']; const messages = { postNotFound: 'Post not found.' diff --git a/core/server/api/canary/pages-public.js b/core/server/api/canary/pages-public.js index b0b7002fd1..72e45c56e6 100644 --- a/core/server/api/canary/pages-public.js +++ b/core/server/api/canary/pages-public.js @@ -1,7 +1,7 @@ const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); const models = require('../../models'); -const ALLOWED_INCLUDES = ['tags', 'authors']; +const ALLOWED_INCLUDES = ['tags', 'authors', 'tiers']; const messages = { pageNotFound: 'Page not found.' diff --git a/core/server/api/canary/pages.js b/core/server/api/canary/pages.js index f2512a3102..48b4d8d6de 100644 --- a/core/server/api/canary/pages.js +++ b/core/server/api/canary/pages.js @@ -2,7 +2,7 @@ const models = require('../../models'); const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); const getPostServiceInstance = require('../../services/posts/posts-service'); -const ALLOWED_INCLUDES = ['tags', 'authors', 'authors.roles']; +const ALLOWED_INCLUDES = ['tags', 'authors', 'authors.roles', 'tiers']; const UNSAFE_ATTRS = ['status', 'authors', 'visibility']; const messages = { diff --git a/core/server/api/canary/posts-public.js b/core/server/api/canary/posts-public.js index 437dbc1a89..a9eb750eb6 100644 --- a/core/server/api/canary/posts-public.js +++ b/core/server/api/canary/posts-public.js @@ -1,7 +1,7 @@ const models = require('../../models'); const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); -const allowedIncludes = ['tags', 'authors']; +const allowedIncludes = ['tags', 'authors', 'tiers']; const messages = { postNotFound: 'Post not found.' diff --git a/core/server/api/canary/posts.js b/core/server/api/canary/posts.js index 1534ecb0eb..da69387beb 100644 --- a/core/server/api/canary/posts.js +++ b/core/server/api/canary/posts.js @@ -2,7 +2,7 @@ const models = require('../../models'); const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); const getPostServiceInstance = require('../../services/posts/posts-service'); -const allowedIncludes = ['tags', 'authors', 'authors.roles', 'email']; +const allowedIncludes = ['tags', 'authors', 'authors.roles', 'email', 'tiers']; const unsafeAttrs = ['status', 'authors', 'visibility']; const messages = { diff --git a/core/server/api/canary/utils/serializers/input/pages.js b/core/server/api/canary/utils/serializers/input/pages.js index ff6ec8ec14..9d9532579e 100644 --- a/core/server/api/canary/utils/serializers/input/pages.js +++ b/core/server/api/canary/utils/serializers/input/pages.js @@ -38,7 +38,7 @@ function defaultRelations(frame) { return false; } - frame.options.withRelated = ['tags', 'authors', 'authors.roles']; + frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'tiers']; } function setDefaultOrder(frame) { @@ -102,13 +102,6 @@ const forceStatusFilter = (frame) => { } }; -const transformPageVisibilityFilters = (frame) => { - if (frame.data.pages[0].visibility === 'filter' && frame.data.pages[0].visibility_filter) { - frame.data.pages[0].visibility = frame.data.pages[0].visibility_filter; - } - delete frame.data.pages[0].visibility_filter; -}; - module.exports = { browse(apiConfig, frame) { debug('browse'); @@ -187,7 +180,6 @@ module.exports = { }); } - transformPageVisibilityFilters(frame); handlePostsMeta(frame); defaultFormat(frame); defaultRelations(frame); diff --git a/core/server/api/canary/utils/serializers/input/posts.js b/core/server/api/canary/utils/serializers/input/posts.js index d7be4a2fa6..4df1aee3e5 100644 --- a/core/server/api/canary/utils/serializers/input/posts.js +++ b/core/server/api/canary/utils/serializers/input/posts.js @@ -38,7 +38,7 @@ function defaultRelations(frame) { return false; } - frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email']; + frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers']; } function setDefaultOrder(frame) { @@ -111,13 +111,6 @@ const transformLegacyEmailRecipientFilters = (frame) => { } }; -const transformPostVisibilityFilters = (frame) => { - if (frame.data.posts[0].visibility === 'filter' && frame.data.posts[0].visibility_filter) { - frame.data.posts[0].visibility = frame.data.posts[0].visibility_filter; - } - delete frame.data.posts[0].visibility_filter; -}; - module.exports = { browse(apiConfig, frame) { debug('browse'); @@ -212,7 +205,6 @@ module.exports = { }); } - transformPostVisibilityFilters(frame); transformLegacyEmailRecipientFilters(frame); handlePostsMeta(frame); defaultFormat(frame); diff --git a/core/server/api/canary/utils/serializers/output/email-posts.js b/core/server/api/canary/utils/serializers/output/email-posts.js index e16125601e..12aaf87b76 100644 --- a/core/server/api/canary/utils/serializers/output/email-posts.js +++ b/core/server/api/canary/utils/serializers/output/email-posts.js @@ -2,8 +2,8 @@ const mapper = require('./utils/mapper'); const gating = require('./utils/post-gating'); module.exports = { - read(model, apiConfig, frame) { - const emailPost = mapper.mapPost(model, frame); + async read(model, apiConfig, frame) { + const emailPost = await mapper.mapPost(model, frame); gating.forPost(emailPost, frame); frame.response = { diff --git a/core/server/api/canary/utils/serializers/output/pages.js b/core/server/api/canary/utils/serializers/output/pages.js index 8588eb7ec3..7e9d64fe63 100644 --- a/core/server/api/canary/utils/serializers/output/pages.js +++ b/core/server/api/canary/utils/serializers/output/pages.js @@ -2,25 +2,29 @@ const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:pa const mapper = require('./utils/mapper'); module.exports = { - all(models, apiConfig, frame) { + async all(models, apiConfig, frame) { debug('all'); // CASE: e.g. destroy returns null if (!models) { return; } - + let pages = []; if (models.meta) { + for (let model of models.data) { + let page = await mapper.mapPage(model, frame); + pages.push(page); + } frame.response = { - pages: models.data.map(model => mapper.mapPage(model, frame)), + pages, meta: models.meta }; return; } - + let page = await mapper.mapPage(models, frame); frame.response = { - pages: [mapper.mapPage(models, frame)] + pages: [page] }; } }; diff --git a/core/server/api/canary/utils/serializers/output/posts.js b/core/server/api/canary/utils/serializers/output/posts.js index 7fac964f45..753a1dc03c 100644 --- a/core/server/api/canary/utils/serializers/output/posts.js +++ b/core/server/api/canary/utils/serializers/output/posts.js @@ -2,25 +2,29 @@ const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:po const mapper = require('./utils/mapper'); module.exports = { - all(models, apiConfig, frame) { + async all(models, apiConfig, frame) { debug('all'); // CASE: e.g. destroy returns null if (!models) { return; } - + let posts = []; if (models.meta) { + for (let model of models.data) { + let post = await mapper.mapPost(model, frame); + posts.push(post); + } frame.response = { - posts: models.data.map(model => mapper.mapPost(model, frame)), + posts, meta: models.meta }; return; } - + let post = await mapper.mapPost(models, frame); frame.response = { - posts: [mapper.mapPost(models, frame)] + posts: [post] }; } }; diff --git a/core/server/api/canary/utils/serializers/output/preview.js b/core/server/api/canary/utils/serializers/output/preview.js index 769335b984..c1eee7e1c1 100644 --- a/core/server/api/canary/utils/serializers/output/preview.js +++ b/core/server/api/canary/utils/serializers/output/preview.js @@ -1,9 +1,10 @@ const mapper = require('./utils/mapper'); module.exports = { - all(model, apiConfig, frame) { + async all(model, apiConfig, frame) { + const data = await mapper.mapPost(model, frame); frame.response = { - preview: [mapper.mapPost(model, frame)] + preview: [data] }; frame.response.preview[0].page = model.get('type') === 'page'; } diff --git a/core/server/api/canary/utils/serializers/output/utils/clean.js b/core/server/api/canary/utils/serializers/output/utils/clean.js index 83e5b53f8a..b8c8ac6f78 100644 --- a/core/server/api/canary/utils/serializers/output/utils/clean.js +++ b/core/server/api/canary/utils/serializers/output/utils/clean.js @@ -1,6 +1,5 @@ const _ = require('lodash'); const localUtils = require('../../../index'); -const labsService = require('../../../../../../../shared/labs'); const tag = (attrs, frame) => { if (localUtils.isContentAPI(frame)) { @@ -122,14 +121,6 @@ const post = (attrs, frame) => { delete attrs.primary_author; } - // Handles visibility filter for multiple products - if (attrs.visibility && labsService.isSet('multipleProducts')) { - if (!['members', 'public', 'paid'].includes(attrs.visibility)) { - attrs.visibility_filter = attrs.visibility; - attrs.visibility = 'filter'; - } - } - delete attrs.locale; delete attrs.author; delete attrs.type; diff --git a/core/server/api/canary/utils/serializers/output/utils/mapper.js b/core/server/api/canary/utils/serializers/output/utils/mapper.js index aaa8ef52df..eecc9ad876 100644 --- a/core/server/api/canary/utils/serializers/output/utils/mapper.js +++ b/core/server/api/canary/utils/serializers/output/utils/mapper.js @@ -7,6 +7,10 @@ const clean = require('./clean'); const extraAttrs = require('./extra-attrs'); const postsMetaSchema = require('../../../../../../data/schema').tables.posts_meta; const mega = require('../../../../../../services/mega'); +const labsService = require('../../../../../../../shared/labs'); + +const getPostServiceInstance = require('../../../../../../services/posts/posts-service'); +const postsService = getPostServiceInstance('canary'); const mapUser = (model, frame) => { const jsonModel = model.toJSON ? model.toJSON(frame.options) : model; @@ -27,7 +31,7 @@ const mapTag = (model, frame) => { return jsonModel; }; -const mapPost = (model, frame) => { +const mapPost = async (model, frame) => { const extendedOptions = Object.assign(_.cloneDeep(frame.options), { extraProperties: ['canonical_url'] }); @@ -38,6 +42,17 @@ const mapPost = (model, frame) => { extraAttrs.forPost(frame, model, jsonModel); + // Attach tiers to custom nql visibility filter + if (labsService.isSet('multipleProducts') + && jsonModel.visibility + && !['members', 'public', 'paid', 'tiers'].includes(jsonModel.visibility) + ) { + const tiers = await postsService.getProductsFromVisibilityFilter(jsonModel.visibility); + + jsonModel.visibility = 'tiers'; + jsonModel.tiers = tiers; + } + if (utils.isContentAPI(frame)) { // Content api v2 still expects page prop if (jsonModel.type === 'page') { @@ -88,8 +103,8 @@ const mapPost = (model, frame) => { return jsonModel; }; -const mapPage = (model, frame) => { - const jsonModel = mapPost(model, frame); +const mapPage = async (model, frame) => { + const jsonModel = await mapPost(model, frame); delete jsonModel.email_subject; delete jsonModel.email_recipient_filter; diff --git a/core/server/services/posts/posts-service.js b/core/server/services/posts/posts-service.js index 180ad71631..202c85b679 100644 --- a/core/server/services/posts/posts-service.js +++ b/core/server/services/posts/posts-service.js @@ -1,8 +1,10 @@ +const nql = require('@nexes/nql'); const {BadRequestError} = require('@tryghost/errors'); const tpl = require('@tryghost/tpl'); const messages = { - invalidEmailRecipientFilter: 'Invalid filter in email_recipient_filter param.' + invalidEmailRecipientFilter: 'Invalid filter in email_recipient_filter param.', + invalidVisibilityFilter: 'Invalid visibility filter.' }; class PostsService { @@ -78,6 +80,28 @@ class PostsService { return model; } + async getProductsFromVisibilityFilter(visibilityFilter) { + try { + const allProducts = await this.models.Product.findAll(); + const visibilityFilterJson = nql(visibilityFilter).toJSON(); + const productsData = (visibilityFilterJson.product ? [visibilityFilterJson] : visibilityFilterJson.$or) || []; + const tiers = productsData + .map((data) => { + return allProducts.find((p) => { + return p.get('slug') === data.product; + }); + }).filter(p => !!p).map((d) => { + return d.toJSON(); + }); + return tiers; + } catch (err) { + return Promise.reject(new BadRequestError({ + message: tpl(messages.invalidVisibilityFilter), + context: err.message + })); + } + } + /** * Calculates if the email should be tried to be sent out * @private