Skip to content

Commit 0c743d6

Browse files
authored
🐛 Fixed member newsletter subscription not saving in Admin (#16490)
fixes https://github.com/TryGhost/Team/issues/2783 refs cb05fae The root cause of the issue was the fact we no longer checked for lack of `newsletters` property on member data before checking its `subscribed` property which is now deprecated. This caused a cascading effect where `subscribed:false` property on a member overrides the value for `newsletters` data. The check was accidentally removed in a previous bug fix. So for members that were not subscribed to any newsletters, saving a newsletter subscription failed as they had their `subscribed` set to `false`, and it was resetting the newsletter subscription to empty always.
1 parent e096fbc commit 0c743d6

File tree

3 files changed

+216
-6
lines changed

3 files changed

+216
-6
lines changed

ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap

+143
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,149 @@ Object {
356356
}
357357
`;
358358

359+
exports[`Members API Adding newsletters to member with no subscriptions works even with subscribed false 1: [body] 1`] = `
360+
Object {
361+
"members": Array [
362+
Object {
363+
"attribution": Object {
364+
"id": null,
365+
"referrer_medium": "Ghost Admin",
366+
"referrer_source": "Created manually",
367+
"referrer_url": null,
368+
"title": null,
369+
"type": "url",
370+
"url": null,
371+
},
372+
"avatar_image": null,
373+
"comped": false,
374+
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
375+
"email": "[email protected]",
376+
"email_count": 0,
377+
"email_open_rate": null,
378+
"email_opened_count": 0,
379+
"email_suppression": Object {
380+
"info": null,
381+
"suppressed": false,
382+
},
383+
"geolocation": null,
384+
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
385+
"labels": Any<Array>,
386+
"last_seen_at": null,
387+
"name": "test newsletter",
388+
"newsletters": Array [],
389+
"note": null,
390+
"status": "free",
391+
"subscribed": false,
392+
"subscriptions": Any<Array>,
393+
"tiers": Array [],
394+
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
395+
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
396+
},
397+
],
398+
}
399+
`;
400+
401+
exports[`Members API Adding newsletters to member with no subscriptions works even with subscribed false 2: [headers] 1`] = `
402+
Object {
403+
"access-control-allow-origin": "http://127.0.0.1:2369",
404+
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
405+
"content-length": "694",
406+
"content-type": "application/json; charset=utf-8",
407+
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
408+
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
409+
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
410+
"vary": "Accept-Version, Origin, Accept-Encoding",
411+
"x-powered-by": "Express",
412+
}
413+
`;
414+
415+
exports[`Members API Adding newsletters to member with no subscriptions works even with subscribed false 3: [body] 1`] = `
416+
Object {
417+
"members": Array [
418+
Object {
419+
"attribution": Object {
420+
"id": null,
421+
"referrer_medium": "Ghost Admin",
422+
"referrer_source": "Created manually",
423+
"referrer_url": null,
424+
"title": null,
425+
"type": "url",
426+
"url": null,
427+
},
428+
"avatar_image": null,
429+
"comped": false,
430+
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
431+
"email": "[email protected]",
432+
"email_count": 0,
433+
"email_open_rate": null,
434+
"email_opened_count": 0,
435+
"email_suppression": Object {
436+
"info": null,
437+
"suppressed": false,
438+
},
439+
"geolocation": null,
440+
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
441+
"labels": Any<Array>,
442+
"last_seen_at": null,
443+
"name": "test newsletter",
444+
"newsletters": Array [
445+
Object {
446+
"body_font_category": "serif",
447+
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
448+
"description": null,
449+
"feedback_enabled": false,
450+
"footer_content": null,
451+
"header_image": "http://127.0.0.1:2369/content/images/2022/05/test.jpg",
452+
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
453+
"name": "Daily newsletter",
454+
"sender_email": "[email protected]",
455+
"sender_name": "Jamie",
456+
"sender_reply_to": "newsletter",
457+
"show_badge": true,
458+
"show_comment_cta": true,
459+
"show_feature_image": true,
460+
"show_header_icon": true,
461+
"show_header_name": true,
462+
"show_header_title": true,
463+
"show_latest_posts": false,
464+
"show_post_title_section": true,
465+
"show_subscription_details": false,
466+
"slug": "daily-newsletter",
467+
"sort_order": 1,
468+
"status": "active",
469+
"subscribe_on_signup": false,
470+
"title_alignment": "center",
471+
"title_font_category": "serif",
472+
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
473+
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
474+
"visibility": "members",
475+
},
476+
],
477+
"note": null,
478+
"status": "free",
479+
"subscribed": true,
480+
"subscriptions": Any<Array>,
481+
"tiers": Array [],
482+
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
483+
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
484+
},
485+
],
486+
}
487+
`;
488+
489+
exports[`Members API Adding newsletters to member with no subscriptions works even with subscribed false 4: [headers] 1`] = `
490+
Object {
491+
"access-control-allow-origin": "http://127.0.0.1:2369",
492+
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
493+
"content-length": "1531",
494+
"content-type": "application/json; charset=utf-8",
495+
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
496+
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
497+
"vary": "Accept-Version, Origin, Accept-Encoding",
498+
"x-powered-by": "Express",
499+
}
500+
`;
501+
359502
exports[`Members API Bulk operations Can bulk delete a label from members 1: [body] 1`] = `
360503
Object {
361504
"bulk": Object {

ghost/core/test/e2e-api/admin/members.test.js

+65
Original file line numberDiff line numberDiff line change
@@ -2442,6 +2442,71 @@ describe('Members API', function () {
24422442
assert.ok(changedMember.newsletters.find(n => n.id === testUtils.DataGenerator.Content.newsletters[1].id), 'The member is still subscribed for the newsletter it subscribed to');
24432443
});
24442444

2445+
it('Adding newsletters to member with no subscriptions works even with subscribed false', async function () {
2446+
// Add member with no subscriptions
2447+
const member = {
2448+
name: 'test newsletter',
2449+
2450+
newsletters: []
2451+
};
2452+
2453+
const {body} = await agent
2454+
.post(`/members/`)
2455+
.body({members: [member]})
2456+
.expectStatus(201)
2457+
.matchBodySnapshot({
2458+
members: [{
2459+
id: anyObjectId,
2460+
uuid: anyUuid,
2461+
created_at: anyISODateTime,
2462+
updated_at: anyISODateTime,
2463+
subscriptions: anyArray,
2464+
labels: anyArray,
2465+
newsletters: Array(0).fill(newsletterSnapshot)
2466+
}]
2467+
})
2468+
.matchHeaderSnapshot({
2469+
'content-version': anyContentVersion,
2470+
etag: anyEtag,
2471+
location: anyLocationFor('members')
2472+
});
2473+
2474+
const memberId = body.members[0].id;
2475+
2476+
const editedMember = {
2477+
subscribed: false,
2478+
newsletters: [
2479+
{
2480+
id: testUtils.DataGenerator.Content.newsletters[0].id
2481+
}
2482+
]
2483+
};
2484+
2485+
// Edit member
2486+
const {body: body2} = await agent
2487+
.put(`/members/${memberId}`)
2488+
.body({members: [editedMember]})
2489+
.expectStatus(200)
2490+
.matchBodySnapshot({
2491+
members: [{
2492+
id: anyObjectId,
2493+
uuid: anyUuid,
2494+
created_at: anyISODateTime,
2495+
updated_at: anyISODateTime,
2496+
subscriptions: anyArray,
2497+
labels: anyArray,
2498+
newsletters: Array(1).fill(newsletterSnapshot)
2499+
}]
2500+
})
2501+
.matchHeaderSnapshot({
2502+
'content-version': anyContentVersion,
2503+
etag: anyEtag
2504+
});
2505+
const changedMember = body2.members[0];
2506+
assert.equal(changedMember.newsletters.length, 1);
2507+
assert.ok(changedMember.newsletters.find(n => n.id === testUtils.DataGenerator.Content.newsletters[0].id), 'The member should be subscribed to the newsletter');
2508+
});
2509+
24452510
it('Updating member data without newsletters does not change newsletters', async function () {
24462511
// check that this newsletter is archived, or this test would not make sense
24472512
const archivedNewsletterId = testUtils.DataGenerator.Content.newsletters[2].id;

ghost/members-api/lib/repositories/member.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,14 @@ module.exports = class MemberRepository {
539539
if (needsNewsletters) {
540540
const existingNewsletters = initialMember.related('newsletters').models;
541541

542-
// This maps the old subscribed property to the new newsletters field
543-
if (memberData.subscribed === false) {
544-
memberData.newsletters = [];
545-
} else if (memberData.subscribed === true && !existingNewsletters.find(n => n.get('status') === 'active')) {
546-
const browseOptions = _.pick(options, 'transacting');
547-
memberData.newsletters = await this.getSubscribeOnSignupNewsletters(browseOptions);
542+
// This maps the old subscribed property to the new newsletters field and is only used to keep backward compatibility
543+
if (!memberData.newsletters) {
544+
if (memberData.subscribed === false) {
545+
memberData.newsletters = [];
546+
} else if (memberData.subscribed === true && !existingNewsletters.find(n => n.get('status') === 'active')) {
547+
const browseOptions = _.pick(options, 'transacting');
548+
memberData.newsletters = await this.getSubscribeOnSignupNewsletters(browseOptions);
549+
}
548550
}
549551

550552
// only ever populated with active newsletters - never archived ones

0 commit comments

Comments
 (0)