2021-07-19 13:46:38 +03:00
const errors = require ( '@tryghost/errors' ) ;
const tpl = require ( '@tryghost/tpl' ) ;
2021-05-12 15:02:27 +03:00
const MembersSSR = require ( '@tryghost/members-ssr' ) ;
const db = require ( '../../data/db' ) ;
const MembersConfigProvider = require ( './config' ) ;
2021-07-21 18:34:11 +03:00
const MembersCSVImporter = require ( '@tryghost/members-importer' ) ;
2021-08-18 10:48:07 +03:00
const MembersStats = require ( './stats/members-stats' ) ;
2021-05-12 15:02:27 +03:00
const createMembersSettingsInstance = require ( './settings' ) ;
2021-06-15 17:36:27 +03:00
const logging = require ( '@tryghost/logging' ) ;
2021-05-12 15:02:27 +03:00
const urlUtils = require ( '../../../shared/url-utils' ) ;
2021-07-20 17:42:57 +03:00
const labsService = require ( '../../../shared/labs' ) ;
2021-06-30 16:56:57 +03:00
const settingsCache = require ( '../../../shared/settings-cache' ) ;
2021-05-12 15:02:27 +03:00
const config = require ( '../../../shared/config' ) ;
2021-07-27 12:27:59 +03:00
const models = require ( '../../models' ) ;
2021-05-12 15:02:27 +03:00
const _ = require ( 'lodash' ) ;
2021-07-20 17:42:26 +03:00
const { GhostMailer } = require ( '../mail' ) ;
2021-07-20 18:21:59 +03:00
const jobsService = require ( '../jobs' ) ;
2021-05-12 15:02:27 +03:00
2021-07-19 13:46:38 +03:00
const messages = {
noLiveKeysInDevelopment : 'Cannot use live stripe keys in development. Please restart in production mode.' ,
sslRequiredForStripe : 'Cannot run Ghost without SSL when Stripe is connected. Please update your url config to use "https://".' ,
2021-07-23 19:37:29 +03:00
remoteWebhooksInDevelopment : 'Cannot use remote webhooks in development. See https://ghost.org/docs/webhooks/#stripe-webhooks for developing with Stripe.' ,
2021-08-20 17:07:20 +03:00
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. ` ,
2021-07-28 18:28:13 +03:00
emailVerificationEmailMessage : ` Email verification needed for site: {siteUrl}, just imported: {importedNumber} members. `
2021-07-19 13:46:38 +03:00
} ;
2021-07-20 17:42:26 +03:00
const ghostMailer = new GhostMailer ( ) ;
2021-05-12 15:02:27 +03:00
const membersConfig = new MembersConfigProvider ( {
config ,
settingsCache ,
2021-12-14 18:18:46 +03:00
urlUtils
2021-05-12 15:02:27 +03:00
} ) ;
let membersApi ;
let membersSettings ;
2021-08-18 17:39:43 +03:00
/ * *
* @ description Calculates threshold based on following formula
* Threshold = max { [ current number of members ] , [ volume threshold ] }
*
* @ returns { Promise < number > }
* /
const fetchImportThreshold = async ( ) => {
2022-01-18 18:56:47 +03:00
const membersTotal = await module . exports . stats . getTotalMembers ( ) ;
2021-10-22 16:28:50 +03:00
const configThreshold = _ . get ( config . get ( 'hostSettings' ) , 'emailVerification.importThreshold' ) ;
const volumeThreshold = ( configThreshold === undefined ) ? Infinity : configThreshold ;
2021-08-18 17:39:43 +03:00
const threshold = Math . max ( membersTotal , volumeThreshold ) ;
return threshold ;
2021-07-23 19:37:29 +03:00
} ;
2021-07-23 15:58:35 +03:00
const membersImporter = new MembersCSVImporter ( {
storagePath : config . getContentPath ( 'data' ) ,
getTimezone : ( ) => settingsCache . get ( 'timezone' ) ,
2022-01-18 18:56:47 +03:00
getMembersApi : ( ) => module . exports . api ,
2021-07-23 15:58:35 +03:00
sendEmail : ghostMailer . send . bind ( ghostMailer ) ,
isSet : labsService . isSet . bind ( labsService ) ,
addJob : jobsService . addJob . bind ( jobsService ) ,
knex : db . knex ,
urlFor : urlUtils . urlFor . bind ( urlUtils ) ,
2021-08-18 17:39:43 +03:00
fetchThreshold : fetchImportThreshold
2021-07-23 15:58:35 +03:00
} ) ;
2021-07-28 18:28:13 +03:00
const startEmailVerification = async ( importedNumber ) => {
2021-07-27 14:44:08 +03:00
const isVerifiedEmail = config . get ( 'hostSettings:emailVerification:verified' ) === true ;
2021-07-28 15:36:20 +03:00
if ( ( ! isVerifiedEmail ) ) {
2021-07-28 18:28:13 +03:00
// Only trigger flag change and escalation email the first time
if ( settingsCache . get ( 'email_verification_required' ) !== true ) {
await models . Settings . edit ( [ {
key : 'email_verification_required' ,
value : true
} ] , { context : { internal : true } } ) ;
const escalationAddress = config . get ( 'hostSettings:emailVerification:escalationAddress' ) ;
2021-08-12 13:00:33 +03:00
const fromAddress = config . get ( 'user_email' ) ;
2021-07-28 18:28:13 +03:00
if ( escalationAddress ) {
ghostMailer . send ( {
subject : 'Email needs verification' ,
html : tpl ( messages . emailVerificationEmailMessage , {
importedNumber ,
siteUrl : urlUtils . getSiteUrl ( )
} ) ,
forceTextContent : true ,
2021-08-12 13:00:33 +03:00
from : fromAddress ,
2021-07-28 18:28:13 +03:00
to : escalationAddress
} ) ;
}
}
2021-07-27 12:27:59 +03:00
2021-07-23 19:37:29 +03:00
throw new errors . ValidationError ( {
message : tpl ( messages . emailVerificationNeeded )
} ) ;
}
2021-07-28 15:36:20 +03:00
} ;
const processImport = async ( options ) => {
const result = await membersImporter . process ( options ) ;
const freezeTriggered = result . meta . freeze ;
2021-07-28 18:28:13 +03:00
const importSize = result . meta . originalImportSize ;
2021-07-28 15:36:20 +03:00
delete result . meta . freeze ;
2021-07-28 18:28:13 +03:00
delete result . meta . originalImportSize ;
2021-07-28 15:36:20 +03:00
if ( freezeTriggered ) {
2021-07-28 18:28:13 +03:00
await startEmailVerification ( importSize ) ;
2021-07-28 15:36:20 +03:00
}
2021-07-23 19:37:29 +03:00
return result ;
2021-07-23 15:58:35 +03:00
} ;
2021-05-12 15:02:27 +03:00
2022-01-18 18:56:47 +03:00
module . exports = {
2021-05-12 15:46:05 +03:00
async init ( ) {
2022-01-18 18:56:47 +03:00
const stripeService = require ( '../stripe' ) ;
const createMembersApiInstance = require ( './api' ) ;
2021-05-12 15:46:05 +03:00
const env = config . get ( 'env' ) ;
2022-01-18 18:56:47 +03:00
// @TODO Move to stripe service
2021-05-12 15:46:05 +03:00
if ( env !== 'production' ) {
2021-10-04 14:18:22 +03:00
if ( stripeService . api . configured && stripeService . api . mode === 'live' ) {
2021-12-01 14:22:14 +03:00
throw new errors . IncorrectUsageError ( {
message : tpl ( messages . noLiveKeysInDevelopment )
} ) ;
2021-05-12 15:46:05 +03:00
}
} else {
const siteUrl = urlUtils . getSiteUrl ( ) ;
2021-10-04 14:18:22 +03:00
if ( ! /^https/ . test ( siteUrl ) && stripeService . api . configured ) {
2021-12-01 14:22:14 +03:00
throw new errors . IncorrectUsageError ( {
message : tpl ( messages . sslRequiredForStripe )
} ) ;
2021-05-12 15:46:05 +03:00
}
}
2021-05-12 15:02:27 +03:00
if ( ! membersApi ) {
membersApi = createMembersApiInstance ( membersConfig ) ;
membersApi . bus . on ( 'error' , function ( err ) {
logging . error ( err ) ;
} ) ;
}
2022-01-04 12:21:36 +03:00
( async ( ) => {
try {
const collection = await models . SingleUseToken . fetchAll ( ) ;
await collection . invokeThen ( 'destroy' ) ;
} catch ( err ) {
logging . error ( err ) ;
}
} ) ( ) ;
2022-01-18 18:56:47 +03:00
2022-01-24 17:38:16 +03:00
try {
await stripeService . migrations . execute ( ) ;
} catch ( err ) {
logging . error ( err ) ;
}
2021-10-04 14:18:22 +03:00
} ,
contentGating : require ( './content-gating' ) ,
config : membersConfig ,
get api ( ) {
2021-05-12 15:02:27 +03:00
return membersApi ;
} ,
get settings ( ) {
if ( ! membersSettings ) {
membersSettings = createMembersSettingsInstance ( membersConfig ) ;
}
return membersSettings ;
} ,
ssr : MembersSSR ( {
cookieSecure : urlUtils . isSSL ( urlUtils . getSiteUrl ( ) ) ,
cookieKeys : [ settingsCache . get ( 'theme_session_secret' ) ] ,
cookieName : 'ghost-members-ssr' ,
cookieCacheName : 'ghost-members-ssr-cache' ,
2022-01-18 18:56:47 +03:00
getMembersApi : ( ) => module . exports . api
2021-05-12 15:02:27 +03:00
} ) ,
stripeConnect : require ( './stripe-connect' ) ,
2021-07-23 15:58:35 +03:00
processImport : processImport ,
2021-05-12 15:02:27 +03:00
stats : new MembersStats ( {
db : db ,
settingsCache : settingsCache ,
isSQLite : config . get ( 'database:client' ) === 'sqlite3'
} )
2022-01-18 18:56:47 +03:00
} ;
2021-05-12 15:02:27 +03:00
module . exports . middleware = require ( './middleware' ) ;