Handled visibility filter in post/page API

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

As part of work for segmented post access with multiple products, the custom filter for post access is stored in `visibility` field on posts but passed with `visibility_filter` property on API. This change -

- updates input serializer of posts to transform `visibility` and `visibility_filter` properties correctly
- updates output serializer for canary to transform and send `visibility_filter` attribute with filter value
- updates output serializer for v3 to ignore any custom filter on visibility and return `paid` instead as v3 didn't have a concept of custom filter
This commit is contained in:
Rishabh 2021-07-02 22:21:44 +05:30 committed by Rishabh Garg
parent d413b3d654
commit 3e9a23355f
9 changed files with 38 additions and 18 deletions

View File

@ -111,6 +111,13 @@ 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');
@ -205,6 +212,7 @@ module.exports = {
});
}
transformPostVisibilityFilters(frame);
transformLegacyEmailRecipientFilters(frame);
handlePostsMeta(frame);
defaultFormat(frame);

View File

@ -1,5 +1,6 @@
const _ = require('lodash');
const localUtils = require('../../../index');
const labsService = require('../../../../../../services/labs');
const tag = (attrs, frame) => {
if (localUtils.isContentAPI(frame)) {
@ -120,6 +121,14 @@ 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;

View File

@ -4,7 +4,7 @@ const {ValidationError} = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const messages = {
invalidVisibilityFilter: 'Invalid filter in visibility_filter property'
invalidVisibilityFilter: 'Invalid filter in visibility property'
};
const validateVisibility = async function (frame) {
@ -14,17 +14,16 @@ const validateVisibility = async function (frame) {
// validate visibility - not done at schema level because this can be an NQL query so needs model access
const visibility = frame.data.pages[0].visibility;
const visibilityFilter = frame.data.pages[0].visibility_filter;
if (visibility) {
if (!['public', 'members', 'paid'].includes(visibility)) {
// check filter is valid
try {
await models.Member.findPage({filter: visibilityFilter, limit: 1});
await models.Member.findPage({filter: visibility, limit: 1});
return Promise.resolve();
} catch (err) {
return Promise.reject(new ValidationError({
message: tpl(messages.invalidVisibilityFilter),
property: 'visibility_filter'
property: 'visibility'
}));
}
}

View File

@ -4,7 +4,7 @@ const {ValidationError} = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const messages = {
invalidVisibilityFilter: 'Invalid filter in visibility_filter property'
invalidVisibilityFilter: 'Invalid filter in visibility property'
};
const validateVisibility = async function (frame) {
@ -14,17 +14,16 @@ const validateVisibility = async function (frame) {
// validate visibility - not done at schema level because this can be an NQL query so needs model access
const visibility = frame.data.posts[0].visibility;
const visibilityFilter = frame.data.posts[0].visibility_filter;
if (visibility) {
if (!['public', 'members', 'paid'].includes(visibility)) {
// check filter is valid
try {
await models.Member.findPage({filter: visibilityFilter, limit: 1});
await models.Member.findPage({filter: visibility, limit: 1});
return Promise.resolve();
} catch (err) {
return Promise.reject(new ValidationError({
message: tpl(messages.invalidVisibilityFilter),
property: 'visibility_filter'
property: 'visibility'
}));
}
}

View File

@ -96,6 +96,11 @@ const post = (attrs, frame) => {
if (attrs.og_description === '') {
attrs.og_description = null;
}
// Note: If visibility is set to a specific filter in v4, we want to return paid in v3
if (attrs.visibility && !['public', 'members', 'paid'].includes(attrs.visibility)) {
attrs.visibility = 'paid';
}
// NOTE: the visibility column has to be always present in Content API response to perform content gating
if (columns && columns.includes('visibility') && fields && !fields.includes('visibility')) {
delete attrs.visibility;

View File

@ -4,7 +4,7 @@ const {ValidationError} = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const messages = {
invalidVisibilityFilter: 'Invalid filter in visibility_filter property'
invalidVisibilityFilter: 'Invalid filter in visibility property'
};
const validateVisibility = async function (frame) {
@ -14,17 +14,16 @@ const validateVisibility = async function (frame) {
// validate visibility - not done at schema level because this can be an NQL query so needs model access
const visibility = frame.data.pages[0].visibility;
const visibilityFilter = frame.data.pages[0].visibility_filter;
if (visibility) {
if (!['public', 'members', 'paid'].includes(visibility)) {
// check filter is valid
try {
await models.Member.findPage({filter: visibilityFilter, limit: 1});
await models.Member.findPage({filter: visibility, limit: 1});
return Promise.resolve();
} catch (err) {
return Promise.reject(new ValidationError({
message: tpl(messages.invalidVisibilityFilter),
property: 'visibility_filter'
property: 'visibility'
}));
}
}

View File

@ -4,7 +4,7 @@ const {ValidationError} = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const messages = {
invalidVisibilityFilter: 'Invalid filter in visibility_filter property'
invalidVisibilityFilter: 'Invalid filter in visibility property'
};
const validateVisibility = async function (frame) {
@ -14,17 +14,16 @@ const validateVisibility = async function (frame) {
// validate visibility - not done at schema level because this can be an NQL query so needs model access
const visibility = frame.data.posts[0].visibility;
const visibilityFilter = frame.data.posts[0].visibility_filter;
if (visibility) {
if (!['public', 'members', 'paid'].includes(visibility)) {
// check filter is valid
try {
await models.Member.findPage({filter: visibilityFilter, limit: 1});
await models.Member.findPage({filter: visibility, limit: 1});
return Promise.resolve();
} catch (err) {
return Promise.reject(new ValidationError({
message: tpl(messages.invalidVisibilityFilter),
property: 'visibility_filter'
property: 'visibility'
}));
}
}

View File

@ -198,7 +198,8 @@ describe('Unit: canary/utils/validators/input/pages', function () {
pages: [{
title: 'pass',
authors: [{id: 'correct'}],
visibility: 'label:vip'
visibility: 'filter',
visibility_filter: 'label:vip'
}]
}
};

View File

@ -198,7 +198,8 @@ describe('Unit: canary/utils/validators/input/posts', function () {
posts: [{
title: 'pass',
authors: [{id: 'correct'}],
visibility: 'label:vip'
visibility: 'filter',
visibility_filter: 'label:vip'
}]
}
};