Added member endpoints for managing newsletter subscriptions (#14624)

refs TryGhost/Team#1495

With multiple newsletters, members are allowed to manage their newsletter pref via email unsubscribe link with member uuid. Since Portal needs to manage member's newsletter pref via their UUID, we need new endpoints on members that allow fetch/update of newsletter subscriptions via only uuid. The endpoints return only limited data for a member that are needed for the UI.

- adds endpoint to fetch newsletter subscriptions for member via uuid
- adds endpoint to update newsletter subscriptions for member via uuid
This commit is contained in:
Rishabh Garg 2022-04-28 17:14:17 +05:30 committed by GitHub
parent a332e42476
commit c7b247a079
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 116 additions and 1 deletions

View File

@ -69,6 +69,65 @@ const getOfferData = async function (req, res) {
}); });
}; };
const getMemberNewsletters = async function (req, res) {
try {
const memberUuid = req.query.uuid;
if (!memberUuid) {
res.writeHead(400);
return res.end('Invalid member uuid');
}
const memberData = await membersService.api.members.get({
uuid: memberUuid
}, {
withRelated: ['newsletters']
});
if (!memberData) {
res.writeHead(404);
return res.end('Email address not found.');
}
const data = _.pick(memberData.toJSON(), 'uuid', 'email', 'name', 'newsletters', 'status');
return res.json(data);
} catch (err) {
res.writeHead(400);
res.end('Failed to unsubscribe this email address');
}
};
const updateMemberNewsletters = async function (req, res) {
try {
const memberUuid = req.query.uuid;
if (!memberUuid) {
res.writeHead(400);
return res.end('Invalid member uuid');
}
const data = _.pick(req.body, 'newsletters');
const memberData = await membersService.api.members.get({
uuid: memberUuid
});
if (!memberData) {
res.writeHead(404);
return res.end('Email address not found.');
}
const options = {
id: memberData.get('id'),
withRelated: ['newsletters']
};
const updatedMember = await membersService.api.members.update(data, options);
const updatedMemberData = _.pick(updatedMember.toJSON(), ['uuid', 'email', 'name', 'newsletters', 'status']);
res.json(updatedMemberData);
} catch (err) {
res.writeHead(400);
res.end('Failed to update newsletters');
}
};
const updateMemberData = async function (req, res) { const updateMemberData = async function (req, res) {
try { try {
const data = _.pick(req.body, 'name', 'subscribed', 'newsletters'); const data = _.pick(req.body, 'name', 'subscribed', 'newsletters');
@ -270,9 +329,11 @@ module.exports = {
loadMemberSession, loadMemberSession,
createSessionFromMagicLink, createSessionFromMagicLink,
getIdentityToken, getIdentityToken,
getMemberNewsletters,
getMemberData, getMemberData,
getOfferData, getOfferData,
updateMemberData, updateMemberData,
updateMemberNewsletters,
getMemberSiteData, getMemberSiteData,
deleteSession deleteSession
}; };

View File

@ -33,6 +33,11 @@ module.exports = function setupMembersApp() {
// Initializes members specific routes as well as assigns members specific data to the req/res objects // Initializes members specific routes as well as assigns members specific data to the req/res objects
// We don't want to add global bodyParser middleware as that interfers with stripe webhook requests on - `/webhooks`. // We don't want to add global bodyParser middleware as that interfers with stripe webhook requests on - `/webhooks`.
// Manage newsletter subscription via unsubscribe link
membersApp.get('/api/member/newsletters', middleware.getMemberNewsletters);
membersApp.put('/api/member/newsletters', bodyParser.json({limit: '1mb'}), middleware.updateMemberNewsletters);
membersApp.get('/api/member', middleware.getMemberData); membersApp.get('/api/member', middleware.getMemberData);
membersApp.put('/api/member', bodyParser.json({limit: '1mb'}), middleware.updateMemberData); membersApp.put('/api/member', bodyParser.json({limit: '1mb'}), middleware.updateMemberData);
membersApp.post('/api/member/email', bodyParser.json({limit: '1mb'}), (req, res) => membersService.api.middleware.updateEmailAddress(req, res)); membersApp.post('/api/member/email', bodyParser.json({limit: '1mb'}), (req, res) => membersService.api.middleware.updateEmailAddress(req, res));

View File

@ -10,6 +10,7 @@ const DomainEvents = require('@tryghost/domain-events');
const {MemberPageViewEvent} = require('@tryghost/member-events'); const {MemberPageViewEvent} = require('@tryghost/member-events');
const models = require('../../core/server/models'); const models = require('../../core/server/models');
const {mockManager} = require('../utils/e2e-framework'); const {mockManager} = require('../utils/e2e-framework');
const DataGenerator = require('../utils/fixtures/data-generator');
function assertContentIsPresent(res) { function assertContentIsPresent(res) {
res.text.should.containEql('<h2 id="markdown">markdown</h2>'); res.text.should.containEql('<h2 id="markdown">markdown</h2>');
@ -57,7 +58,8 @@ describe('Front-end members behaviour', function () {
return originalSettingsCacheGetFn(key, options); return originalSettingsCacheGetFn(key, options);
}); });
await testUtils.startGhost(); await testUtils.startGhost();
await testUtils.initFixtures('members'); await testUtils.initFixtures('newsletters', 'members:newsletters');
request = supertest.agent(configUtils.config.get('url')); request = supertest.agent(configUtils.config.get('url'));
}); });
@ -117,6 +119,53 @@ describe('Front-end members behaviour', function () {
.expect(400); .expect(400);
}); });
it('should error for fetching member newsletters with missing uuid', async function () {
await request.get('/members/api/member/newsletters')
.expect(400);
});
it('should error for fetching member newsletters with invalid uuid', async function () {
await request.get('/members/api/member/newsletters?uuid=abc')
.expect(404);
});
it('should error for updating member newsletters with missing uuid', async function () {
await request.put('/members/api/member/newsletters')
.expect(400);
});
it('should error for updating member newsletters with invalid uuid', async function () {
await request.put('/members/api/member/newsletters?uuid=abc')
.expect(404);
});
it('should fetch and update member newsletters with valid uuid', async function () {
const memberUUID = DataGenerator.Content.members[0].uuid;
// Can fetch newsletter subscriptions
const getRes = await request.get(`/members/api/member/newsletters?uuid=${memberUUID}`)
.expect(200);
const getJsonResponse = getRes.body;
should.exist(getJsonResponse);
getJsonResponse.should.have.properties(['email', 'uuid', 'status', 'name', 'newsletters']);
getJsonResponse.should.not.have.property('id');
getJsonResponse.newsletters.should.have.length(1);
// Can update newsletter subscription
const res = await request.put(`/members/api/member/newsletters?uuid=${memberUUID}`)
.send({
newsletters: []
})
.expect(200);
const jsonResponse = res.body;
should.exist(jsonResponse);
jsonResponse.should.have.properties(['email', 'uuid', 'status', 'name', 'newsletters']);
jsonResponse.should.not.have.property('id');
jsonResponse.newsletters.should.have.length(0);
});
it('should serve theme 404 on members endpoint', async function () { it('should serve theme 404 on members endpoint', async function () {
await request.get('/members/') await request.get('/members/')
.expect(404) .expect(404)