diff --git a/core/server/models/member.js b/core/server/models/member.js index 4e530b99ca..c5a8864d44 100644 --- a/core/server/models/member.js +++ b/core/server/models/member.js @@ -373,6 +373,18 @@ const Member = ghostBookshelf.Model.extend({ return query; }, + getNewsletterRelations(data, unfilteredOptions = {}) { + const query = ghostBookshelf.knex('members_newsletters') + .select('id') + .whereIn('member_id', data.memberIds); + + if (unfilteredOptions.transacting) { + query.transacting(unfilteredOptions.transacting); + } + + return query; + }, + fetchAllSubscribed(unfilteredOptions = {}) { // we use raw queries instead of model relationships because model hydration is expensive const query = ghostBookshelf.knex('members_newsletters') diff --git a/package.json b/package.json index dbaa95debb..9e6e4f08f8 100644 --- a/package.json +++ b/package.json @@ -67,11 +67,11 @@ "@tryghost/custom-theme-settings-service": "0.3.2", "@tryghost/database-info": "0.3.3", "@tryghost/debug": "0.1.16", - "@tryghost/domain-events": "0.1.9", + "@tryghost/domain-events": "0.1.10", "@tryghost/email-analytics-provider-mailgun": "1.0.8", "@tryghost/email-analytics-service": "1.0.6", "@tryghost/errors": "1.2.12", - "@tryghost/express-dynamic-redirects": "0.2.8", + "@tryghost/express-dynamic-redirects": "0.2.9", "@tryghost/helpers": "1.1.64", "@tryghost/image-transform": "1.0.30", "@tryghost/job-manager": "0.8.22", @@ -82,14 +82,14 @@ "@tryghost/kg-mobiledoc-html-renderer": "5.3.5", "@tryghost/limit-service": "1.1.0", "@tryghost/logging": "2.1.8", - "@tryghost/magic-link": "1.0.21", - "@tryghost/member-events": "0.4.1", - "@tryghost/members-api": "6.1.0", + "@tryghost/magic-link": "1.0.22", + "@tryghost/member-events": "0.4.2", + "@tryghost/members-api": "6.2.2", "@tryghost/members-events-service": "0.3.3", - "@tryghost/members-importer": "0.5.8", - "@tryghost/members-offers": "0.11.1", - "@tryghost/members-ssr": "1.0.23", - "@tryghost/members-stripe-service": "0.10.0", + "@tryghost/members-importer": "0.5.9", + "@tryghost/members-offers": "0.11.2", + "@tryghost/members-ssr": "1.0.24", + "@tryghost/members-stripe-service": "0.10.1", "@tryghost/metrics": "1.0.11", "@tryghost/minifier": "0.1.13", "@tryghost/mw-api-version-mismatch": "0.1.1", @@ -111,7 +111,7 @@ "@tryghost/update-check-service": "0.3.2", "@tryghost/url-utils": "2.1.0", "@tryghost/validator": "0.1.24", - "@tryghost/verification-trigger": "0.2.0", + "@tryghost/verification-trigger": "0.2.1", "@tryghost/version": "0.1.14", "@tryghost/version-notifications-data-service": "0.1.0", "@tryghost/vhost-middleware": "1.0.23", diff --git a/test/e2e-api/admin/legacy-members.test.js b/test/e2e-api/admin/legacy-members.test.js deleted file mode 100644 index 7c5be7645a..0000000000 --- a/test/e2e-api/admin/legacy-members.test.js +++ /dev/null @@ -1,709 +0,0 @@ -const path = require('path'); -const should = require('should'); -const supertest = require('supertest'); -const sinon = require('sinon'); -const testUtils = require('../../utils'); -const localUtils = require('./utils'); -const config = require('../../../core/shared/config'); -const Papa = require('papaparse'); - -const {mockManager} = require('../../utils/e2e-framework'); - -describe('Legacy Members API', function () { - let request; - - beforeEach(function () { - mockManager.mockLabsDisabled('multipleProducts'); - }); - - afterEach(function () { - mockManager.restore(); - }); - - before(async function () { - await localUtils.startGhost(); - request = supertest.agent(config.get('url')); - await localUtils.doAuth(request, 'members', 'members:emails'); - }); - - it('Can browse', async function () { - const res = await request - .get(localUtils.API.getApiQuery('members/')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(8); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'subscriptions'); - - testUtils.API.isISO8601(jsonResponse.members[0].created_at).should.be.true(); - jsonResponse.members[0].created_at.should.be.an.instanceof(String); - - jsonResponse.meta.pagination.should.have.property('page', 1); - jsonResponse.meta.pagination.should.have.property('limit', 15); - jsonResponse.meta.pagination.should.have.property('pages', 1); - jsonResponse.meta.pagination.should.have.property('total', 8); - jsonResponse.meta.pagination.should.have.property('next', null); - jsonResponse.meta.pagination.should.have.property('prev', null); - }); - - it('Can browse with filter', async function () { - const res = await request - .get(localUtils.API.getApiQuery('members/?filter=label:label-1')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse, 'members'); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'subscriptions'); - localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination'); - }); - - it('Can browse with search', async function () { - const res = await request - .get(localUtils.API.getApiQuery('members/?search=member1')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - jsonResponse.members[0].email.should.equal('member1@test.com'); - localUtils.API.checkResponse(jsonResponse, 'members'); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'subscriptions'); - localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination'); - }); - - it('Can filter by paid status', async function () { - const res = await request - .get(localUtils.API.getApiQuery('members/?filter=status:paid')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(5); - jsonResponse.members[0].email.should.equal('with-product@test.com'); - jsonResponse.members[1].email.should.equal('vip-paid@test.com'); - localUtils.API.checkResponse(jsonResponse, 'members'); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'subscriptions'); - localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination'); - }); - - it('Can read', async function () { - const res = await request - .get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', ['subscriptions', 'products']); - }); - - it('Can read and include email_recipients', async function () { - const res = await request - .get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/?include=email_recipients`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', ['subscriptions', 'email_recipients', 'products']); - jsonResponse.members[0].email_recipients.length.should.equal(1); - localUtils.API.checkResponse(jsonResponse.members[0].email_recipients[0], 'email_recipient', ['email']); - localUtils.API.checkResponse(jsonResponse.members[0].email_recipients[0].email, 'email'); - }); - - it('Can add', async function () { - const member = { - name: 'test', - email: 'memberTestAdd@test.com', - note: 'test note', - subscribed: false, - labels: ['test-label'] - }; - - const res = await request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [member]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - - jsonResponse.members[0].name.should.equal(member.name); - jsonResponse.members[0].email.should.equal(member.email); - jsonResponse.members[0].note.should.equal(member.note); - jsonResponse.members[0].subscribed.should.equal(member.subscribed); - testUtils.API.isISO8601(jsonResponse.members[0].created_at).should.be.true(); - - jsonResponse.members[0].labels.length.should.equal(1); - jsonResponse.members[0].labels[0].name.should.equal('test-label'); - - should.exist(res.headers.location); - res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('members/')}${res.body.members[0].id}/`); - - await request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [member]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(422); - }); - - it('Can add complimentary subscription', async function () { - const stripeService = require('../../../core/server/services/stripe'); - stripeService.api._configured = true; - const fakePrice = { - id: 'price_1', - product: '', - active: true, - nickname: 'Complimentary', - unit_amount: 0, - currency: 'USD', - type: 'recurring', - recurring: { - interval: 'year' - } - }; - const fakeSubscription = { - id: 'sub_1', - customer: 'cus_1', - status: 'active', - cancel_at_period_end: false, - metadata: {}, - current_period_end: Date.now() / 1000, - start_date: Date.now() / 1000, - plan: fakePrice, - items: { - data: [{ - price: fakePrice - }] - } - }; - sinon.stub(stripeService.api, 'createCustomer').callsFake(async function (data) { - return { - id: 'cus_1', - email: data.email - }; - }); - sinon.stub(stripeService.api, 'createPrice').resolves(fakePrice); - sinon.stub(stripeService.api, 'createSubscription').resolves(fakeSubscription); - sinon.stub(stripeService.api, 'getSubscription').resolves(fakeSubscription); - const initialMember = { - name: 'Name', - email: 'compedtest@test.com', - subscribed: true - }; - - const compedPayload = { - comped: true - }; - - const res = await request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [initialMember]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - - should.exist(res.headers.location); - res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('members/')}${res.body.members[0].id}/`); - - const newMember = jsonResponse.members[0]; - - const res2 = await request - .put(localUtils.API.getApiQuery(`members/${newMember.id}/`)) - .send({members: [compedPayload]}) - .set('Origin', config.get('url')) - .expect(({body}) => { - body.should.have.property('members'); - body.members.should.have.length(1); - }) - .expect(200); - - should.not.exist(res2.headers['x-cache-invalidate']); - - const jsonResponse2 = res2.body; - localUtils.API.checkResponse(jsonResponse2.members[0], 'member', ['subscriptions', 'products']); - - const member = jsonResponse2.members[0]; - - should.equal(member.status, 'comped'); - should.equal(member.subscriptions.length, 1); - stripeService.api._configured = false; - }); - - it('Can edit by id', async function () { - const memberToChange = { - name: 'change me', - email: 'member2Change@test.com', - note: 'initial note', - subscribed: true - }; - - const memberChanged = { - name: 'changed', - email: 'cantChangeMe@test.com', - note: 'edited note', - subscribed: false - }; - - const res = await request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [memberToChange]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - - should.exist(res.headers.location); - res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('members/')}${res.body.members[0].id}/`); - - const newMember = jsonResponse.members[0]; - - const res2 = await request - .put(localUtils.API.getApiQuery(`members/${newMember.id}/`)) - .send({members: [memberChanged]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res2.headers['x-cache-invalidate']); - - const jsonResponse2 = res2.body; - - should.exist(jsonResponse2); - should.exist(jsonResponse2.members); - jsonResponse2.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse2.members[0], 'member', ['subscriptions', 'products']); - jsonResponse2.members[0].name.should.equal(memberChanged.name); - jsonResponse2.members[0].email.should.equal(memberChanged.email); - jsonResponse2.members[0].email.should.not.equal(memberToChange.email); - jsonResponse2.members[0].note.should.equal(memberChanged.note); - jsonResponse2.members[0].subscribed.should.equal(memberChanged.subscribed); - }); - - it('Can destroy', async function () { - const member = { - name: 'test', - email: 'memberTestDestroy@test.com' - }; - - const res = await request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [member]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201); - - should.not.exist(res.headers['x-cache-invalidate']); - - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.members); - - const newMember = jsonResponse.members[0]; - - await request - .delete(localUtils.API.getApiQuery(`members/${newMember.id}`)) - .set('Origin', config.get('url')) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(204); - - await request - .get(localUtils.API.getApiQuery(`members/${newMember.id}/`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404); - }); - - it('Can export CSV', async function () { - const res = await request - .get(localUtils.API.getApiQuery(`members/upload/`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /text\/csv/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - res.headers['content-disposition'].should.match(/Attachment;\sfilename="members/); - res.text.should.match(/id,email,name,note,subscribed_to_emails,complimentary_plan,stripe_customer_id,created_at,deleted_at/); - - const csv = Papa.parse(res.text, {header: true}); - should.exist(csv.data.find(row => row.name === 'Mr Egg')); - should.exist(csv.data.find(row => row.name === 'Egon Spengler')); - should.exist(csv.data.find(row => row.name === 'Ray Stantz')); - should.exist(csv.data.find(row => row.email === 'member2@test.com')); - }); - - it('Can export a filtered CSV', async function () { - const res = await request - .get(localUtils.API.getApiQuery(`members/upload/?search=Egg`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /text\/csv/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - res.headers['content-disposition'].should.match(/Attachment;\sfilename="members/); - res.text.should.match(/id,email,name,note,subscribed_to_emails,complimentary_plan,stripe_customer_id,created_at,deleted_at/); - - const csv = Papa.parse(res.text, {header: true}); - should.exist(csv.data.find(row => row.name === 'Mr Egg')); - should.not.exist(csv.data.find(row => row.name === 'Egon Spengler')); - should.not.exist(csv.data.find(row => row.name === 'Ray Stantz')); - should.not.exist(csv.data.find(row => row.email === 'member2@test.com')); - }); - - it('Can import CSV', async function () { - const res = await request - .post(localUtils.API.getApiQuery(`members/upload/`)) - .attach('membersfile', path.join(__dirname, '/../../utils/fixtures/csv/valid-members-import.csv')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.meta); - should.exist(jsonResponse.meta.stats); - - jsonResponse.meta.stats.imported.should.equal(2); - jsonResponse.meta.stats.invalid.length.should.equal(0); - jsonResponse.meta.import_label.name.should.match(/^Import \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/); - - const importLabel = jsonResponse.meta.import_label; - - // check that members had the auto-generated label attached - const res2 = await request.get(localUtils.API.getApiQuery(`members/?filter=label:${importLabel.slug}`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - const jsonResponse2 = res2.body; - should.exist(jsonResponse2); - should.exist(jsonResponse2.members); - jsonResponse2.members.should.have.length(2); - - const importedMember1 = jsonResponse2.members.find(m => m.email === 'jbloggs@example.com'); - should.exist(importedMember1); - importedMember1.name.should.equal('joe'); - should(importedMember1.note).equal(null); - importedMember1.subscribed.should.equal(true); - importedMember1.labels.length.should.equal(1); - testUtils.API.isISO8601(importedMember1.created_at).should.be.true(); - importedMember1.comped.should.equal(false); - importedMember1.subscriptions.should.not.be.undefined(); - importedMember1.subscriptions.length.should.equal(0); - - const importedMember2 = jsonResponse2.members.find(m => m.email === 'test@example.com'); - should.exist(importedMember2); - importedMember2.name.should.equal('test'); - should(importedMember2.note).equal('test note'); - importedMember2.subscribed.should.equal(false); - importedMember2.labels.length.should.equal(2); - testUtils.API.isISO8601(importedMember2.created_at).should.be.true(); - importedMember2.created_at.should.equal('1991-10-02T20:30:31.000Z'); - importedMember2.comped.should.equal(false); - importedMember2.subscriptions.should.not.be.undefined(); - importedMember2.subscriptions.length.should.equal(0); - }); - - it('Can fetch member counts stats', async function () { - const res = await request - .get(localUtils.API.getApiQuery('members/stats/count/')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.total); - should.exist(jsonResponse.resource); - should.exist(jsonResponse.data); - const data = jsonResponse.data; - // 2 from above posts, 2 from above import - data[data.length - 1].free.should.equal(4); - data[data.length - 1].paid.should.equal(0); - // 1 from the comped test - data[data.length - 1].comped.should.equal(1); - }); - - it('Can import CSV and bulk destroy via auto-added label', function () { - // HACK: mock dates otherwise we'll often get unexpected members appearing - // from previous tests with the same import label due to auto-generated - // import labels only including minutes - sinon.stub(Date, 'now').returns(new Date('2021-03-30T17:21:00.000Z')); - - // import our dummy data for deletion - return request - .post(localUtils.API.getApiQuery(`members/upload/`)) - .attach('membersfile', path.join(__dirname, '/../../utils/fixtures/csv/valid-members-for-bulk-delete.csv')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.meta); - should.exist(jsonResponse.meta.stats); - should.exist(jsonResponse.meta.import_label); - - jsonResponse.meta.stats.imported.should.equal(8); - - return jsonResponse.meta.import_label; - }) - .then((importLabel) => { - // check that the import worked by checking browse response with filter - return request.get(localUtils.API.getApiQuery(`members/?filter=label:${importLabel.slug}`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(8); - }) - .then(() => importLabel); - }) - .then((importLabel) => { - // perform the bulk delete - return request - .del(localUtils.API.getApiQuery(`members/?filter=label:'${importLabel.slug}'`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.meta); - should.exist(jsonResponse.meta.stats); - should.exist(jsonResponse.meta.stats.successful); - should.equal(jsonResponse.meta.stats.successful, 8); - }) - .then(() => importLabel); - }) - .then((importLabel) => { - // check that the bulk delete worked by checking browse response with filter - return request.get(localUtils.API.getApiQuery(`members/?filter=label:${importLabel.slug}`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then((res) => { - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(0); - }); - }); - }); - - it('Can bulk unsubscribe members with filter', async function () { - // import our dummy data for deletion - await request - .post(localUtils.API.getApiQuery(`members/upload/`)) - .attach('membersfile', path.join(__dirname, '/../../utils/fixtures/csv/members-for-bulk-unsubscribe.csv')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private); - - const browseResponse = await request - .get(localUtils.API.getApiQuery('members/?filter=label:bulk-unsubscribe-test')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - browseResponse.body.members.should.have.length(8); - const allMembersSubscribed = browseResponse.body.members.every((member) => { - return member.subscribed; - }); - - should.ok(allMembersSubscribed); - - const bulkUnsubscribeResponse = await request - .put(localUtils.API.getApiQuery('members/bulk/?filter=label:bulk-unsubscribe-test')) - .set('Origin', config.get('url')) - .send({ - bulk: { - action: 'unsubscribe' - } - }) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.exist(bulkUnsubscribeResponse.body.bulk); - should.exist(bulkUnsubscribeResponse.body.bulk.meta); - should.exist(bulkUnsubscribeResponse.body.bulk.meta.stats); - should.exist(bulkUnsubscribeResponse.body.bulk.meta.stats.successful); - should.equal(bulkUnsubscribeResponse.body.bulk.meta.stats.successful, 8); - - const postUnsubscribeBrowseResponse = await request - .get(localUtils.API.getApiQuery('members/?filter=label:bulk-unsubscribe-test')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - postUnsubscribeBrowseResponse.body.members.should.have.length(8); - const allMembersUnsubscribed = postUnsubscribeBrowseResponse.body.members.every((member) => { - return !member.subscribed; - }); - - should.ok(allMembersUnsubscribed); - }); - - it('Can bulk add and remove labels to members with filter', async function () { - // import our dummy data for deletion - await request - .post(localUtils.API.getApiQuery('members/upload/')) - .attach('membersfile', path.join(__dirname, '/../../utils/fixtures/csv/members-for-bulk-add-labels.csv')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private); - - const newLabelResponse = await request - .post(localUtils.API.getApiQuery('labels')) - .set('Origin', config.get('url')) - .send({ - labels: [{ - name: 'Awesome Label For Testing Bulk Add' - }] - }); - - const labelToAdd = newLabelResponse.body.labels[0]; - - const bulkAddLabelResponse = await request - .put(localUtils.API.getApiQuery('members/bulk/?filter=label:bulk-add-labels-test')) - .set('Origin', config.get('url')) - .send({ - bulk: { - action: 'addLabel', - meta: { - label: labelToAdd - } - } - }) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.exist(bulkAddLabelResponse.body.bulk); - should.exist(bulkAddLabelResponse.body.bulk.meta); - should.exist(bulkAddLabelResponse.body.bulk.meta.stats); - should.exist(bulkAddLabelResponse.body.bulk.meta.stats.successful); - should.equal(bulkAddLabelResponse.body.bulk.meta.stats.successful, 8); - - const postLabelAddBrowseResponse = await request - .get(localUtils.API.getApiQuery(`members/?filter=label:${labelToAdd.slug}`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - postLabelAddBrowseResponse.body.members.should.have.length(8); - - const labelToRemove = newLabelResponse.body.labels[0]; - - const bulkRemoveLabelResponse = await request - .put(localUtils.API.getApiQuery('members/bulk/?filter=label:bulk-add-labels-test')) - .set('Origin', config.get('url')) - .send({ - bulk: { - action: 'removeLabel', - meta: { - label: labelToRemove - } - } - }) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - should.exist(bulkRemoveLabelResponse.body.bulk); - should.exist(bulkRemoveLabelResponse.body.bulk.meta); - should.exist(bulkRemoveLabelResponse.body.bulk.meta.stats); - should.exist(bulkRemoveLabelResponse.body.bulk.meta.stats.successful); - should.equal(bulkRemoveLabelResponse.body.bulk.meta.stats.successful, 8); - - const postLabelRemoveBrowseResponse = await request - .get(localUtils.API.getApiQuery(`members/?filter=label:${labelToRemove.slug}`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - postLabelRemoveBrowseResponse.body.members.should.have.length(0); - }); -}); diff --git a/test/e2e-api/admin/members-importer.test.js b/test/e2e-api/admin/members-importer.test.js index d59cd0cf75..ee4c118166 100644 --- a/test/e2e-api/admin/members-importer.test.js +++ b/test/e2e-api/admin/members-importer.test.js @@ -7,18 +7,28 @@ const localUtils = require('./utils'); const config = require('../../../core/shared/config'); const {mockManager} = require('../../utils/e2e-framework'); +const models = require('../../../core/server/models'); let request; +async function getNewsletters() { + return (await models.Newsletter.findAll({filter: 'status:active'})).models; +} + describe('Members Importer API', function () { + let newsletters; + before(async function () { await localUtils.startGhost(); request = supertest.agent(config.get('url')); - await localUtils.doAuth(request, 'members'); + await localUtils.doAuth(request, 'newsletters', 'members:newsletters'); + + newsletters = await getNewsletters(); }); beforeEach(function () { mockManager.mockLabsEnabled('multipleProducts'); + mockManager.mockLabsEnabled('multipleNewsletters'); }); afterEach(function () { @@ -26,6 +36,9 @@ describe('Members Importer API', function () { }); it('Can import CSV', async function () { + const filteredNewsletters = newsletters.filter(n => n.get('subscribe_on_signup')); + filteredNewsletters.length.should.be.greaterThan(0, 'For this test to work, we need at least one newsletter fixture with subscribe_on_signup = true'); + const res = await request .post(localUtils.API.getApiQuery(`members/upload/`)) .attach('membersfile', path.join(__dirname, '/../../utils/fixtures/csv/valid-members-import.csv')) @@ -64,6 +77,7 @@ describe('Members Importer API', function () { importedMember1.name.should.equal('joe'); should(importedMember1.note).equal(null); importedMember1.subscribed.should.equal(true); + importedMember1.newsletters.length.should.equal(filteredNewsletters.length); importedMember1.labels.length.should.equal(1); testUtils.API.isISO8601(importedMember1.created_at).should.be.true(); importedMember1.comped.should.equal(false); @@ -75,6 +89,7 @@ describe('Members Importer API', function () { importedMember2.name.should.equal('test'); should(importedMember2.note).equal('test note'); importedMember2.subscribed.should.equal(false); + importedMember2.newsletters.length.should.equal(0); importedMember2.labels.length.should.equal(2); testUtils.API.isISO8601(importedMember2.created_at).should.be.true(); importedMember2.created_at.should.equal('1991-10-02T20:30:31.000Z'); @@ -163,6 +178,9 @@ describe('Members Importer API', function () { // }); it('Can bulk unsubscribe members with filter', async function () { + const filteredNewsletters = newsletters.filter(n => n.get('subscribe_on_signup')); + filteredNewsletters.length.should.be.greaterThan(0, 'For this test to work, we need at least one newsletter fixture with subscribe_on_signup = true'); + // import our dummy data for deletion await request .post(localUtils.API.getApiQuery(`members/upload/`)) @@ -180,7 +198,7 @@ describe('Members Importer API', function () { browseResponse.body.members.should.have.length(8); const allMembersSubscribed = browseResponse.body.members.every((member) => { - return member.subscribed; + return member.subscribed && member.newsletters.length > 0; }); should.ok(allMembersSubscribed); @@ -201,7 +219,7 @@ describe('Members Importer API', function () { should.exist(bulkUnsubscribeResponse.body.bulk.meta); should.exist(bulkUnsubscribeResponse.body.bulk.meta.stats); should.exist(bulkUnsubscribeResponse.body.bulk.meta.stats.successful); - should.equal(bulkUnsubscribeResponse.body.bulk.meta.stats.successful, 8); + should.equal(bulkUnsubscribeResponse.body.bulk.meta.stats.successful, 8 * filteredNewsletters.length); const postUnsubscribeBrowseResponse = await request .get(localUtils.API.getApiQuery('members/?filter=label:bulk-unsubscribe-test')) @@ -212,7 +230,7 @@ describe('Members Importer API', function () { postUnsubscribeBrowseResponse.body.members.should.have.length(8); const allMembersUnsubscribed = postUnsubscribeBrowseResponse.body.members.every((member) => { - return !member.subscribed; + return member.newsletters.length === 0; }); should.ok(allMembersUnsubscribed); diff --git a/yarn.lock b/yarn.lock index d739772181..5412409460 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1892,7 +1892,12 @@ "@tryghost/root-utils" "^0.3.14" debug "^4.3.1" -"@tryghost/domain-events@0.1.9", "@tryghost/domain-events@^0.1.9": +"@tryghost/domain-events@0.1.10", "@tryghost/domain-events@^0.1.10": + version "0.1.10" + resolved "https://registry.yarnpkg.com/@tryghost/domain-events/-/domain-events-0.1.10.tgz#1a657c7964b928ac63e7ada87902d6fa5d4f5a7a" + integrity sha512-C6xqg8VC5KgfgZ0X4zMUcoRBEDk7DS8xSepbIfZx61UZHbJRUMfbZ+AklGdnNQmQFxBc3eUCLAOByF6k1HXvTQ== + +"@tryghost/domain-events@^0.1.9": version "0.1.9" resolved "https://registry.yarnpkg.com/@tryghost/domain-events/-/domain-events-0.1.9.tgz#f2de5189df2238bb72a53a76abcded0d84ae6fa7" integrity sha512-n2FEA5xBQBCHq02CNBMh8BkWV7zdy9yp/Y4dGWc2QQ58lcNTgSBqZBaPpfPURgTGJbLqO+xsNQgU0LYvUN9JTQ== @@ -1957,10 +1962,10 @@ utils-copy-error "^1.0.1" uuid "^8.3.2" -"@tryghost/express-dynamic-redirects@0.2.8": - version "0.2.8" - resolved "https://registry.yarnpkg.com/@tryghost/express-dynamic-redirects/-/express-dynamic-redirects-0.2.8.tgz#67b4f19c6210734e0226e6c8f18c0ad7ce2a75a2" - integrity sha512-kg1vDQ0E8hVJTwu3OYebYdI1KD3wwTwTfRv1dXEA7r8JSMdJw/LdlxJ39WBiQObmCuHYu2nsnTf3/Ccah7Ew+w== +"@tryghost/express-dynamic-redirects@0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@tryghost/express-dynamic-redirects/-/express-dynamic-redirects-0.2.9.tgz#62094c67bce0c5e6918a292e8a7e0d8d4c72aa16" + integrity sha512-BguBNvq+E/wEiJ+FJv5C5g0Q9Xj4PZEwy5Zs7GWkqUFGiDB3Evfpsj02MucNyuC1ysxz1FwpEyr2NRxk65ZkQg== "@tryghost/express-test@0.10.0": version "0.10.0" @@ -2117,55 +2122,60 @@ json-stringify-safe "^5.0.1" lodash "^4.17.21" -"@tryghost/magic-link@1.0.21", "@tryghost/magic-link@^1.0.21": - version "1.0.21" - resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-1.0.21.tgz#6b1fbcd007d5ba64bd2b0d47a1ace6c1d50658f2" - integrity sha512-d57N8356H0w4in3Zb+IpzgnOO1IkYW+D/agZPjilyQcB11Hj+pJq8xhw2S0Ra4wSrzO2pbTBxEC/yceufiryEg== +"@tryghost/magic-link@1.0.22", "@tryghost/magic-link@^1.0.22": + version "1.0.22" + resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-1.0.22.tgz#1bfa1fa93efb0d493f242ed400e8b663fc0c7e60" + integrity sha512-d+uut21jb3iW6x7yqMvNcd7nBT5Y2VMTu4pFVA+eXkFQSQxFY+AD3qSeJxkSXp5KxFpKOZRRJycrG2Ppsh8TaA== dependencies: bluebird "^3.5.5" jsonwebtoken "^8.5.1" lodash "^4.17.15" -"@tryghost/member-analytics-service@^0.1.11": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@tryghost/member-analytics-service/-/member-analytics-service-0.1.11.tgz#99da7eb47678aa0770ac2aef7c99fdd3e5a648b9" - integrity sha512-IdddDTbpHp7hLJWIELCBrgkGDr6duZsYK7HPkjW51L/5cqj3I0XjLCbjQavF4hXFhVZ3hekiHq61v7agWPRCXA== +"@tryghost/member-analytics-service@^0.1.12": + version "0.1.12" + resolved "https://registry.yarnpkg.com/@tryghost/member-analytics-service/-/member-analytics-service-0.1.12.tgz#dbf3ef1ee7e7732ca9e162a0b10608207ebf9032" + integrity sha512-Mat32Q1rIo1N5WvjzJsyterBzVhEkSkAbsCa8nXOPsmCk/rRmOY9aO/KD6B6wQB84whMgK0Y2+QpYQk2lLmVtQ== dependencies: - "@tryghost/domain-events" "^0.1.9" + "@tryghost/domain-events" "^0.1.10" "@tryghost/errors" "^1.0.0" - "@tryghost/member-events" "^0.4.1" + "@tryghost/member-events" "^0.4.2" "@tryghost/tpl" "^0.1.4" bson-objectid "^2.0.1" -"@tryghost/member-events@0.4.1", "@tryghost/member-events@^0.4.1": +"@tryghost/member-events@0.4.2", "@tryghost/member-events@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@tryghost/member-events/-/member-events-0.4.2.tgz#d1266abad38aa0b8f9b9f3395b083971e85221bf" + integrity sha512-4VlGlB9uq3xxgeW3OXx9//Yb1kiRm7HbVVGiBJ9LMMjeoKZnpUQA9zHyY5FUK4Aolvz/wyxjb4Ci7JNjvoMrJg== + +"@tryghost/member-events@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@tryghost/member-events/-/member-events-0.4.1.tgz#d75c183e29c1d186bc8a584a1f05b6e7c05c23b0" integrity sha512-eYsDWrsLbdX5alZI9hsxsswN0XnlkOH6AvOmzeaX17pxDqzAhG7DN9rs0lDH3Ov3aniRfHc2UukcQ65oJbvA1A== -"@tryghost/members-analytics-ingress@^0.1.12": - version "0.1.12" - resolved "https://registry.yarnpkg.com/@tryghost/members-analytics-ingress/-/members-analytics-ingress-0.1.12.tgz#517bb68636cd093ec1fa725fc0ac9605efce4a1e" - integrity sha512-ljf5/7QwnHZDpF2aAQLneML7ytk8TELYOwLvX+ye6FLhqRHNzEeas1baFgpfJl+Iyf3u4ZQU9NWnoj09yFgC3A== +"@tryghost/members-analytics-ingress@^0.1.13": + version "0.1.13" + resolved "https://registry.yarnpkg.com/@tryghost/members-analytics-ingress/-/members-analytics-ingress-0.1.13.tgz#6ab72155aea2defe44b88c79520d645f6dd54c78" + integrity sha512-XaEll3ea0yXJofdiP1a2I6GWnc+kPcoMe8lWZ3r8qOVtfD7Yynkd3sL7JBC9I3RRihCQYxmKka7+AOTh69V+XA== dependencies: - "@tryghost/domain-events" "^0.1.9" - "@tryghost/member-events" "^0.4.1" + "@tryghost/domain-events" "^0.1.10" + "@tryghost/member-events" "^0.4.2" -"@tryghost/members-api@6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-6.1.0.tgz#60ee22f62ea0def89815a3563243d9e1840e8a09" - integrity sha512-XnfO59JrtN4PySwlfdygnXg6o7+4nVwwSHvhCXBlUEgrszQhT3mQXMSyGvRM4gHg3zOhKOVvzYWPh5Nboos/nA== +"@tryghost/members-api@6.2.2": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-6.2.2.tgz#ea888882edde3ccce811a481aca11b5bb8e86ea6" + integrity sha512-fDlrNkTm1DMH7fDlxEZQxjQSx+HJzw7g188hN948l52QdxB8sSdBcKl5+0e9WnOvV6EvbVTAq4pvm+94CsIE8w== dependencies: "@nexes/nql" "^0.6.0" "@tryghost/debug" "^0.1.2" - "@tryghost/domain-events" "^0.1.9" + "@tryghost/domain-events" "^0.1.10" "@tryghost/errors" "^1.1.1" "@tryghost/logging" "^2.0.0" - "@tryghost/magic-link" "^1.0.21" - "@tryghost/member-analytics-service" "^0.1.11" - "@tryghost/member-events" "^0.4.1" - "@tryghost/members-analytics-ingress" "^0.1.12" - "@tryghost/members-payments" "^0.3.1" - "@tryghost/members-stripe-service" "^0.10.0" + "@tryghost/magic-link" "^1.0.22" + "@tryghost/member-analytics-service" "^0.1.12" + "@tryghost/member-events" "^0.4.2" + "@tryghost/members-analytics-ingress" "^0.1.13" + "@tryghost/members-payments" "^0.3.2" + "@tryghost/members-stripe-service" "^0.10.1" "@tryghost/tpl" "^0.1.2" "@types/jsonwebtoken" "^8.5.1" bluebird "^3.5.4" @@ -2178,10 +2188,10 @@ lodash "^4.17.11" node-jose "^2.0.0" -"@tryghost/members-csv@^1.2.10": - version "1.2.10" - resolved "https://registry.yarnpkg.com/@tryghost/members-csv/-/members-csv-1.2.10.tgz#b69b41bc6eaacb0f6b53f959cdf8dc8ec5d156b3" - integrity sha512-D7OVxkcOQ7/bD0f/0XGd4yXEc8+R8HVfxzRckov7DERXNlfa0EBdXMCijWKwfIbGYC9BJLZxykpaCtE7krN5Ww== +"@tryghost/members-csv@^1.2.11": + version "1.2.11" + resolved "https://registry.yarnpkg.com/@tryghost/members-csv/-/members-csv-1.2.11.tgz#dd2eaec417a2671786e8b827fa1dd64f4edd2a4c" + integrity sha512-qxbcqnJSja3L/5UOlBIsGtySyHvpYNyM+qmv8EWiLSBcrE2cPorhlwZZI9BUM4ZW2FQ90cEihgADoueNdniX0Q== dependencies: bluebird "^3.7.2" fs-extra "^10.0.0" @@ -2198,36 +2208,36 @@ "@tryghost/member-events" "^0.4.1" moment-timezone "^0.5.34" -"@tryghost/members-importer@0.5.8": - version "0.5.8" - resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.5.8.tgz#afb9aa0ff8d1aa113591116d1a82dbe13ab13b58" - integrity sha512-AKT4G8IOpQZJxol6wozkpDtf9HVb1kvncMEZuk4nq7V92VHzOP6M88UJ98GUwtmDkAvLZ5+uilkkiVIFuRZVoA== +"@tryghost/members-importer@0.5.9": + version "0.5.9" + resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.5.9.tgz#c2cca9b414a252f088d76ad01d2b9643f16176ec" + integrity sha512-wNmBnizOUIqmfn6LCuBQJW+A99UyDv63jW9Juu2rEKkmRQo6VOZemcCGgKGZP2yFYB3PthckMoAfteBkUNyfLw== dependencies: "@tryghost/errors" "^1.0.0" - "@tryghost/members-csv" "^1.2.10" + "@tryghost/members-csv" "^1.2.11" "@tryghost/tpl" "^0.1.3" moment-timezone "^0.5.23" -"@tryghost/members-offers@0.11.1", "@tryghost/members-offers@^0.11.1": - version "0.11.1" - resolved "https://registry.yarnpkg.com/@tryghost/members-offers/-/members-offers-0.11.1.tgz#f9f4704b187aabaa54b641cc954415c50cf0165d" - integrity sha512-cA4/AVvL6SH4ATvud75u/S1UiBtVZa0/0V4gX5V/Ncq11UMXb/kZV20cRSIF/5iuaBtrfrCfaUvpXQfXZylRXQ== +"@tryghost/members-offers@0.11.2", "@tryghost/members-offers@^0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@tryghost/members-offers/-/members-offers-0.11.2.tgz#8c53344055fbd8dbec203f166ce74a12e0085aec" + integrity sha512-sAhrHm3ShFtf+5Tz8MfALriDPgdgKISRFXZ9j4bldu5uZrjwSJMWJwH3z/wtwdWix9yL1mNxH9EE+hjPMZzAzg== dependencies: "@nexes/mongo-utils" "^0.3.1" "@tryghost/string" "^0.1.20" -"@tryghost/members-payments@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@tryghost/members-payments/-/members-payments-0.3.1.tgz#cd52c87fcb02fbe86dbda9c843a5946f9dad40e0" - integrity sha512-12aTBMHb9JcxC6rsGSeIQnwsEDNVpCtcp+ail9W7ghcBjbLTqf6menWq++DavsohFfzStwI3D5reKstA2M68+A== +"@tryghost/members-payments@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@tryghost/members-payments/-/members-payments-0.3.2.tgz#f48e5a61a726d3a0f233925ecc1eb0643172e539" + integrity sha512-qbxw9oix49+iFzHHtO5M2dUcde9AApc3FWR0SFCIZdi08cQnQnqyEzg2T6QtQHH+k8/5WMZBewHzHd2tXlGfBA== dependencies: - "@tryghost/domain-events" "^0.1.9" - "@tryghost/members-offers" "^0.11.1" + "@tryghost/domain-events" "^0.1.10" + "@tryghost/members-offers" "^0.11.2" -"@tryghost/members-ssr@1.0.23": - version "1.0.23" - resolved "https://registry.yarnpkg.com/@tryghost/members-ssr/-/members-ssr-1.0.23.tgz#7e42d0db36a564437a20f7d0419083204eafac9a" - integrity sha512-yowIa1l/LnEAG7C7szV987hRekJwlZMt77JjOvCnHH94aQnCjr2KGKuSy68oBG2eKlUmXO4Iaest0iuEpWEcSQ== +"@tryghost/members-ssr@1.0.24": + version "1.0.24" + resolved "https://registry.yarnpkg.com/@tryghost/members-ssr/-/members-ssr-1.0.24.tgz#12c2d8af366754a1eb3e71f7ffb17e3859526d7c" + integrity sha512-8LBkE1uj/kRkoY9C+hrVsa2gk4bfkuUI+bOfsF9XQvWjTOQlbY/KFoEN5twxhiw90zFRMqR8bWzfL4EEaaILVg== dependencies: "@tryghost/debug" "^0.1.2" "@tryghost/errors" "^1.1.0" @@ -2237,16 +2247,16 @@ jsonwebtoken "^8.5.1" lodash "^4.17.11" -"@tryghost/members-stripe-service@0.10.0", "@tryghost/members-stripe-service@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@tryghost/members-stripe-service/-/members-stripe-service-0.10.0.tgz#e63b672fbb9f30a34ee2be7feb1452a16ae44734" - integrity sha512-ViXwAORs5RF15Q+17zUJw16khppnTF3UtcRgiNX8NXNozFYuIHGLd3A9nKbBgZyPGxEOcBF8u/qBZ1apjEVT2g== +"@tryghost/members-stripe-service@0.10.1", "@tryghost/members-stripe-service@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tryghost/members-stripe-service/-/members-stripe-service-0.10.1.tgz#2877ccb7f2a318f0fcc24a4e6a4d30d5116de5ce" + integrity sha512-bbtyLAMDivwFA10KkuxsTwBzCSboyu1f/vqbmolSpaHxSDg8qxIr8fAeJFZWByVrJKfUD0e3IgMRYJl1jW0Ogw== dependencies: "@tryghost/debug" "^0.1.4" - "@tryghost/domain-events" "^0.1.9" + "@tryghost/domain-events" "^0.1.10" "@tryghost/errors" "^1.2.5" "@tryghost/logging" "^2.0.5" - "@tryghost/member-events" "^0.4.1" + "@tryghost/member-events" "^0.4.2" leaky-bucket "^2.2.0" lodash "^4.17.21" stripe "^8.174.0" @@ -2504,13 +2514,13 @@ moment-timezone "^0.5.23" validator "7.2.0" -"@tryghost/verification-trigger@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@tryghost/verification-trigger/-/verification-trigger-0.2.0.tgz#cf9d22ea59be6fca8f8a54464663d8177e063e37" - integrity sha512-qsz7IWJ+LbKmSD6JeEkoa/ZCRMFjwUHwIfRWp2mTNf7WW+dA13T+4VsE14lxRtOsMnD9A12PqWIUQB6H6ef2UQ== +"@tryghost/verification-trigger@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@tryghost/verification-trigger/-/verification-trigger-0.2.1.tgz#8b371fee47dec57bfeb8ab61a4698aaae4d17974" + integrity sha512-ox47qz+aiUC70dy2oOqpuZ1AGHLawFB1DDE76US4vWVAEV0K8Ny+k/vR8Qtt5r8lVN+G8tFpsf8NtELFE2npdw== dependencies: - "@tryghost/domain-events" "^0.1.9" - "@tryghost/member-events" "^0.4.1" + "@tryghost/domain-events" "^0.1.10" + "@tryghost/member-events" "^0.4.2" "@tryghost/version-notifications-data-service@0.1.0": version "0.1.0"