0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added newsletters endpoint to Content API (#14778)

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

- allows active newsletters data to be fetched via content API
This commit is contained in:
Rishabh Garg 2022-05-11 21:36:43 +05:30 committed by GitHub
parent 837e11b4d8
commit 9f85f7a4fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 265 additions and 0 deletions

View file

@ -215,5 +215,9 @@ module.exports = {
get tiersPublic() {
return shared.pipeline(require('./tiers-public'), localUtils, 'content');
},
get newslettersPublic() {
return shared.pipeline(require('./newsletters-public'), localUtils, 'content');
}
};

View file

@ -0,0 +1,19 @@
const models = require('../../models');
module.exports = {
docName: 'newsletters',
browse: {
options: [
'filter',
'fields',
'limit',
'order',
'page'
],
permissions: true,
query(frame) {
return models.Newsletter.findPage(frame.options);
}
}
};

View file

@ -9,5 +9,6 @@ module.exports = {
settings: require('./settings'),
snippets: require('./snippets'),
tags: require('./tags'),
newsletters: require('./newsletters'),
users: require('./users')
};

View file

@ -0,0 +1,23 @@
const utils = require('../../../index');
module.exports = (model, frame) => {
const jsonModel = model.toJSON(frame.options);
if (utils.isContentAPI(frame)) {
const serialized = {
id: jsonModel.id,
uuid: jsonModel.uuid,
name: jsonModel.name,
description: jsonModel.description,
slug: jsonModel.slug,
visibility: jsonModel.visibility,
sort_order: jsonModel.sort_order,
created_at: jsonModel.created_at,
updated_at: jsonModel.updated_at
};
return serialized;
}
return jsonModel;
};

View file

@ -38,6 +38,11 @@ const Newsletter = ghostBookshelf.Model.extend({
return this.hasMany('Post');
},
// Force active newsletters for content API
enforcedFilters: function enforcedFilters(options) {
return options.context && options.context.public ? 'status:active' : null;
},
async onSaving(model, _attr, options) {
ghostBookshelf.Model.prototype.onSaving.apply(this, arguments);

View file

@ -34,6 +34,7 @@ module.exports = function apiRoutes() {
router.get('/products', mw.authenticatePublic, http(api.productsPublic.browse));
router.get('/tiers', mw.authenticatePublic, http(api.tiersPublic.browse));
router.get('/newsletters', mw.authenticatePublic, http(api.newslettersPublic.browse));
return router;
};

View file

@ -0,0 +1,125 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Newsletters Content API Can request only active newsletters 1: [body] 1`] = `
Object {
"meta": Object {
"pagination": Object {
"limit": 15,
"next": null,
"page": 1,
"pages": 1,
"prev": null,
"total": 3,
},
},
"newsletters": Array [
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"slug": "default-newsletter",
"sort_order": 0,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Daily newsletter",
"slug": "daily-newsletter",
"sort_order": 1,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Weekly newsletter",
"slug": "weekly-newsletter",
"sort_order": 2,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters Content API Can request only active newsletters 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": "918",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Newsletters Content API Cannot override filters to fetch archived newsletters 1: [body] 1`] = `
Object {
"meta": Object {
"pagination": Object {
"limit": 15,
"next": null,
"page": 1,
"pages": 1,
"prev": null,
"total": 3,
},
},
"newsletters": Array [
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"slug": "default-newsletter",
"sort_order": 0,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Daily newsletter",
"slug": "daily-newsletter",
"sort_order": 1,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Weekly newsletter",
"slug": "weekly-newsletter",
"sort_order": 2,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
}
`;
exports[`Newsletters Content API Cannot override filters to fetch archived newsletters 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": "918",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;

View file

@ -0,0 +1,40 @@
const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
const newsletterSnapshot = {
id: matchers.anyObjectId,
uuid: matchers.anyUuid,
created_at: matchers.anyISODateTime,
updated_at: matchers.anyISODateTime
};
describe('Newsletters Content API', function () {
let agent;
before(async function () {
agent = await agentProvider.getContentAPIAgent();
await fixtureManager.init('api_keys', 'newsletters');
agent.authenticate();
});
it('Can request only active newsletters', async function () {
await agent.get('/newsletters/')
.expectStatus(200)
.matchHeaderSnapshot({
etag: matchers.anyEtag
})
.matchBodySnapshot({
newsletters: Array(3).fill(newsletterSnapshot)
});
});
it('Cannot override filters to fetch archived newsletters', async function () {
await agent.get('/newsletters/?filter=status:archived')
.expectStatus(200)
.matchHeaderSnapshot({
etag: matchers.anyEtag
})
.matchBodySnapshot({
newsletters: Array(3).fill(newsletterSnapshot)
});
});
});

View file

@ -211,4 +211,51 @@ describe('Unit: utils/serializers/output/mappers', function () {
});
});
});
describe('Newsletter Mapper', function () {
let newsletterModel;
beforeEach(function () {
newsletterModel = (data) => {
return Object.assign(data, {toJSON: sinon.stub().returns(data)});
};
});
it('returns only allowed keys for content API', function () {
const frame = {
apiType: 'content'
};
const newsletter = newsletterModel(testUtils.DataGenerator.forKnex.createNewsletter({
name: 'Basic newsletter',
slug: 'basic-newsletter'
}));
const mapped = mappers.newsletters(newsletter, frame);
mapped.should.eql({
id: newsletter.id,
uuid: newsletter.uuid,
name: newsletter.name,
description: newsletter.description,
slug: newsletter.slug,
visibility: newsletter.visibility,
sort_order: newsletter.sort_order,
created_at: newsletter.created_at,
updated_at: newsletter.updated_at
});
});
it('returns all keys for admin API', function () {
const frame = {};
const newsletter = newsletterModel(testUtils.DataGenerator.forKnex.createNewsletter({
name: 'Full newsletter',
slug: 'full-newsletter'
}));
const mapped = mappers.newsletters(newsletter, frame);
mapped.should.eql(newsletter.toJSON());
});
});
});