mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-27 00:52:36 +03:00
✨ Added unsubscribe_url to member api response (#21207)
ref https://linear.app/tryghost/issue/ONC-387/ With some recent changes, we added validation to unsubscribe URLs to verify the source, allowing us to cut down on spam and improving security, as the underlying key could be re-generated should the need arise. This had the side effect of making unsubscribe URLs difficult to reconstruct when using third-party/downstream integrations, such as ActiveCampaign, which fills a gap in the current Ghost feature set. Now any authenticated query to `/api/members` will return an `unsubscribe_url` field that can be used directly.
This commit is contained in:
parent
a0600e3595
commit
63f25ece6d
@ -167,7 +167,8 @@ function serializeMember(member, options) {
|
||||
email_recipients: json.email_recipients,
|
||||
status: json.status,
|
||||
last_seen_at: json.last_seen_at,
|
||||
attribution: serializeAttribution(json.attribution)
|
||||
attribution: serializeAttribution(json.attribution),
|
||||
unsubscribe_url: json.unsubscribe_url
|
||||
};
|
||||
|
||||
if (json.products) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const stripeService = require('../stripe');
|
||||
const settingsCache = require('../../../shared/settings-cache');
|
||||
const settingsHelpers = require('../../services/settings-helpers');
|
||||
const MembersApi = require('@tryghost/members-api');
|
||||
const logging = require('@tryghost/logging');
|
||||
const mail = require('../mail');
|
||||
@ -236,7 +237,8 @@ function createApiInstance(config) {
|
||||
memberAttributionService: memberAttributionService.service,
|
||||
emailSuppressionList,
|
||||
settingsCache,
|
||||
sentry
|
||||
sentry,
|
||||
settingsHelpers
|
||||
});
|
||||
|
||||
return membersApiInstance;
|
||||
|
@ -22,6 +22,7 @@ module.exports.formattedMemberResponse = function formattedMemberResponse(member
|
||||
firstname: member.name && member.name.split(' ')[0],
|
||||
expertise: member.expertise,
|
||||
avatar_image: member.avatar_image,
|
||||
unsubscribe_url: member.unsubscribe_url,
|
||||
subscribed: !!member.subscribed,
|
||||
subscriptions: member.subscriptions || [],
|
||||
paid: member.status !== 'free',
|
||||
|
@ -2,6 +2,7 @@ const tpl = require('@tryghost/tpl');
|
||||
const errors = require('@tryghost/errors');
|
||||
const {EmailAddressParser} = require('@tryghost/email-addresses');
|
||||
const logging = require('@tryghost/logging');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const messages = {
|
||||
incorrectKeyType: 'type must be one of "direct" or "connect".'
|
||||
@ -179,6 +180,30 @@ class SettingsHelpers {
|
||||
return this.#managedEmailEnabled() || this.labs.isSet('newEmailAddresses');
|
||||
}
|
||||
|
||||
createUnsubscribeUrl(uuid, options = {}) {
|
||||
const siteUrl = this.urlUtils.urlFor('home', true);
|
||||
const unsubscribeUrl = new URL(siteUrl);
|
||||
const key = this.getMembersValidationKey();
|
||||
unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
|
||||
if (uuid) {
|
||||
// hash key with member uuid for verification (and to not leak uuid) - it's possible to update member email prefs without logging in
|
||||
// @ts-ignore
|
||||
const hmac = crypto.createHmac('sha256', key).update(`${uuid}`).digest('hex');
|
||||
unsubscribeUrl.searchParams.set('uuid', uuid);
|
||||
unsubscribeUrl.searchParams.set('key', hmac);
|
||||
} else {
|
||||
unsubscribeUrl.searchParams.set('preview', '1');
|
||||
}
|
||||
if (options.newsletterUuid) {
|
||||
unsubscribeUrl.searchParams.set('newsletter', options.newsletterUuid);
|
||||
}
|
||||
if (options.comments) {
|
||||
unsubscribeUrl.searchParams.set('comments', '1');
|
||||
}
|
||||
|
||||
return unsubscribeUrl.href;
|
||||
}
|
||||
|
||||
// PRIVATE
|
||||
|
||||
#managedEmailEnabled() {
|
||||
|
@ -122,6 +122,7 @@ Object {
|
||||
"yearly_price_id": Any<String>,
|
||||
},
|
||||
],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -133,7 +134,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription 2: [headers]
|
||||
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": "2484",
|
||||
"content-length": "2573",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -225,6 +226,7 @@ Object {
|
||||
},
|
||||
],
|
||||
"tiers": Array [],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -236,7 +238,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription 4: [headers]
|
||||
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": "1584",
|
||||
"content-length": "1673",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -272,6 +274,7 @@ Object {
|
||||
"subscribed": false,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -283,7 +286,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription for a member
|
||||
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": "3635",
|
||||
"content-length": "3724",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -319,6 +322,7 @@ Object {
|
||||
"subscribed": false,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -330,7 +334,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription for a member
|
||||
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": "2735",
|
||||
"content-length": "2824",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -374,6 +378,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -385,7 +390,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription for a member
|
||||
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": "3425",
|
||||
"content-length": "3514",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -429,6 +434,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -440,7 +446,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription for a member
|
||||
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": "2525",
|
||||
"content-length": "2614",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -484,6 +490,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -495,7 +502,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription for a member
|
||||
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": "3434",
|
||||
"content-length": "3523",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -539,6 +546,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -550,7 +558,7 @@ exports[`Members API: edit subscriptions Can cancel a subscription for a member
|
||||
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": "2534",
|
||||
"content-length": "2623",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -839,6 +847,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -850,7 +859,7 @@ exports[`Members API: edit subscriptions Can recover member products when we can
|
||||
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": "3467",
|
||||
"content-length": "3556",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -894,6 +903,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -905,7 +915,7 @@ exports[`Members API: edit subscriptions Can recover member products when we can
|
||||
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": "2567",
|
||||
"content-length": "2656",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -949,6 +959,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -960,7 +971,7 @@ exports[`Members API: edit subscriptions Can recover member products when we upd
|
||||
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": "4222",
|
||||
"content-length": "4311",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -1004,6 +1015,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -1015,7 +1027,7 @@ exports[`Members API: edit subscriptions Can update a subscription for a member
|
||||
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": "4201",
|
||||
"content-length": "4290",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -1059,6 +1071,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -1070,7 +1083,7 @@ exports[`Members API: edit subscriptions Can update a subscription for a member
|
||||
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": "4200",
|
||||
"content-length": "4289",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
|
@ -25,6 +25,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": false,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -50,6 +51,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": false,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -71,7 +73,7 @@ exports[`Members API - With Newsletters - compat mode Can fetch members who are
|
||||
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": "2229",
|
||||
"content-length": "2407",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -105,6 +107,7 @@ Object {
|
||||
"status": "free",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -130,6 +133,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -155,6 +159,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -180,6 +185,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -205,6 +211,7 @@ Object {
|
||||
"status": "free",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -230,6 +237,7 @@ Object {
|
||||
"status": "free",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -251,7 +259,7 @@ exports[`Members API - With Newsletters - compat mode Can fetch members who are
|
||||
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": "7972",
|
||||
"content-length": "8506",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -285,6 +293,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": false,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -310,6 +319,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": false,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -331,7 +341,7 @@ exports[`Members API - With Newsletters Can fetch members who are NOT subscribed
|
||||
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": "2229",
|
||||
"content-length": "2407",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -365,6 +375,7 @@ Object {
|
||||
"status": "free",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -390,6 +401,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -415,6 +427,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -440,6 +453,7 @@ Object {
|
||||
"status": "paid",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -465,6 +479,7 @@ Object {
|
||||
"status": "free",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -490,6 +505,7 @@ Object {
|
||||
"status": "free",
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -511,7 +527,7 @@ exports[`Members API - With Newsletters Can fetch members who are subscribed 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": "7972",
|
||||
"content-length": "8506",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,8 @@ const assert = require('assert/strict');
|
||||
const models = require('../../../core/server/models');
|
||||
const {stripeMocker} = require('../../utils/e2e-framework-mock-manager');
|
||||
const DomainEvents = require('@tryghost/domain-events/lib/DomainEvents');
|
||||
const settingsHelpers = require('../../../core/server/services/settings-helpers');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const subscriptionSnapshot = {
|
||||
id: anyString,
|
||||
@ -49,6 +51,7 @@ describe('Members API: edit subscriptions', function () {
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
sinon.stub(settingsHelpers, 'createUnsubscribeUrl').returns('http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme'); // member uuid changes with every test run
|
||||
mockManager.mockStripe();
|
||||
mockManager.mockMail();
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
const {agentProvider, mockManager, fixtureManager, matchers} = require('../../utils/e2e-framework');
|
||||
const {anyContentVersion, anyEtag, anyObjectId, anyUuid, anyISODateTime, anyArray} = matchers;
|
||||
const settingsHelpers = require('../../../core/server/services/settings-helpers');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const memberMatcherShallowIncludesForNewsletters = {
|
||||
id: anyObjectId,
|
||||
@ -20,6 +22,10 @@ describe('Members API - With Newsletters', function () {
|
||||
await agent.loginAsOwner();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
sinon.stub(settingsHelpers, 'createUnsubscribeUrl').returns('http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
mockManager.restore();
|
||||
});
|
||||
@ -60,6 +66,10 @@ describe('Members API - With Newsletters - compat mode', function () {
|
||||
await agent.loginAsOwner();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
sinon.stub(settingsHelpers, 'createUnsubscribeUrl').returns('http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
mockManager.restore();
|
||||
});
|
||||
|
@ -22,6 +22,7 @@ const settingsCache = require('../../../core/shared/settings-cache');
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const logging = require('@tryghost/logging');
|
||||
const {stripeMocker, mockLabsDisabled} = require('../../utils/e2e-framework-mock-manager');
|
||||
const settingsHelpers = require('../../../core/server/services/settings-helpers');
|
||||
|
||||
/**
|
||||
* Assert that haystack and needles match, ignoring the order.
|
||||
@ -136,7 +137,8 @@ function buildMemberWithIncludesSnapshot(options) {
|
||||
attribution: attributionSnapshot,
|
||||
newsletters: new Array(options.newsletters).fill(newsletterSnapshot),
|
||||
subscriptions: anyArray,
|
||||
labels: anyArray
|
||||
labels: anyArray,
|
||||
unsubscribe_url: anyString
|
||||
};
|
||||
}
|
||||
|
||||
@ -154,7 +156,8 @@ const memberMatcherShallowIncludes = {
|
||||
created_at: anyISODateTime,
|
||||
updated_at: anyISODateTime,
|
||||
subscriptions: anyArray,
|
||||
labels: anyArray
|
||||
labels: anyArray,
|
||||
unsubscribe_url: anyString
|
||||
};
|
||||
|
||||
/**
|
||||
@ -487,13 +490,14 @@ describe('Members API', function () {
|
||||
agent = await agentProvider.getAdminAPIAgent();
|
||||
await fixtureManager.init('posts', 'newsletters', 'members:newsletters', 'comments', 'redirects', 'clicks');
|
||||
await agent.loginAsOwner();
|
||||
|
||||
|
||||
newsletters = await getNewsletters();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
mockManager.mockStripe();
|
||||
emailMockReceiver = mockManager.mockMail();
|
||||
sinon.stub(settingsHelpers, 'createUnsubscribeUrl').returns('http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
@ -1668,6 +1668,94 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Comments API when commenting enabled for all when authenticated Browsing comments does not return the member unsubscribe_url 1: [body] 1`] = `
|
||||
Object {
|
||||
"comments": Array [
|
||||
Object {
|
||||
"count": Object {
|
||||
"likes": Any<Number>,
|
||||
"replies": 0,
|
||||
},
|
||||
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"edited_at": null,
|
||||
"html": "<p>This is a comment</p>",
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"liked": Any<Boolean>,
|
||||
"member": Object {
|
||||
"avatar_image": null,
|
||||
"expertise": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Egon Spengler",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
},
|
||||
"replies": Array [],
|
||||
"status": "published",
|
||||
},
|
||||
Object {
|
||||
"count": Object {
|
||||
"likes": Any<Number>,
|
||||
"replies": Any<Number>,
|
||||
},
|
||||
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"edited_at": null,
|
||||
"html": "<p>This is a comment</p>",
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"liked": Any<Boolean>,
|
||||
"member": Object {
|
||||
"avatar_image": null,
|
||||
"expertise": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Mr Egg",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
},
|
||||
"replies": Array [
|
||||
Object {
|
||||
"count": Object {
|
||||
"likes": Any<Number>,
|
||||
},
|
||||
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"edited_at": null,
|
||||
"html": "<p>This is a reply</p>",
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"liked": Any<Boolean>,
|
||||
"member": Object {
|
||||
"avatar_image": null,
|
||||
"expertise": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": null,
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
},
|
||||
"status": "published",
|
||||
},
|
||||
],
|
||||
"status": "published",
|
||||
},
|
||||
],
|
||||
"meta": Object {
|
||||
"pagination": Object {
|
||||
"limit": 15,
|
||||
"next": null,
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"prev": null,
|
||||
"total": 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Comments API when commenting enabled for all when authenticated Browsing comments does not return the member unsubscribe_url 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": "1118",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Comments API when commenting enabled for all when authenticated Can browse all comments of a post (legacy) 1: [body] 1`] = `
|
||||
Object {
|
||||
"comments": Array [
|
||||
|
@ -501,6 +501,15 @@ describe('Comments API', function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it('Browsing comments does not return the member unsubscribe_url', async function () {
|
||||
await setupBrowseCommentsData();
|
||||
const response = await testGetComments(`/api/comments/post/${postId}/`, [
|
||||
commentMatcher,
|
||||
commentMatcherWithReplies({replies: 1})
|
||||
]);
|
||||
should.not.exist(response.body.comments[0].unsubscribe_url);
|
||||
});
|
||||
|
||||
it('Can reply to your own comment', async function () {
|
||||
// Should not update last_seen_at or last_commented_at when both are already set to a value on the same day
|
||||
const timezone = settingsCache.get('timezone');
|
||||
|
@ -32,6 +32,7 @@ Object {
|
||||
"paid": false,
|
||||
"subscribed": false,
|
||||
"subscriptions": Array [],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
}
|
||||
`;
|
||||
@ -40,7 +41,7 @@ exports[`Comments API when authenticated can get member data 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": "621",
|
||||
"content-length": "710",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Encoding",
|
||||
@ -159,6 +160,7 @@ Object {
|
||||
"paid": false,
|
||||
"subscribed": false,
|
||||
"subscriptions": Array [],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
}
|
||||
`;
|
||||
@ -167,7 +169,7 @@ exports[`Comments API when authenticated can update comment notifications 2: [he
|
||||
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": "633",
|
||||
"content-length": "722",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Encoding",
|
||||
@ -245,6 +247,7 @@ Object {
|
||||
"paid": false,
|
||||
"subscribed": false,
|
||||
"subscriptions": Array [],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
}
|
||||
`;
|
||||
@ -253,7 +256,7 @@ exports[`Comments API when authenticated can update member expertise 2: [headers
|
||||
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": "634",
|
||||
"content-length": "723",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Encoding",
|
||||
@ -293,6 +296,7 @@ Object {
|
||||
"paid": false,
|
||||
"subscribed": false,
|
||||
"subscriptions": Array [],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
}
|
||||
`;
|
||||
@ -301,7 +305,7 @@ exports[`Comments API when authenticated can update name 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": "632",
|
||||
"content-length": "721",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Encoding",
|
||||
@ -341,6 +345,7 @@ Object {
|
||||
"paid": false,
|
||||
"subscribed": false,
|
||||
"subscriptions": Array [],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
}
|
||||
`;
|
||||
@ -349,7 +354,7 @@ exports[`Comments API when authenticated trims whitespace from expertise 2: [hea
|
||||
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": "623",
|
||||
"content-length": "712",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Encoding",
|
||||
@ -441,6 +446,7 @@ Object {
|
||||
"paid": false,
|
||||
"subscribed": false,
|
||||
"subscriptions": Array [],
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
}
|
||||
`;
|
||||
@ -449,7 +455,7 @@ exports[`Comments API when caching members content is enabled sets ghost-access
|
||||
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": "621",
|
||||
"content-length": "710",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"set-cookie": Array [
|
||||
|
@ -27,6 +27,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -38,7 +39,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent with
|
||||
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": "2550",
|
||||
"content-length": "2639",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -74,6 +75,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -85,7 +87,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent with
|
||||
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": "2564",
|
||||
"content-length": "2653",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -121,6 +123,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -132,7 +135,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent with
|
||||
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": "2452",
|
||||
"content-length": "2541",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -168,6 +171,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -179,7 +183,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent with
|
||||
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": "2612",
|
||||
"content-length": "2701",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -215,6 +219,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -226,7 +231,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent with
|
||||
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": "2578",
|
||||
"content-length": "2667",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -262,6 +267,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -273,7 +279,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent with
|
||||
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": "2592",
|
||||
"content-length": "2681",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -309,6 +315,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -320,7 +327,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent with
|
||||
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": "2506",
|
||||
"content-length": "2595",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -356,6 +363,7 @@ Object {
|
||||
"subscribed": true,
|
||||
"subscriptions": Any<Array>,
|
||||
"tiers": Any<Array>,
|
||||
"unsubscribe_url": "http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme",
|
||||
"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\\}/,
|
||||
},
|
||||
@ -367,7 +375,7 @@ exports[`Members API Member attribution Creates a SubscriptionCreatedEvent witho
|
||||
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": "2452",
|
||||
"content-length": "2541",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
|
@ -42,6 +42,7 @@ describe('Comments API', function () {
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
sinon.stub(settingsHelpers, 'createUnsubscribeUrl').returns('http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme');
|
||||
mockManager.mockMail();
|
||||
});
|
||||
|
||||
|
@ -10,6 +10,8 @@ const urlService = require('../../../core/server/services/url');
|
||||
const urlUtils = require('../../../core/shared/url-utils');
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const {anyContentVersion, anyEtag, anyObjectId, anyUuid, anyISODateTime, anyString, anyArray, anyObject} = matchers;
|
||||
const settingsHelpers = require('../../../core/server/services/settings-helpers');
|
||||
const sinon = require('sinon');
|
||||
|
||||
let membersAgent;
|
||||
let adminAgent;
|
||||
@ -120,6 +122,8 @@ describe('Members API', function () {
|
||||
|
||||
return [500];
|
||||
});
|
||||
|
||||
sinon.stub(settingsHelpers, 'createUnsubscribeUrl').returns('http://domain.com/unsubscribe/?uuid=memberuuid&key=abc123dontstealme');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
@ -829,9 +829,10 @@ describe('Front-end members behavior', function () {
|
||||
'created_at',
|
||||
'enable_comment_notifications',
|
||||
'newsletters',
|
||||
'email_suppression'
|
||||
'email_suppression',
|
||||
'unsubscribe_url'
|
||||
]);
|
||||
Object.keys(memberData).should.have.length(13);
|
||||
Object.keys(memberData).should.have.length(14);
|
||||
memberData.should.not.have.property('id');
|
||||
memberData.newsletters.should.have.length(1);
|
||||
|
||||
|
@ -28,6 +28,7 @@ describe('Members Service - utils', function () {
|
||||
suppressed: false,
|
||||
info: null
|
||||
},
|
||||
unsubscribe_url: undefined,
|
||||
created_at: '2020-01-01T00:00:00.000Z'
|
||||
});
|
||||
should(member1).deepEqual({
|
||||
@ -37,6 +38,7 @@ describe('Members Service - utils', function () {
|
||||
expertise: null,
|
||||
firstname: 'Jamie',
|
||||
avatar_image: 'https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank',
|
||||
unsubscribe_url: undefined,
|
||||
subscribed: true,
|
||||
subscriptions: [],
|
||||
paid: false,
|
||||
@ -69,6 +71,7 @@ describe('Members Service - utils', function () {
|
||||
sort_order: 0
|
||||
}],
|
||||
enable_comment_notifications: false,
|
||||
unsubscribe_url: undefined,
|
||||
created_at: '2020-01-01T00:00:00.000Z'
|
||||
});
|
||||
should(member1).deepEqual({
|
||||
@ -89,6 +92,7 @@ describe('Members Service - utils', function () {
|
||||
sort_order: 0
|
||||
}],
|
||||
enable_comment_notifications: false,
|
||||
unsubscribe_url: undefined,
|
||||
created_at: '2020-01-01T00:00:00.000Z'
|
||||
});
|
||||
});
|
||||
|
@ -2,6 +2,9 @@ const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const configUtils = require('../../../../utils/configUtils');
|
||||
const SettingsHelpers = require('../../../../../core/server/services/settings-helpers/SettingsHelpers');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const mockValidationKey = 'validation_key';
|
||||
|
||||
function createSettingsMock({setDirect, setConnect}) {
|
||||
const getStub = sinon.stub();
|
||||
@ -27,67 +30,122 @@ function createSettingsMock({setDirect, setConnect}) {
|
||||
getStub.withArgs('stripe_connect_display_name').returns('Test');
|
||||
getStub.withArgs('stripe_connect_account_id').returns('ac_XXXXXXXXXXXXX');
|
||||
|
||||
getStub.withArgs('members_email_auth_secret').returns(mockValidationKey);
|
||||
|
||||
return {
|
||||
get: getStub
|
||||
};
|
||||
}
|
||||
|
||||
describe('Settings Helpers - getActiveStripeKeys', function () {
|
||||
beforeEach(function () {
|
||||
configUtils.set({
|
||||
url: 'http://domain.tld/subdir',
|
||||
admin: {url: 'http://sub.domain.tld'}
|
||||
describe('Settings Helpers', function () {
|
||||
describe('getActiveStripeKeys', function () {
|
||||
beforeEach(function () {
|
||||
configUtils.set({
|
||||
url: 'http://domain.tld/subdir',
|
||||
admin: {url: 'http://sub.domain.tld'}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
await configUtils.restore();
|
||||
});
|
||||
|
||||
it('Uses direct keys when stripeDirect is true, regardles of which keys exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: true, setConnect: true});
|
||||
configUtils.set({
|
||||
stripeDirect: true
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys.publicKey, 'direct_publishable');
|
||||
should.equal(keys.secretKey, 'direct_secret');
|
||||
});
|
||||
|
||||
it('Does not use connect keys if stripeDirect is true, and the direct keys do not exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: false, setConnect: true});
|
||||
configUtils.set({
|
||||
stripeDirect: true
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys, null);
|
||||
});
|
||||
|
||||
it('Uses connect keys when stripeDirect is false, and the connect keys exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: true, setConnect: true});
|
||||
configUtils.set({
|
||||
stripeDirect: false
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys.publicKey, 'connect_publishable');
|
||||
should.equal(keys.secretKey, 'connect_secret');
|
||||
});
|
||||
|
||||
it('Uses direct keys when stripeDirect is false, but the connect keys do not exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: true, setConnect: false});
|
||||
configUtils.set({
|
||||
stripeDirect: false
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys.publicKey, 'direct_publishable');
|
||||
should.equal(keys.secretKey, 'direct_secret');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
await configUtils.restore();
|
||||
describe('getMembersValidationKey', function () {
|
||||
it('returns a key that can be used to validate members', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: true, setConnect: true});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const key = settingsHelpers.getMembersValidationKey();
|
||||
should.equal(key, 'validation_key');
|
||||
});
|
||||
});
|
||||
|
||||
it('Uses direct keys when stripeDirect is true, regardles of which keys exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: true, setConnect: true});
|
||||
configUtils.set({
|
||||
stripeDirect: true
|
||||
describe('createUnsubscribeUrl', function () {
|
||||
const memberUuid = 'memberuuid';
|
||||
const newsletterUuid = 'newsletteruuid';
|
||||
const urlUtils = {
|
||||
urlFor: sinon.stub().returns('http://domain.com/')
|
||||
};
|
||||
const memberUuidHash = crypto.createHmac('sha256', mockValidationKey).update(`${memberUuid}`).digest('hex');
|
||||
let fakeSettings;
|
||||
|
||||
before(function () {
|
||||
fakeSettings = createSettingsMock({setDirect: true, setConnect: true});
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys.publicKey, 'direct_publishable');
|
||||
should.equal(keys.secretKey, 'direct_secret');
|
||||
});
|
||||
|
||||
it('Does not use connect keys if stripeDirect is true, and the direct keys do not exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: false, setConnect: true});
|
||||
configUtils.set({
|
||||
stripeDirect: true
|
||||
afterEach(async function () {
|
||||
await configUtils.restore();
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys, null);
|
||||
});
|
||||
|
||||
it('Uses connect keys when stripeDirect is false, and the connect keys exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: true, setConnect: true});
|
||||
configUtils.set({
|
||||
stripeDirect: false
|
||||
it('returns a generic unsubscribe url when no uuid is provided', function () {
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils});
|
||||
const url = settingsHelpers.createUnsubscribeUrl(null);
|
||||
should.equal(url, 'http://domain.com/unsubscribe/?preview=1');
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys.publicKey, 'connect_publishable');
|
||||
should.equal(keys.secretKey, 'connect_secret');
|
||||
});
|
||||
|
||||
it('Uses direct keys when stripeDirect is false, but the connect keys do not exist', function () {
|
||||
const fakeSettings = createSettingsMock({setDirect: true, setConnect: false});
|
||||
configUtils.set({
|
||||
stripeDirect: false
|
||||
it('returns a url that can be used to unsubscribe a member', function () {
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils});
|
||||
const url = settingsHelpers.createUnsubscribeUrl(memberUuid);
|
||||
should.equal(url, `http://domain.com/unsubscribe/?uuid=memberuuid&key=${memberUuidHash}`);
|
||||
});
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils: {}});
|
||||
const keys = settingsHelpers.getActiveStripeKeys();
|
||||
|
||||
should.equal(keys.publicKey, 'direct_publishable');
|
||||
should.equal(keys.secretKey, 'direct_secret');
|
||||
it('returns a url that can be used to unsubscribe a member for a given newsletter', function () {
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils});
|
||||
const url = settingsHelpers.createUnsubscribeUrl(memberUuid, {newsletterUuid});
|
||||
should.equal(url, `http://domain.com/unsubscribe/?uuid=memberuuid&key=${memberUuidHash}&newsletter=newsletteruuid`);
|
||||
});
|
||||
|
||||
it('returns a url that can be used to unsubscribe a member from comments', function () {
|
||||
const settingsHelpers = new SettingsHelpers({settingsCache: fakeSettings, config: configUtils.config, urlUtils});
|
||||
const url = settingsHelpers.createUnsubscribeUrl(memberUuid, {comments: true});
|
||||
should.equal(url, `http://domain.com/unsubscribe/?uuid=memberuuid&key=${memberUuidHash}&comments=1`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -123,7 +123,7 @@ class EmailRenderer {
|
||||
/**
|
||||
* @param {object} dependencies
|
||||
* @param {object} dependencies.settingsCache
|
||||
* @param {{getNoReplyAddress(): string, getMembersSupportAddress(): string, getMembersValidationKey(): string}} dependencies.settingsHelpers
|
||||
* @param {{getNoReplyAddress(): string, getMembersSupportAddress(): string, getMembersValidationKey(): string, createUnsubscribeUrl(uuid: string, options: object): string}} dependencies.settingsHelpers
|
||||
* @param {object} dependencies.renderers
|
||||
* @param {{render(object, options): string}} dependencies.renderers.lexical
|
||||
* @param {{render(object, options): string}} dependencies.renderers.mobiledoc
|
||||
@ -505,27 +505,7 @@ class EmailRenderer {
|
||||
* @param {boolean} [options.comments] Unsubscribe from comment emails
|
||||
*/
|
||||
createUnsubscribeUrl(uuid, options = {}) {
|
||||
const siteUrl = this.#urlUtils.urlFor('home', true);
|
||||
const unsubscribeUrl = new URL(siteUrl);
|
||||
const key = this.#settingsHelpers.getMembersValidationKey();
|
||||
unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
|
||||
if (uuid) {
|
||||
// hash key with member uuid for verification (and to not leak uuid) - it's possible to update member email prefs without logging in
|
||||
// @ts-ignore
|
||||
const hmac = crypto.createHmac('sha256', key).update(`${uuid}`).digest('hex');
|
||||
unsubscribeUrl.searchParams.set('uuid', uuid);
|
||||
unsubscribeUrl.searchParams.set('key', hmac);
|
||||
} else {
|
||||
unsubscribeUrl.searchParams.set('preview', '1');
|
||||
}
|
||||
if (options.newsletterUuid) {
|
||||
unsubscribeUrl.searchParams.set('newsletter', options.newsletterUuid);
|
||||
}
|
||||
if (options.comments) {
|
||||
unsubscribeUrl.searchParams.set('comments', '1');
|
||||
}
|
||||
|
||||
return unsubscribeUrl.href;
|
||||
return this.#settingsHelpers.createUnsubscribeUrl(uuid, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,6 +61,10 @@ async function validateHtml(html) {
|
||||
assert.equal(report.valid, true, 'Expected valid HTML without warnings, got errors:\n' + parsedErrors.join('\n\n'));
|
||||
}
|
||||
|
||||
const createUnsubscribeUrl = (uuid) => {
|
||||
return `https://example.com/unsubscribe/?uuid=${uuid}&key=456`;
|
||||
};
|
||||
|
||||
const getMembersValidationKey = () => {
|
||||
return 'members-key';
|
||||
};
|
||||
@ -98,7 +102,7 @@ describe('Email renderer', function () {
|
||||
}
|
||||
}
|
||||
},
|
||||
settingsHelpers: {getMembersValidationKey}
|
||||
settingsHelpers: {getMembersValidationKey,createUnsubscribeUrl}
|
||||
});
|
||||
newsletter = createModel({
|
||||
uuid: 'newsletteruuid'
|
||||
@ -119,8 +123,8 @@ describe('Email renderer', function () {
|
||||
assert.equal(replacements.length, 1);
|
||||
assert.equal(replacements[0].token.toString(), '/%%\\{list_unsubscribe\\}%%/g');
|
||||
assert.equal(replacements[0].id, 'list_unsubscribe');
|
||||
const memberHmac = crypto.createHmac('sha256', getMembersValidationKey()).update(member.uuid).digest('hex');
|
||||
assert.equal(replacements[0].getValue(member), `http://example.com/subdirectory/unsubscribe/?uuid=${member.uuid}&key=${memberHmac}&newsletter=newsletteruuid`);
|
||||
const unsubscribeUrl = createUnsubscribeUrl(member.uuid);
|
||||
assert.equal(replacements[0].getValue(member), unsubscribeUrl);
|
||||
});
|
||||
|
||||
it('returns a replacement if it is used', function () {
|
||||
@ -156,8 +160,8 @@ describe('Email renderer', function () {
|
||||
assert.equal(replacements.length, 2);
|
||||
assert.equal(replacements[0].token.toString(), '/%%\\{unsubscribe_url\\}%%/g');
|
||||
assert.equal(replacements[0].id, 'unsubscribe_url');
|
||||
const memberHmac = crypto.createHmac('sha256', getMembersValidationKey()).update(member.uuid).digest('hex');
|
||||
assert.equal(replacements[0].getValue(member), `http://example.com/subdirectory/unsubscribe/?uuid=${member.uuid}&key=${memberHmac}&newsletter=newsletteruuid`);
|
||||
const unsubscribeUrl = createUnsubscribeUrl(member.uuid);
|
||||
assert.equal(replacements[0].getValue(member), unsubscribeUrl);
|
||||
});
|
||||
|
||||
it('returns correct name', function () {
|
||||
@ -2303,7 +2307,8 @@ describe('Email renderer', function () {
|
||||
}
|
||||
},
|
||||
settingsHelpers: {
|
||||
getMembersValidationKey
|
||||
getMembersValidationKey,
|
||||
createUnsubscribeUrl
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -2312,21 +2317,26 @@ describe('Email renderer', function () {
|
||||
const response = await emailRenderer.createUnsubscribeUrl('memberuuid', {
|
||||
newsletterUuid: 'newsletteruuid'
|
||||
});
|
||||
const memberHmac = crypto.createHmac('sha256', getMembersValidationKey()).update('memberuuid').digest('hex');
|
||||
assert.equal(response, `http://example.com/subdirectory/unsubscribe/?uuid=memberuuid&key=${memberHmac}&newsletter=newsletteruuid`);
|
||||
const unsubscribeUrl = createUnsubscribeUrl('memberuuid', {
|
||||
newsletterUuid: 'newsletteruuid'
|
||||
});
|
||||
assert.equal(response, unsubscribeUrl);
|
||||
});
|
||||
|
||||
it('includes comments', async function () {
|
||||
const response = await emailRenderer.createUnsubscribeUrl('memberuuid', {
|
||||
comments: true
|
||||
});
|
||||
const memberHmac = crypto.createHmac('sha256', getMembersValidationKey()).update('memberuuid').digest('hex');
|
||||
assert.equal(response, `http://example.com/subdirectory/unsubscribe/?uuid=memberuuid&key=${memberHmac}&comments=1`);
|
||||
const unsubscribeUrl = createUnsubscribeUrl('memberuuid', {
|
||||
comments: true
|
||||
});
|
||||
assert.equal(response, unsubscribeUrl);
|
||||
});
|
||||
|
||||
it('works for previews', async function () {
|
||||
const response = await emailRenderer.createUnsubscribeUrl();
|
||||
assert.equal(response, `http://example.com/subdirectory/unsubscribe/?preview=1`);
|
||||
const unsubscribeUrl = createUnsubscribeUrl();
|
||||
assert.equal(response, unsubscribeUrl);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -72,7 +72,8 @@ module.exports = function MembersAPI({
|
||||
memberAttributionService,
|
||||
emailSuppressionList,
|
||||
settingsCache,
|
||||
sentry
|
||||
sentry,
|
||||
settingsHelpers
|
||||
}) {
|
||||
const tokenService = new TokenService({
|
||||
privateKey,
|
||||
@ -144,7 +145,8 @@ module.exports = function MembersAPI({
|
||||
labsService,
|
||||
stripeService: stripeAPIService,
|
||||
memberAttributionService,
|
||||
emailSuppressionList
|
||||
emailSuppressionList,
|
||||
settingsHelpers
|
||||
});
|
||||
|
||||
const geolocationService = new GeolocationService();
|
||||
|
@ -37,8 +37,9 @@ module.exports = class MemberBREADService {
|
||||
* @param {IStripeService} deps.stripeService
|
||||
* @param {import('@tryghost/member-attribution/lib/service')} deps.memberAttributionService
|
||||
* @param {import('@tryghost/email-suppression-list/lib/email-suppression-list').IEmailSuppressionList} deps.emailSuppressionList
|
||||
* @param {import('@tryghost/settings-helpers')} deps.settingsHelpers
|
||||
*/
|
||||
constructor({memberRepository, labsService, emailService, stripeService, offersAPI, memberAttributionService, emailSuppressionList}) {
|
||||
constructor({memberRepository, labsService, emailService, stripeService, offersAPI, memberAttributionService, emailSuppressionList, settingsHelpers}) {
|
||||
this.offersAPI = offersAPI;
|
||||
/** @private */
|
||||
this.memberRepository = memberRepository;
|
||||
@ -52,6 +53,8 @@ module.exports = class MemberBREADService {
|
||||
this.memberAttributionService = memberAttributionService;
|
||||
/** @private */
|
||||
this.emailSuppressionList = emailSuppressionList;
|
||||
/** @private */
|
||||
this.settingsHelpers = settingsHelpers;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,6 +249,9 @@ module.exports = class MemberBREADService {
|
||||
info: suppressionData.info
|
||||
};
|
||||
|
||||
const unsubscribeUrl = this.settingsHelpers.createUnsubscribeUrl(member.id);
|
||||
member.unsubscribe_url = unsubscribeUrl;
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
@ -426,6 +432,7 @@ module.exports = class MemberBREADService {
|
||||
suppressed: bulkSuppressionData[index].suppressed || !!model.get('email_disabled'),
|
||||
info: bulkSuppressionData[index].info
|
||||
};
|
||||
member.unsubscribe_url = this.settingsHelpers.createUnsubscribeUrl(member.id);
|
||||
return member;
|
||||
});
|
||||
|
||||
|
@ -26,6 +26,9 @@ describe('MemberBreadService', function () {
|
||||
|
||||
const getService = () => {
|
||||
return new MemberBreadService({
|
||||
settingsHelpers: {
|
||||
createUnsubscribeUrl: sinon.stub().returns('https://example.com/unsubscribe/?uuid=123&key=456')
|
||||
},
|
||||
memberRepository: memberRepositoryStub,
|
||||
memberAttributionService: memberAttributionServiceStub,
|
||||
emailSuppressionList: emailSuppressionListStub
|
||||
@ -286,5 +289,12 @@ describe('MemberBreadService', function () {
|
||||
info: 'bounce'
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a member with an unsubscribe url', async function () {
|
||||
const memberBreadService = getService();
|
||||
const member = await memberBreadService.read({id: MEMBER_ID});
|
||||
|
||||
assert.equal(member.unsubscribe_url, 'https://example.com/unsubscribe/?uuid=123&key=456');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user