Merge pull request #801 from sharelatex/ho-mailchimp

Add non checked checkbox for newsletter subscription on signup and use mailchimp as newsletter provider.
This commit is contained in:
Henry Oswald 2018-08-16 11:40:39 +01:00 committed by GitHub
commit 5ed95694da
7 changed files with 104 additions and 39 deletions

View file

@ -1,37 +1,68 @@
async = require('async') async = require('async')
Request = require('request')
logger = require 'logger-sharelatex' logger = require 'logger-sharelatex'
Settings = require 'settings-sharelatex' Settings = require 'settings-sharelatex'
crypto = require('crypto')
Mailchimp = require('mailchimp-api-v3')
if !Settings.mailchimp?.api_key?
logger.info "Using newsletter provider: none"
mailchimp =
request: (opts, cb)-> cb()
else
logger.info "Using newsletter provider: mailchimp"
mailchimp = new Mailchimp(Settings.mailchimp?.api_key)
module.exports = module.exports =
subscribe: (user, callback = () ->)-> subscribe: (user, callback = () ->)->
if !Settings.markdownmail?
logger.warn "No newsletter provider configured so not subscribing user"
return callback()
logger.log user:user, email:user.email, "trying to subscribe user to the mailing list"
options = buildOptions(user, true) options = buildOptions(user, true)
Request.post options, (err, response, body)-> logger.log options:options, user:user, email:user.email, "trying to subscribe user to the mailing list"
logger.log body:body, user:user, "finished attempting to subscribe the user to the news letter" mailchimp.request options, (err)->
if err?
logger.err err:err, "error subscribing person to newsletter"
else
logger.log user:user, "finished subscribing user to the newsletter"
callback(err) callback(err)
unsubscribe: (user, callback = () ->)-> unsubscribe: (user, callback = () ->)->
if !Settings.markdownmail?
logger.warn "No newsletter provider configured so not unsubscribing user"
return callback()
logger.log user:user, email:user.email, "trying to unsubscribe user to the mailing list" logger.log user:user, email:user.email, "trying to unsubscribe user to the mailing list"
options = buildOptions(user, false) options = buildOptions(user, false)
Request.post options, (err, response, body)-> mailchimp.request options, (err)->
logger.log err:err, body:body, email:user.email, "compled newsletter unsubscribe attempt" if err?
logger.err err:err, "error unsubscribing person to newsletter"
else
logger.log user:user, "finished unsubscribing user to the newsletter"
callback(err) callback(err)
changeEmail: (oldEmail, newEmail, callback = ()->)->
options = buildOptions({email:oldEmail})
delete options.body.status
options.body.email_address = newEmail
mailchimp.request options, (err)->
# if the user has unsubscribed mailchimp will error on email address change
if err? and err?.message.indexOf("could not be validated") == -1
logger.err err:err, "error changing email in newsletter"
return callback(err)
else
logger.log "finished changing email in the newsletter"
return callback()
hashEmail = (email)->
crypto.createHash('md5').update(email.toLowerCase()).digest("hex")
buildOptions = (user, is_subscribed)-> buildOptions = (user, is_subscribed)->
options = status = if is_subscribed then "subscribed" else "unsubscribed"
json: subscriber_hash = hashEmail(user.email)
secret_token: Settings.markdownmail.secret opts =
name: "#{user.first_name} #{user.last_name}" method: "PUT"
email: user.email path: "/lists/#{Settings.mailchimp?.list_id}/members/#{subscriber_hash}"
subscriber_list_id: Settings.markdownmail.list_id body:
is_subscribed: is_subscribed status_if_new: status
url: "https://www.markdownmail.io/lists/subscribe" status: status
timeout: 30 * 1000 email_address:user.email
return options merge_fields:
FNAME: user.first_name
LNAME: user.last_name
MONGO_ID:user._id
return opts

View file

