2022-01-27 13:57:51 +03:00
const errors = require ( '@tryghost/errors' ) ;
2022-02-03 18:02:33 +03:00
const DomainEvents = require ( '@tryghost/domain-events' ) ;
const { MemberSubscribeEvent } = require ( '@tryghost/member-events' ) ;
2022-01-27 13:57:51 +03:00
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 ` ,
2022-04-13 19:35:24 +03:00
emailVerificationEmailMessageImport : ` Email verification needed for site: {siteUrl}, has imported: {importedNumber} members in the last 30 days. ` ,
2022-02-03 18:03:47 +03:00
emailVerificationEmailMessageAPI : ` Email verification needed for site: {siteUrl} has added: {importedNumber} members through the API in the last 30 days. `
2022-01-27 13:57:51 +03:00
} ;
class VerificationTrigger {
/ * *
2022-02-04 14:54:48 +03:00
*
2022-01-27 13:57:51 +03:00
* @ param { object } deps
* @ param { number } deps . configThreshold Threshold for triggering verification as defined in config
* @ param { ( ) => boolean } deps . isVerified Check Ghost config to see if we are already verified
* @ param { ( ) => boolean } deps . isVerificationRequired Check Ghost settings to see whether verification has been requested
* @ param { ( content : { subject : string , message : string , amountImported : number } ) => { } } deps . sendVerificationEmail Sends an email to the escalation address to confirm that customer needs to be verified
* @ param { any } deps . membersStats MemberStats service
* @ param { any } deps . Settings Ghost Settings model
* @ param { any } deps . eventRepository For querying events
* /
constructor ( {
configThreshold ,
isVerified ,
isVerificationRequired ,
sendVerificationEmail ,
membersStats ,
Settings ,
eventRepository
} ) {
this . _configThreshold = configThreshold ;
this . _isVerified = isVerified ;
this . _isVerificationRequired = isVerificationRequired ;
this . _sendVerificationEmail = sendVerificationEmail ;
this . _membersStats = membersStats ;
this . _Settings = Settings ;
this . _eventRepository = eventRepository ;
2022-02-03 18:02:33 +03:00
2022-08-24 09:06:05 +03:00
this . _handleMemberSubscribeEvent = this . _handleMemberSubscribeEvent . bind ( this ) ;
DomainEvents . subscribe ( MemberSubscribeEvent , this . _handleMemberSubscribeEvent ) ;
}
2022-02-03 18:02:33 +03:00
2022-08-24 09:06:05 +03:00
async _handleMemberSubscribeEvent ( event ) {
2022-08-24 09:23:32 +03:00
const source = event . data ? . source ;
const sourceThreshold = this . _configThreshold ;
if ( source === 'api' && isFinite ( sourceThreshold ) ) {
2022-08-24 09:06:05 +03:00
const createdAt = new Date ( ) ;
createdAt . setDate ( createdAt . getDate ( ) - 30 ) ;
const events = await this . _eventRepository . getNewsletterSubscriptionEvents ( { } , {
2022-08-24 09:23:32 +03:00
'data.source' : ` data.source:' ${ source } ' ` ,
2022-08-24 09:06:05 +03:00
'data.created_at' : ` data.created_at:>' ${ createdAt . toISOString ( ) . replace ( 'T' , ' ' ) . substring ( 0 , 19 ) } ' `
} ) ;
2022-08-24 09:23:32 +03:00
if ( events . meta . pagination . total > sourceThreshold ) {
2022-08-24 09:06:05 +03:00
await this . startVerificationProcess ( {
amountImported : events . meta . pagination . total ,
throwOnTrigger : false ,
2022-08-24 09:23:32 +03:00
source : source
2022-08-24 09:06:05 +03:00
} ) ;
2022-02-03 18:02:33 +03:00
}
2022-08-24 09:06:05 +03:00
}
2022-01-27 13:57:51 +03:00
}
async getImportThreshold ( ) {
const volumeThreshold = this . _configThreshold ;
if ( isFinite ( volumeThreshold ) ) {
const membersTotal = await this . _membersStats . getTotalMembers ( ) ;
return Math . max ( membersTotal , volumeThreshold ) ;
} else {
return volumeThreshold ;
}
}
2022-04-13 19:35:24 +03:00
async testImportThreshold ( ) {
2022-07-26 12:35:48 +03:00
if ( ! isFinite ( this . _configThreshold ) ) {
// Infinite threshold, quick path
return ;
}
2022-04-13 19:35:24 +03:00
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 ) } ' `
} ) ;
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'
} ) ;
}
}
2022-02-04 14:54:48 +03:00
/ * *
* @ typedef IVerificationResult
2022-01-27 13:57:51 +03:00
* @ property { boolean } needsVerification Whether the verification workflow was triggered
* /
/ * *
2022-02-04 14:54:48 +03:00
*
2022-01-27 13:57:51 +03:00
* @ param { object } config
* @ param { number } config . amountImported Amount of members which were imported
* @ param { boolean } config . throwOnTrigger Whether to throw if verification is needed
2022-02-03 18:03:47 +03:00
* @ param { string } config . source Source of the verification trigger - currently either 'api' or 'import'
2022-02-04 14:54:48 +03:00
* @ returns { Promise < IVerificationResult > } Object containing property "needsVerification" - true when triggered
2022-01-27 13:57:51 +03:00
* /
async startVerificationProcess ( {
amountImported ,
2022-02-03 18:03:47 +03:00
throwOnTrigger ,
source
2022-01-27 13:57:51 +03:00
} ) {
if ( ! this . _isVerified ( ) ) {
// Only trigger flag change and escalation email the first time
if ( ! this . _isVerificationRequired ( ) ) {
await this . _Settings . edit ( [ {
key : 'email_verification_required' ,
value : true
} ] , { context : { internal : true } } ) ;
this . _sendVerificationEmail ( {
2022-02-03 18:03:47 +03:00
message : source === 'api'
? messages . emailVerificationEmailMessageAPI
: messages . emailVerificationEmailMessageImport ,
2022-01-27 13:57:51 +03:00
subject : messages . emailVerificationEmailSubject ,
amountImported
} ) ;
if ( throwOnTrigger ) {
throw new errors . ValidationError ( {
message : messages . emailVerificationNeeded
} ) ;
}
return {
needsVerification : true
} ;
}
}
return {
needsVerification : false
} ;
}
}
2022-02-04 14:54:48 +03:00
module . exports = VerificationTrigger ;