diff --git a/core/server/api/canary/members.js b/core/server/api/canary/members.js index f50d1e1fd8..5e5b6d641a 100644 --- a/core/server/api/canary/members.js +++ b/core/server/api/canary/members.js @@ -366,6 +366,9 @@ module.exports = { if (labsService.isSet('multipleProducts')) { frame.options.withRelated.push('products'); } + if (labsService.isSet('multipleNewsletters')) { + frame.options.withRelated.push('newsletters'); + } const page = await membersService.api.members.list(frame.options); return page; diff --git a/core/server/models/newsletter.js b/core/server/models/newsletter.js index 02acc80996..2c8094bbf5 100644 --- a/core/server/models/newsletter.js +++ b/core/server/models/newsletter.js @@ -72,6 +72,10 @@ const Newsletter = ghostBookshelf.Model.extend({ return query; } }, { + orderDefaultRaw: function () { + return 'sort_order ASC, created_at ASC, id ASC'; + }, + orderDefaultOptions: function orderDefaultOptions() { return { sort_order: 'ASC', diff --git a/package.json b/package.json index c7dac2b338..39b429ce0e 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@tryghost/logging": "2.1.5", "@tryghost/magic-link": "1.0.21", "@tryghost/member-events": "0.4.1", - "@tryghost/members-api": "5.12.0", + "@tryghost/members-api": "6.0.0-alpha.0", "@tryghost/members-events-service": "0.3.3", "@tryghost/members-importer": "0.5.8", "@tryghost/members-offers": "0.11.1", diff --git a/test/e2e-api/admin/__snapshots__/members.test.js.snap b/test/e2e-api/admin/__snapshots__/members.test.js.snap index 413f2ccb56..11d6997b47 100644 --- a/test/e2e-api/admin/__snapshots__/members.test.js.snap +++ b/test/e2e-api/admin/__snapshots__/members.test.js.snap @@ -45,6 +45,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "test", + "newsletters": Any, "note": "test note", "products": Array [], "status": "free", @@ -61,7 +62,7 @@ exports[`Members API Can add 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "608", + "content-length": "625", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -86,6 +87,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "products": Array [], "status": "paid", @@ -139,7 +141,7 @@ exports[`Members API Can add a subscription 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1391", + "content-length": "2009", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -163,6 +165,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "products": Array [], "status": "paid", @@ -216,7 +219,142 @@ exports[`Members API Can add a subscription 4: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1391", + "content-length": "2009", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can add and edit with custom newsletters 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "memberTestAddNewsletter2@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "test newsletter", + "newsletters": Array [ + Object { + "body_font_category": "serif", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": null, + "footer_content": null, + "header_image": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Weekly newsletter", + "sender_email": "jamie@example.com", + "sender_name": "Jamie", + "sender_reply_to": "newsletter", + "show_badge": true, + "show_feature_image": true, + "show_header_icon": true, + "show_header_name": true, + "show_header_title": true, + "slug": "weekly-newsletter", + "sort_order": 2, + "status": "active", + "subscribe_on_signup": true, + "title_alignment": "center", + "title_font_category": "serif", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "members", + }, + ], + "note": "test note", + "products": Array [], + "status": "free", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + ], +} +`; + +exports[`Members API Can add and edit with custom newsletters 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1248", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can add and edit with custom newsletters 3: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "memberTestAddNewsletter2@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "test newsletter", + "newsletters": Array [ + Object { + "body_font_category": "serif", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": null, + "footer_content": null, + "header_image": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Daily newsletter", + "sender_email": "jamie@example.com", + "sender_name": "Jamie", + "sender_reply_to": "newsletter", + "show_badge": true, + "show_feature_image": true, + "show_header_icon": true, + "show_header_name": true, + "show_header_title": true, + "slug": "daily-newsletter", + "sort_order": 1, + "status": "active", + "subscribe_on_signup": false, + "title_alignment": "center", + "title_font_category": "serif", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "members", + }, + ], + "note": "test note", + "products": Array [], + "status": "free", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + ], +} +`; + +exports[`Members API Can add and edit with custom newsletters 4: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1247", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -240,6 +378,58 @@ Object { "labels": Array [], "last_seen_at": null, "name": "Send Me Confirmation", + "newsletters": Array [ + Object { + "body_font_category": "sans_serif", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": null, + "footer_content": null, + "header_image": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Default Newsletter", + "sender_email": null, + "sender_name": null, + "sender_reply_to": "newsletter", + "show_badge": true, + "show_feature_image": true, + "show_header_icon": true, + "show_header_name": true, + "show_header_title": true, + "slug": "default-newsletter", + "sort_order": 0, + "status": "active", + "subscribe_on_signup": true, + "title_alignment": "center", + "title_font_category": "sans_serif", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "members", + }, + Object { + "body_font_category": "serif", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": null, + "footer_content": null, + "header_image": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Daily newsletter", + "sender_email": "jamie@example.com", + "sender_name": "Jamie", + "sender_reply_to": "newsletter", + "show_badge": true, + "show_feature_image": true, + "show_header_icon": true, + "show_header_name": true, + "show_header_title": true, + "slug": "daily-newsletter", + "sort_order": 1, + "status": "active", + "subscribe_on_signup": false, + "title_alignment": "center", + "title_font_category": "serif", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "members", + }, + ], "note": null, "products": Array [], "status": "free", @@ -256,7 +446,7 @@ exports[`Members API Can add and send a signup confirmation email 2: [headers] 1 Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "477", + "content-length": "1692", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": Any, @@ -291,6 +481,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Name", + "newsletters": Any, "note": null, "products": Array [], "status": "free", @@ -307,7 +498,7 @@ exports[`Members API Can add complimentary subscription (out of date) 2: [header Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "444", + "content-length": "1057", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -332,6 +523,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Name", + "newsletters": Any, "note": null, "products": Array [], "status": "free", @@ -348,7 +540,7 @@ exports[`Members API Can add complimentary subscription (out of date) 4: [header Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "444", + "content-length": "1057", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -372,6 +564,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -392,6 +585,7 @@ Object { "labels": Any, "last_seen_at": null, "name": null, + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -412,6 +606,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Egon Spengler", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -432,6 +627,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Ray Stantz", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -452,6 +648,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Vinz Clortho", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -472,6 +669,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Winston Zeddemore", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -492,6 +690,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Peter Venkman", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -512,6 +711,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Dana Barrett", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -537,7 +737,7 @@ exports[`Members API Can browse 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "8428", + "content-length": "12775", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -561,6 +761,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -586,7 +787,7 @@ exports[`Members API Can browse with filter 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "664", + "content-length": "1282", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -610,6 +811,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -635,7 +837,7 @@ exports[`Members API Can browse with search 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "664", + "content-length": "1282", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -659,6 +861,33 @@ Object { "labels": Any, "last_seen_at": null, "name": "Test Member", + "newsletters": Array [ + Object { + "body_font_category": "sans_serif", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": null, + "footer_content": null, + "header_image": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Default Newsletter", + "sender_email": null, + "sender_name": null, + "sender_reply_to": "newsletter", + "show_badge": true, + "show_feature_image": true, + "show_header_icon": true, + "show_header_name": true, + "show_header_title": true, + "slug": "default-newsletter", + "sort_order": 0, + "status": "active", + "subscribe_on_signup": true, + "title_alignment": "center", + "title_font_category": "sans_serif", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "members", + }, + ], "note": null, "products": Any, "status": "comped", @@ -675,7 +904,7 @@ exports[`Members API Can create a member with an existing complimentary subscrip Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1774", + "content-length": "2387", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -700,6 +929,33 @@ Object { "labels": Any, "last_seen_at": null, "name": "Test Member", + "newsletters": Array [ + Object { + "body_font_category": "sans_serif", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": null, + "footer_content": null, + "header_image": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Default Newsletter", + "sender_email": null, + "sender_name": null, + "sender_reply_to": "newsletter", + "show_badge": true, + "show_feature_image": true, + "show_header_icon": true, + "show_header_name": true, + "show_header_title": true, + "slug": "default-newsletter", + "sort_order": 0, + "status": "active", + "subscribe_on_signup": true, + "title_alignment": "center", + "title_font_category": "sans_serif", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "members", + }, + ], "note": null, "products": Any, "status": "paid", @@ -716,7 +972,7 @@ exports[`Members API Can create a member with an existing paid subscription 2: [ Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1849", + "content-length": "2462", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -741,6 +997,33 @@ Object { "labels": Any, "last_seen_at": null, "name": "Name", + "newsletters": Array [ + Object { + "body_font_category": "sans_serif", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": null, + "footer_content": null, + "header_image": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Default Newsletter", + "sender_email": null, + "sender_name": null, + "sender_reply_to": "newsletter", + "show_badge": true, + "show_feature_image": true, + "show_header_icon": true, + "show_header_name": true, + "show_header_title": true, + "slug": "default-newsletter", + "sort_order": 0, + "status": "active", + "subscribe_on_signup": true, + "title_alignment": "center", + "title_font_category": "sans_serif", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "members", + }, + ], "note": null, "products": Array [ Object { @@ -772,7 +1055,7 @@ exports[`Members API Can create a new member with a product (complementary) 2: [ Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1730", + "content-length": "2343", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -807,6 +1090,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "test", + "newsletters": Any, "note": null, "products": Array [], "status": "free", @@ -823,7 +1107,7 @@ exports[`Members API Can destroy 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "451", + "content-length": "1667", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -887,6 +1171,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "change me", + "newsletters": Any, "note": "initial note", "products": Array [], "status": "free", @@ -903,7 +1188,7 @@ exports[`Members API Can edit by id 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "462", + "content-length": "1075", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -928,6 +1213,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "changed", + "newsletters": Any, "note": "edited note", "products": Array [], "status": "free", @@ -944,7 +1230,7 @@ exports[`Members API Can edit by id 4: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "459", + "content-length": "476", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -984,12 +1270,12 @@ Object { Object { "comped": 3, "date": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, - "free": 3, + "free": 5, "paid": 2, }, ], "resource": "members", - "total": 8, + "total": 10, } `; @@ -997,7 +1283,7 @@ exports[`Members API Can fetch member counts stats 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "92", + "content-length": "93", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1021,6 +1307,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Egon Spengler", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1041,6 +1328,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Ray Stantz", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1061,6 +1349,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Vinz Clortho", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1081,6 +1370,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Peter Venkman", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1101,6 +1391,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Dana Barrett", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1126,7 +1417,120 @@ exports[`Members API Can filter by paid status 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "6893", + "content-length": "9385", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can filter on newsletter slug 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "trialing@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "Ray Stantz", + "newsletters": Any, + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "comped@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "Vinz Clortho", + "newsletters": Any, + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "vip@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "Winston Zeddemore", + "newsletters": Any, + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member2create@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "create me", + "newsletters": Any, + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 4, + }, + }, +} +`; + +exports[`Members API Can filter on newsletter slug 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "8009", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1150,6 +1554,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Peter Venkman", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1175,7 +1580,7 @@ exports[`Members API Can filter using contains operators 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "665", + "content-length": "682", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1199,6 +1604,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Egon Spengler", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1219,6 +1625,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Ray Stantz", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1239,6 +1646,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Vinz Clortho", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1259,6 +1667,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Peter Venkman", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1279,6 +1688,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Dana Barrett", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1304,7 +1714,7 @@ exports[`Members API Can ignore any unknown includes 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "6893", + "content-length": "9385", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1328,6 +1738,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Egon Spengler", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1348,6 +1759,7 @@ Object { "labels": Any, "last_seen_at": null, "name": null, + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1368,6 +1780,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1388,6 +1801,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Ray Stantz", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1408,6 +1822,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Vinz Clortho", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1428,6 +1843,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Winston Zeddemore", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1448,6 +1864,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Peter Venkman", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1468,6 +1885,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Dana Barrett", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1517,6 +1935,7 @@ Object { "labels": Any, "last_seen_at": null, "name": null, + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1537,6 +1956,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Egon Spengler", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1557,6 +1977,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1577,6 +1998,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Ray Stantz", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1597,6 +2019,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Vinz Clortho", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1617,6 +2040,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Winston Zeddemore", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1637,6 +2061,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Peter Venkman", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1657,6 +2082,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Dana Barrett", + "newsletters": Any, "note": null, "status": "paid", "subscribed": false, @@ -1706,6 +2132,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "products": Array [], "status": "free", @@ -1722,7 +2149,7 @@ exports[`Members API Can read 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "590", + "content-length": "1208", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1747,6 +2174,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "products": Array [], "status": "free", @@ -1763,7 +2191,90 @@ exports[`Members API Can read and include email_recipients 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "612", + "content-length": "1230", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can subscribe to a newsletter 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member3change@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "change me", + "newsletters": Any, + "note": null, + "products": Array [], + "status": "free", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + ], +} +`; + +exports[`Members API Can subscribe to a newsletter 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1065", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can subscribe to a newsletter 3: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member3change@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "change me", + "newsletters": Any, + "note": null, + "products": Array [], + "status": "free", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + ], +} +`; + +exports[`Members API Can subscribe to a newsletter 4: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1070", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1816,6 +2327,7 @@ Object { "labels": Any, "last_seen_at": null, "name": null, + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1841,7 +2353,7 @@ exports[`Members API Search by case-insensitive email MEMBER2 receives member wi Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "511", + "content-length": "1129", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1865,6 +2377,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Mr Egg", + "newsletters": Any, "note": null, "status": "free", "subscribed": true, @@ -1890,7 +2403,7 @@ exports[`Members API Search by case-insensitive name egg receives member with na Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "664", + "content-length": "1282", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1942,6 +2455,7 @@ Object { "labels": Any, "last_seen_at": null, "name": "Egon Spengler", + "newsletters": Any, "note": null, "status": "paid", "subscribed": true, @@ -1967,7 +2481,7 @@ exports[`Members API Search for paid members retrieves member with email paid@te Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1720", + "content-length": "2338", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1975,6 +2489,48 @@ Object { } `; +exports[`Members API Subscribes to default newsletters 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member2create@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "last_seen_at": null, + "name": "create me", + "newsletters": Any, + "note": null, + "products": Array [], + "status": "free", + "subscribed": true, + "subscriptions": Any, + "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\\}/, + }, + ], +} +`; + +exports[`Members API Subscribes to default newsletters 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1668", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + exports[`Members API without Stripe Add should fail when comped flag is passed in but Stripe is not enabled 1: [body] 1`] = ` Object { "errors": Array [ diff --git a/test/e2e-api/admin/members.test.js b/test/e2e-api/admin/members.test.js index 30207e843f..3f6ec7b36d 100644 --- a/test/e2e-api/admin/members.test.js +++ b/test/e2e-api/admin/members.test.js @@ -9,7 +9,6 @@ const testUtils = require('../../utils'); const Papa = require('papaparse'); const models = require('../../../core/server/models'); -const {Product} = require('../../../core/server/models/product'); async function assertMemberEvents({eventType, memberId, asserts}) { const events = await models[eventType].where('member_id', memberId).fetchAll(); @@ -26,7 +25,11 @@ async function assertSubscription(subscriptionId, asserts) { } async function getPaidProduct() { - return await Product.findOne({type: 'paid'}); + return await models.Product.findOne({type: 'paid'}); +} + +async function getNewsletters() { + return (await models.Newsletter.findAll({filter: 'status:active'})).models; } const newsletterSnapshot = { @@ -35,23 +38,17 @@ const newsletterSnapshot = { updated_at: anyISODateTime }; -const memberMatcherNoIncludes = { - id: anyObjectId, - uuid: anyUuid, - created_at: anyISODateTime, - updated_at: anyISODateTime -}; +function buildMemberWithoutIncludesSnapshot(options) { + return { + id: anyObjectId, + uuid: anyUuid, + created_at: anyISODateTime, + updated_at: anyISODateTime, + newsletters: new Array(options.newsletters).fill(newsletterSnapshot) + }; +} const memberMatcherShallowIncludes = { - id: anyObjectId, - uuid: anyUuid, - created_at: anyISODateTime, - updated_at: anyISODateTime, - subscriptions: anyArray, - labels: anyArray -}; - -const memberMatcherShallowIncludesForNewsletters = { id: anyObjectId, uuid: anyUuid, created_at: anyISODateTime, @@ -106,14 +103,19 @@ describe('Members API without Stripe', function () { }); describe('Members API', function () { + let newsletters; + before(async function () { agent = await agentProvider.getAdminAPIAgent(); - await fixtureManager.init('members'); + await fixtureManager.init('newsletters', 'members:newsletters'); await agent.loginAsOwner(); + + newsletters = await getNewsletters(); }); beforeEach(function () { mockManager.mockLabsEnabled('multipleProducts'); + mockManager.mockLabsEnabled('multipleNewsletters'); mockManager.mockStripe(); mockManager.mockMail(); }); @@ -309,7 +311,7 @@ describe('Members API', function () { name: 'test', email: 'memberTestAdd@test.com', note: 'test note', - subscribed: false, + newsletters: [], labels: ['test-label'] }; @@ -343,7 +345,7 @@ describe('Members API', function () { }); }); - it('Can add and send a signup confirmation email', async function () { + /*it('Can add and send a signup confirmation email (old)', async function () { const member = { name: 'Send Me Confirmation', email: 'member_getting_confirmation@test.com', @@ -360,7 +362,7 @@ describe('Members API', function () { .body({members: [member]}) .expectStatus(201) .matchBodySnapshot({ - members: [memberMatcherNoIncludes] + members: [news] }) .matchHeaderSnapshot({ etag: anyEtag, @@ -396,6 +398,84 @@ describe('Members API', function () { ] }); + // @TODO: do we really need to delete this member here? + await agent + .delete(`members/${body.members[0].id}/`) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .expectStatus(204); + + const events = await models.MemberSubscribeEvent.findAll(); + assert.equal(events.models.length, 0, 'There should be no MemberSubscribeEvent remaining.'); + });*/ + + it('Can add and send a signup confirmation email', async function () { + const member = { + name: 'Send Me Confirmation', + email: 'member_getting_confirmation@test.com', + newsletters: [ + newsletters[0], + newsletters[1] + ] + }; + + const queryParams = { + send_email: true, + email_type: 'signup' + }; + + const {body} = await agent + .post('/members/?send_email=true&email_type=signup') + .body({members: [member]}) + .expectStatus(201) + .matchBodySnapshot({ + members: [ + buildMemberWithoutIncludesSnapshot({ + newsletters: 2 + }) + ] + }) + .matchHeaderSnapshot({ + etag: anyEtag, + location: anyString + }); + + const newMember = body.members[0]; + + mockManager.assert.sentEmail({ + subject: '🙌 Complete your sign up to Ghost!', + to: 'member_getting_confirmation@test.com' + }); + + await assertMemberEvents({ + eventType: 'MemberStatusEvent', + memberId: newMember.id, + asserts: [ + { + from_status: null, + to_status: 'free' + } + ] + }); + + await assertMemberEvents({ + eventType: 'MemberSubscribeEvent', + memberId: newMember.id, + asserts: [ + { + subscribed: true, + source: 'admin', + newsletter_id: newsletters[0].id + }, + { + subscribed: true, + source: 'admin', + newsletter_id: newsletters[1].id + } + ] + }); + // @TODO: do we really need to delete this member here? await agent .delete(`members/${body.members[0].id}/`) @@ -474,7 +554,7 @@ describe('Members API', function () { const initialMember = { name: 'Name', email: 'compedtest@test.com', - subscribed: true + newsletters: [newsletters[0]] }; const compedPayload = { @@ -520,7 +600,8 @@ describe('Members API', function () { memberId: newMember.id, asserts: [{ subscribed: true, - source: 'admin' + source: 'admin', + newsletter_id: newsletters[0].id }] }); }); @@ -529,7 +610,7 @@ describe('Members API', function () { const initialMember = { name: 'Name', email: 'compedtest2@test.com', - subscribed: true + newsletters: [newsletters[0]] }; const {body} = await agent @@ -581,7 +662,8 @@ describe('Members API', function () { memberId: newMember.id, asserts: [{ subscribed: true, - source: 'admin' + source: 'admin', + newsletter_id: newsletters[0].id }] }); @@ -597,7 +679,7 @@ describe('Members API', function () { const initialMember = { name: 'Name', email: 'compedtest3@test.com', - subscribed: true, + newsletters: [newsletters[0]], products: [ { id: product.id @@ -651,7 +733,8 @@ describe('Members API', function () { asserts: [ { subscribed: true, - source: 'admin' + source: 'admin', + newsletter_id: newsletters[0].id } ] }); @@ -669,6 +752,7 @@ describe('Members API', function () { name: 'Name', email: 'compedtest4@test.com', subscribed: true, + newsletters: [newsletters[0]], products: [ { id: product.id @@ -694,7 +778,8 @@ describe('Members API', function () { yearly_price_id: anyObjectId, created_at: anyISODateTime, updated_at: anyISODateTime - }) + }), + newsletters: new Array(1).fill(newsletterSnapshot) }) }) .matchHeaderSnapshot({ @@ -792,6 +877,7 @@ describe('Members API', function () { name: fakeCustomer.name, email: fakeCustomer.email, subscribed: true, + newsletters: [newsletters[0]], stripe_customer_id: fakeCustomer.id }; @@ -807,7 +893,8 @@ describe('Members API', function () { updated_at: anyISODateTime, labels: anyArray, subscriptions: anyArray, - products: anyArray + products: anyArray, + newsletters: new Array(1).fill(newsletterSnapshot) }) }) .matchHeaderSnapshot({ @@ -918,6 +1005,7 @@ describe('Members API', function () { name: fakeCustomer.name, email: fakeCustomer.email, subscribed: true, + newsletters: [newsletters[0]], stripe_customer_id: fakeCustomer.id }; @@ -933,7 +1021,8 @@ describe('Members API', function () { updated_at: anyISODateTime, labels: anyArray, subscriptions: anyArray, - products: anyArray + products: anyArray, + newsletters: new Array(1).fill(newsletterSnapshot) }) }) .matchHeaderSnapshot({ @@ -1020,14 +1109,16 @@ describe('Members API', function () { name: 'change me', email: 'member2Change@test.com', note: 'initial note', - subscribed: true + newsletters: [ + newsletters[0] + ] }; const memberChanged = { name: 'changed', email: 'cantChangeMe@test.com', note: 'edited note', - subscribed: false + newsletters: [] }; const {body} = await agent @@ -1048,7 +1139,8 @@ describe('Members API', function () { memberId: newMember.id, asserts: [{ subscribed: true, - source: 'admin' + source: 'admin', + newsletter_id: newsletters[0].id }] }); await assertMemberEvents({ @@ -1085,15 +1177,131 @@ describe('Members API', function () { asserts: [ { subscribed: true, - source: 'admin' + source: 'admin', + newsletter_id: newsletters[0].id }, { subscribed: false, - source: 'admin' + source: 'admin', + newsletter_id: newsletters[0].id } ] }); }); + it('Can subscribe to a newsletter', async function () { + const memberToChange = { + name: 'change me', + email: 'member3change@test.com', + newsletters: [ + newsletters[0] + ] + }; + + const memberChanged = { + newsletters: [ + newsletters[1] + ] + }; + + const {body} = await agent + .post(`/members/`) + .body({members: [memberToChange]}) + .expectStatus(201) + .matchBodySnapshot({ + members: new Array(1).fill(memberMatcherShallowIncludes) + }) + .matchHeaderSnapshot({ + etag: anyEtag, + location: anyLocationFor('members') + }); + const newMember = body.members[0]; + + await assertMemberEvents({ + eventType: 'MemberSubscribeEvent', + memberId: newMember.id, + asserts: [{ + subscribed: true, + source: 'admin', + newsletter_id: newsletters[0].id + }] + }); + + await agent + .put(`/members/${newMember.id}/`) + .body({members: [memberChanged]}) + .expectStatus(200) + .matchBodySnapshot({ + members: new Array(1).fill(memberMatcherShallowIncludes) + }) + .matchHeaderSnapshot({ + etag: anyEtag + }); + + await assertMemberEvents({ + eventType: 'MemberSubscribeEvent', + memberId: newMember.id, + asserts: [ + { + subscribed: true, + source: 'admin', + newsletter_id: newsletters[0].id + }, { + subscribed: true, + source: 'admin', + newsletter_id: newsletters[1].id + }, { + subscribed: false, + source: 'admin', + newsletter_id: newsletters[0].id + } + ] + }); + }); + + it('Subscribes to default newsletters', async function () { + const filtered = newsletters.filter(n => n.get('subscribe_on_signup')); + filtered.length.should.be.greaterThan(0, 'There should be at least one newsletter with subscribe on signup for this test to work'); + + const memberToCreate = { + name: 'create me', + email: 'member2create@test.com' + }; + + const {body} = await agent + .post(`/members/`) + .body({members: [memberToCreate]}) + .expectStatus(201) + .matchBodySnapshot({ + members: new Array(1).fill(memberMatcherShallowIncludes) + }) + .matchHeaderSnapshot({ + etag: anyEtag, + location: anyLocationFor('members') + }); + + const newMember = body.members[0]; + newMember.newsletters.should.match([ + { + id: filtered[0].id + }, + { + id: filtered[1].id + } + ]); + + await assertMemberEvents({ + eventType: 'MemberSubscribeEvent', + memberId: newMember.id, + asserts: filtered.map((n) => { + return { + subscribed: true, + source: 'admin', + newsletter_id: n.id + }; + }) + }); + }); + it('Can add a subscription', async function () { const memberId = testUtils.DataGenerator.Content.members[0].id; const price = testUtils.DataGenerator.Content.stripe_prices[0]; @@ -1156,7 +1364,8 @@ describe('Members API', function () { product_id: anyObjectId } } - }] + }], + newsletters: anyArray }) }) .matchHeaderSnapshot({ @@ -1183,7 +1392,8 @@ describe('Members API', function () { product_id: anyObjectId } } - }] + }], + newsletters: anyArray }) }) .matchHeaderSnapshot({ @@ -1339,90 +1549,13 @@ describe('Members API', function () { }] }); }); -}); - -describe('Members API: with multiple newsletters', function () { - before(async function () { - agent = await agentProvider.getAdminAPIAgent(); - await fixtureManager.init('newsletters', 'members:newsletters'); - await agent.loginAsOwner(); - }); - - beforeEach(function () { - mockManager.mockLabsEnabled('multipleNewsletters'); - mockManager.mockStripe(); - mockManager.mockMail(); - }); - - afterEach(function () { - mockManager.restore(); - }); - - // List Members - - it('Can browse', async function () { - await agent - .get('/members/') - .expectStatus(200) - .matchBodySnapshot({ - members: new Array(8).fill(memberMatcherShallowIncludesForNewsletters) - }) - .matchHeaderSnapshot({ - etag: anyEtag - }); - }); - - // Read a member - - it('Can read', async function () { - await agent - .get(`/members/${testUtils.DataGenerator.Content.members[0].id}/`) - .expectStatus(200) - .matchBodySnapshot({ - members: new Array(1).fill(memberMatcherShallowIncludesForNewsletters) - }) - .matchHeaderSnapshot({ - etag: anyEtag - }); - }); - - // Create a member - it('Can add with default newsletters', async function () { - const member = { - name: 'test', - email: 'memberTestNewsletterAdd@test.com', - note: 'test note', - subscribed: false, - labels: ['test-label'] - }; - - await agent - .post(`/members/`) - .body({members: [member]}) - .expectStatus(201) - .matchBodySnapshot({ - members: [{ - id: anyObjectId, - uuid: anyUuid, - created_at: anyISODateTime, - updated_at: anyISODateTime, - subscriptions: anyArray, - labels: anyArray, - newsletters: Array(2).fill(newsletterSnapshot) - }] - }) - .matchHeaderSnapshot({ - etag: anyEtag, - location: anyLocationFor('members') - }); - }); it('Can filter on newsletter slug', async function () { await agent .get('/members/?filter=newsletters:weekly-newsletter') .expectStatus(200) .matchBodySnapshot({ - members: new Array(4).fill(memberMatcherShallowIncludesForNewsletters) + members: new Array(4).fill(memberMatcherShallowIncludes) }) .matchHeaderSnapshot({ etag: anyEtag diff --git a/yarn.lock b/yarn.lock index 477b912b4c..6c57af9522 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2046,10 +2046,10 @@ "@tryghost/domain-events" "^0.1.9" "@tryghost/member-events" "^0.4.1" -"@tryghost/members-api@5.12.0": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-5.12.0.tgz#b26389fef5e66602768e953cdc37da6714bd693f" - integrity sha512-lczUcc5Vd3WKUaBu4x2aP5y9CqFqqHEFOS5Rh/KvYD6Nu8UDdjxtb6mok9IngH48UG2RS8y3WoWkJFNtSNBfpQ== +"@tryghost/members-api@6.0.0-alpha.0": + version "6.0.0-alpha.0" + resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-6.0.0-alpha.0.tgz#91f99cc7076c612ccb84d8b8dd6135246968b842" + integrity sha512-qV7WEK7GZ3ejenIThj8+bSUa7ubKSN5iqB0LZXXFwihEWaySheZhNXhNJda6e1MiZwQYqQBH1TrVuv4hKfRwCA== dependencies: "@nexes/nql" "^0.6.0" "@tryghost/debug" "^0.1.2"