@ -1,4 +1,3 @@
sanitize = require('sanitizer')
User = require("../../models/User").User User = require("../../models/User").User
UserCreator = require("./UserCreator") UserCreator = require("./UserCreator")
UserGetter = require("./UserGetter") UserGetter = require("./UserGetter")
@ -54,7 +53,8 @@ module.exports = UserRegistrationHandler =
(cb)-> User.update {_id: user._id}, {"$set":{holdingAccount:false}}, cb (cb)-> User.update {_id: user._id}, {"$set":{holdingAccount:false}}, cb
(cb)-> AuthenticationManager.setUserPassword user._id, userDetails.password, cb (cb)-> AuthenticationManager.setUserPassword user._id, userDetails.password, cb
(cb)-> (cb)->
NewsLetterManager.subscribe user, -> if userDetails.subscribeToNewsletter == "true"
NewsLetterManager.subscribe user, ->
cb() #this can be slow, just fire it off cb() #this can be slow, just fire it off
], (err)-> ], (err)->
logger.log user: user, "registered" logger.log user: user, "registered"

View file

@ -11,6 +11,7 @@ EmailHelper = require "../Helpers/EmailHelper"
Errors = require "../Errors/Errors" Errors = require "../Errors/Errors"
Settings = require "settings-sharelatex" Settings = require "settings-sharelatex"
request = require 'request' request = require 'request'
NewsletterManager = require "../Newsletter/NewsletterManager"
module.exports = UserUpdater = module.exports = UserUpdater =
updateUser: (query, update, callback = (error) ->) -> updateUser: (query, update, callback = (error) ->) ->
@ -99,15 +100,21 @@ module.exports = UserUpdater =
setDefaultEmailAddress: (userId, email, callback) -> setDefaultEmailAddress: (userId, email, callback) ->
email = EmailHelper.parseEmail(email) email = EmailHelper.parseEmail(email)
return callback(new Error('invalid email')) if !email? return callback(new Error('invalid email')) if !email?
query = _id: userId, 'emails.email': email UserGetter.getUserEmail userId, (error, oldEmail) =>
update = $set: email: email if err?
@updateUser query, update, (error, res) ->
if error?
logger.err error:error, 'problem setting default emails'
return callback(error) return callback(error)
if res.n == 0 # TODO: Check n or nMatched? query = _id: userId, 'emails.email': email
return callback(new Error('Default email does not belong to user')) update = $set: email: email
callback() @updateUser query, update, (error, res) ->
if error?
logger.err error:error, 'problem setting default emails'
return callback(error)
else if res.n == 0 # TODO: Check n or nMatched?
return callback(new Error('Default email does not belong to user'))
else
NewsletterManager.changeEmail oldEmail, email, callback
updateV1AndSetDefaultEmailAddress: (userId, email, callback) -> updateV1AndSetDefaultEmailAddress: (userId, email, callback) ->
@updateEmailAddressInV1 userId, email, (error) => @updateEmailAddressInV1 userId, email, (error) =>

View file

@ -278,10 +278,10 @@ module.exports = settings =
# Third party services # Third party services
# -------------------- # --------------------
# #
# ShareLaTeX's regular newsletter is managed by Markdown mail. Add your # ShareLaTeX's regular newsletter is managed by mailchimp. Add your
# credentials here to integrate with this. # credentials here to integrate with this.
# markdownmail: # mailchimp:
# secret: "" # api_key: ""
# list_id: "" # list_id: ""
# #
# Fill in your unique token from various analytics services to enable # Fill in your unique token from various analytics services to enable

View file

@ -59,6 +59,7 @@
"lodash": "^4.13.1", "lodash": "^4.13.1",
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
"lynx": "0.1.1", "lynx": "0.1.1",
"mailchimp-api-v3": "^1.12.0",
"marked": "^0.3.5", "marked": "^0.3.5",
"method-override": "^2.3.3", "method-override": "^2.3.3",
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.7.1", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.7.1",

View file

