mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 03:14:03 +03:00
Email verification for imports based on 30 days of import
refs: https://github.com/TryGhost/Toolbox/issues/293 Things needed to create this: * MemberSubscriptionEvent now has an import source * Importer now creates events with this type * Verification trigger logic changed to use 30 day window of imports
This commit is contained in:
parent
b76d850620
commit
455778662c
@ -220,7 +220,9 @@ module.exports = class MemberRepository {
|
||||
const context = options && options.context || {};
|
||||
let source;
|
||||
|
||||
if (context.internal) {
|
||||
if (context.import) {
|
||||
source = 'import';
|
||||
} else if (context.internal) {
|
||||
source = 'system';
|
||||
} else if (context.user) {
|
||||
source = 'admin';
|
||||
|
@ -153,7 +153,11 @@ module.exports = class MembersCSVImporter {
|
||||
id: existingMember.id
|
||||
});
|
||||
} else {
|
||||
member = await membersApi.members.create(row, options);
|
||||
member = await membersApi.members.create(row, Object.assign({}, options, {
|
||||
context: {
|
||||
import: true
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (row.stripe_customer_id) {
|
||||
|
@ -32,6 +32,7 @@ describe('Importer', function () {
|
||||
id: 'default_product_id'
|
||||
};
|
||||
|
||||
const memberCreateStub = sinon.stub().resolves(null);
|
||||
const membersApi = {
|
||||
productRepository: {
|
||||
list: async () => {
|
||||
@ -44,9 +45,7 @@ describe('Importer', function () {
|
||||
get: async () => {
|
||||
return null;
|
||||
},
|
||||
create: async (row) => {
|
||||
return row;
|
||||
}
|
||||
create: memberCreateStub
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,6 +95,10 @@ describe('Importer', function () {
|
||||
result.meta.originalImportSize.should.equal(2);
|
||||
|
||||
fsWriteSpy.calledOnce.should.be.true();
|
||||
|
||||
// Called at least once
|
||||
memberCreateStub.notCalled.should.be.false();
|
||||
memberCreateStub.firstCall.lastArg.context.import.should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -5,7 +5,7 @@ const {MemberSubscribeEvent} = require('@tryghost/member-events');
|
||||
const messages = {
|
||||
emailVerificationNeeded: `We're hard at work processing your import. To make sure you get great deliverability on a list of that size, we'll need to enable some extra features for your account. A member of our team will be in touch with you by email to review your account make sure everything is configured correctly so you're ready to go.`,
|
||||
emailVerificationEmailSubject: `Email needs verification`,
|
||||
emailVerificationEmailMessageImport: `Email verification needed for site: {siteUrl}, just imported: {importedNumber} members.`,
|
||||
emailVerificationEmailMessageImport: `Email verification needed for site: {siteUrl}, has imported: {importedNumber} members in the last 30 days.`,
|
||||
emailVerificationEmailMessageAPI: `Email verification needed for site: {siteUrl} has added: {importedNumber} members through the API in the last 30 days.`
|
||||
};
|
||||
|
||||
@ -68,6 +68,33 @@ class VerificationTrigger {
|
||||
}
|
||||
}
|
||||
|
||||
async testImportThreshold() {
|
||||
const createdAt = new Date();
|
||||
createdAt.setDate(createdAt.getDate() - 30);
|
||||
const events = await this._eventRepository.getNewsletterSubscriptionEvents({}, {
|
||||
'data.source': `data.source:'import'`,
|
||||
'data.created_at': `data.created_at:>'${createdAt.toISOString().replace('T', ' ').substring(0, 19)}'`
|
||||
});
|
||||
|
||||
if (!isFinite(this._configThreshold)) {
|
||||
// Inifinte threshold, quick path
|
||||
return;
|
||||
}
|
||||
|
||||
const membersTotal = await this._membersStats.getTotalMembers();
|
||||
|
||||
// Import threshold is either the total number of members (discounting any created by imports in
|
||||
// the last 30 days) or the threshold defined in config, whichever is greater.
|
||||
const importThreshold = Math.max(membersTotal - events.meta.pagination.total, this._configThreshold);
|
||||
if (isFinite(importThreshold) && events.meta.pagination.total > importThreshold) {
|
||||
await this.startVerificationProcess({
|
||||
amountImported: events.meta.pagination.total,
|
||||
throwOnTrigger: false,
|
||||
source: 'import'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef IVerificationResult
|
||||
* @property {boolean} needsVerification Whether the verification workflow was triggered
|
||||
|
@ -2,7 +2,7 @@
|
||||
// const testUtils = require('./utils');
|
||||
const sinon = require('sinon');
|
||||
require('./utils');
|
||||
const VerificationTrigger = require('../lib/verification-trigger');
|
||||
const VerificationTrigger = require('../index');
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const {MemberSubscribeEvent} = require('@tryghost/member-events');
|
||||
|
||||
@ -150,7 +150,7 @@ describe('Email verification flow', function () {
|
||||
|
||||
emailStub.lastCall.firstArg.should.eql({
|
||||
subject: 'Email needs verification',
|
||||
message: 'Email verification needed for site: {siteUrl}, just imported: {importedNumber} members.',
|
||||
message: 'Email verification needed for site: {siteUrl}, has imported: {importedNumber} members in the last 30 days.',
|
||||
amountImported: 10
|
||||
});
|
||||
});
|
||||
@ -190,4 +190,47 @@ describe('Email verification flow', function () {
|
||||
eventStub.lastCall.lastArg['data.source'].should.eql(`data.source:'api'`);
|
||||
eventStub.lastCall.lastArg['data.created_at'].should.startWith(`data.created_at:>'`);
|
||||
});
|
||||
|
||||
it('Triggers when a number of members are imported', async function () {
|
||||
const emailStub = sinon.stub().resolves(null);
|
||||
const settingsStub = sinon.stub().resolves(null);
|
||||
const eventStub = sinon.stub().resolves({
|
||||
meta: {
|
||||
pagination: {
|
||||
total: 10
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const trigger = new VerificationTrigger({
|
||||
configThreshold: 2,
|
||||
Settings: {
|
||||
edit: settingsStub
|
||||
},
|
||||
membersStats: {
|
||||
getTotalMembers: () => 15
|
||||
},
|
||||
isVerified: () => false,
|
||||
isVerificationRequired: () => false,
|
||||
sendVerificationEmail: emailStub,
|
||||
eventRepository: {
|
||||
getNewsletterSubscriptionEvents: eventStub
|
||||
}
|
||||
});
|
||||
|
||||
await trigger.testImportThreshold();
|
||||
|
||||
eventStub.callCount.should.eql(1);
|
||||
eventStub.lastCall.lastArg.should.have.property('data.source');
|
||||
eventStub.lastCall.lastArg.should.have.property('data.created_at');
|
||||
eventStub.lastCall.lastArg['data.source'].should.eql(`data.source:'admin'`);
|
||||
eventStub.lastCall.lastArg['data.created_at'].should.startWith(`data.created_at:>'`);
|
||||
|
||||
emailStub.callCount.should.eql(1);
|
||||
emailStub.lastCall.firstArg.should.eql({
|
||||
subject: 'Email needs verification',
|
||||
message: 'Email verification needed for site: {siteUrl}, has imported: {importedNumber} members in the last 30 days.',
|
||||
amountImported: 10
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user