@ -132,11 +132,17 @@ describe "UserRegistrationHandler", ->
@AuthenticationManager.setUserPassword.calledWith(@user._id, @passingRequest.password).should.equal true @AuthenticationManager.setUserPassword.calledWith(@user._id, @passingRequest.password).should.equal true
done() done()
it "should add the user to the news letter manager", (done)-> it "should add the user to the newsletter if accepted terms", (done)->
@passingRequest.subscribeToNewsletter = "true"
@handler.registerNewUser @passingRequest, (err)=> @handler.registerNewUser @passingRequest, (err)=>
@NewsLetterManager.subscribe.calledWith(@user).should.equal true @NewsLetterManager.subscribe.calledWith(@user).should.equal true
done() done()
it "should not add the user to the newsletter if not accepted terms", (done)->
@handler.registerNewUser @passingRequest, (err)=>
@NewsLetterManager.subscribe.calledWith(@user).should.equal false
done()
it "should track the registration event", (done)-> it "should track the registration event", (done)->
@handler.registerNewUser @passingRequest, (err)=> @handler.registerNewUser @passingRequest, (err)=>
@AnalyticsManager.recordEvent @AnalyticsManager.recordEvent

View file

@ -18,21 +18,27 @@ describe "UserUpdater", ->
getUserEmail: sinon.stub() getUserEmail: sinon.stub()
getUserByAnyEmail: sinon.stub() getUserByAnyEmail: sinon.stub()
ensureUniqueEmailAddress: sinon.stub() ensureUniqueEmailAddress: sinon.stub()
@logger = err: sinon.stub(), log: -> @logger =
err: sinon.stub()
log: ->
warn: ->
@addAffiliation = sinon.stub().yields() @addAffiliation = sinon.stub().yields()
@removeAffiliation = sinon.stub().callsArgWith(2, null) @removeAffiliation = sinon.stub().callsArgWith(2, null)
@refreshFeatures = sinon.stub().yields() @refreshFeatures = sinon.stub().yields()
@NewsletterManager =
changeEmail:sinon.stub()
@UserUpdater = SandboxedModule.require modulePath, requires: @UserUpdater = SandboxedModule.require modulePath, requires:
"logger-sharelatex": @logger "logger-sharelatex": @logger
"../../infrastructure/mongojs":@mongojs
"metrics-sharelatex": timeAsyncMethod: sinon.stub()
"./UserGetter": @UserGetter "./UserGetter": @UserGetter
'../Institutions/InstitutionsAPI': '../Institutions/InstitutionsAPI':
addAffiliation: @addAffiliation addAffiliation: @addAffiliation
removeAffiliation: @removeAffiliation removeAffiliation: @removeAffiliation
'../Subscription/FeaturesUpdater': refreshFeatures: @refreshFeatures '../Subscription/FeaturesUpdater': refreshFeatures: @refreshFeatures
"../../infrastructure/mongojs":@mongojs
"metrics-sharelatex": timeAsyncMethod: sinon.stub()
"settings-sharelatex": @settings = {} "settings-sharelatex": @settings = {}
"request": @request = {} "request": @request = {}
"../Newsletter/NewsletterManager": @NewsletterManager
@stubbedUser = @stubbedUser =
_id: "3131231" _id: "3131231"
@ -174,6 +180,10 @@ describe "UserUpdater", ->
done() done()
describe 'setDefaultEmailAddress', -> describe 'setDefaultEmailAddress', ->
beforeEach ->
@UserGetter.getUserEmail.callsArgWith(1, null, @stubbedUser.email)
@NewsletterManager.changeEmail.callsArgWith(2, null)
it 'set default', (done)-> it 'set default', (done)->
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1) @UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1)
@ -185,6 +195,16 @@ describe "UserUpdater", ->
).should.equal true ).should.equal true
done() done()
it 'set changed the email in newsletter', (done)->
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1)
@UserUpdater.setDefaultEmailAddress @stubbedUser._id, @newEmail, (err)=>
should.not.exist(err)
@NewsletterManager.changeEmail.calledWith(
@stubbedUser.email, @newEmail
).should.equal true
done()
it 'handle error', (done)-> it 'handle error', (done)->
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, new Error('nope')) @UserUpdater.updateUser = sinon.stub().callsArgWith(2, new Error('nope'))