mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #1531 from sharelatex/spd-notify-users-on-affiliation-upgrade
Notify users when affiliations are upgraded GitOrigin-RevId: 0f9e92b6a49f2ddef559e9e23fc73436910fb9f6
This commit is contained in:
parent
b8bb118fe3
commit
838fe00058
5 changed files with 142 additions and 20 deletions
|
@ -1,17 +1,29 @@
|
|||
logger = require 'logger-sharelatex'
|
||||
async = require 'async'
|
||||
db = require("../../infrastructure/mongojs").db
|
||||
_ = require("underscore")
|
||||
ObjectId = require("../../infrastructure/mongojs").ObjectId
|
||||
{ getInstitutionAffiliations } = require('./InstitutionsAPI')
|
||||
FeaturesUpdater = require('../Subscription/FeaturesUpdater')
|
||||
UserGetter = require('../User/UserGetter')
|
||||
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
|
||||
SubscriptionLocator = require("../Subscription/SubscriptionLocator")
|
||||
Institution = require("../../models/Institution").Institution
|
||||
|
||||
ASYNC_LIMIT = 10
|
||||
module.exports = InstitutionsManager =
|
||||
upgradeInstitutionUsers: (institutionId, callback = (error) ->) ->
|
||||
getInstitutionAffiliations institutionId, (error, affiliations) ->
|
||||
return callback(error) if error
|
||||
async.eachLimit affiliations, ASYNC_LIMIT, refreshFeatures, callback
|
||||
async.waterfall [
|
||||
(cb) ->
|
||||
fetchInstitutionAndAffiliations institutionId, cb
|
||||
(institution, affiliations, cb) ->
|
||||
affiliations = _.map affiliations, (affiliation) ->
|
||||
affiliation.institutionName = institution.name
|
||||
affiliation.institutionId = institutionId
|
||||
return affiliation
|
||||
async.eachLimit affiliations, ASYNC_LIMIT, refreshFeatures, (err) -> cb(err)
|
||||
], callback
|
||||
|
||||
checkInstitutionUsers: (institutionId, callback = (error) ->) ->
|
||||
getInstitutionAffiliations institutionId, (error, affiliations) ->
|
||||
UserGetter.getUsersByAnyConfirmedEmail(
|
||||
|
@ -21,9 +33,48 @@ module.exports = InstitutionsManager =
|
|||
callback(error, checkFeatures(users))
|
||||
)
|
||||
|
||||
fetchInstitutionAndAffiliations = (institutionId, callback) ->
|
||||
async.waterfall [
|
||||
(cb) ->
|
||||
Institution.findOne {v1Id: institutionId}, (err, institution) -> cb(err, institution)
|
||||
(institution, cb) ->
|
||||
institution.fetchV1Data (err, institution) -> cb(err, institution)
|
||||
(institution, cb) ->
|
||||
getInstitutionAffiliations institutionId, (err, affiliations) -> cb(err, institution, affiliations)
|
||||
], callback
|
||||
|
||||
refreshFeatures = (affiliation, callback) ->
|
||||
userId = ObjectId(affiliation.user_id)
|
||||
FeaturesUpdater.refreshFeatures(userId, true, callback)
|
||||
async.waterfall [
|
||||
(cb) ->
|
||||
FeaturesUpdater.refreshFeatures userId, true, (err, features, featuresChanged) -> cb(err, featuresChanged)
|
||||
(featuresChanged, cb) ->
|
||||
getUserInfo userId, (error, user, subscription) -> cb(error, user, subscription, featuresChanged)
|
||||
(user, subscription, featuresChanged, cb) ->
|
||||
notifyUser user, affiliation, subscription, featuresChanged, cb
|
||||
], callback
|
||||
|
||||
getUserInfo = (userId, callback) ->
|
||||
async.waterfall [
|
||||
(cb) ->
|
||||
UserGetter.getUser userId, cb
|
||||
(user, cb) ->
|
||||
SubscriptionLocator.getUsersSubscription user, (err, subscription) -> cb(err, user, subscription)
|
||||
], callback
|
||||
|
||||
notifyUser = (user, affiliation, subscription, featuresChanged, callback) ->
|
||||
async.parallel [
|
||||
(cb) ->
|
||||
if featuresChanged
|
||||
NotificationsBuilder.featuresUpgradedByAffiliation(affiliation, user).create cb
|
||||
else
|
||||
cb()
|
||||
(cb) ->
|
||||
if subscription? and !subscription.planCode.match(/(free|trial)/)? and !subscription.groupPlan
|
||||
NotificationsBuilder.redundantPersonalSubscription(affiliation, user).create cb
|
||||
else
|
||||
cb()
|
||||
], callback
|
||||
|
||||
checkFeatures = (users) ->
|
||||
usersSummary = {
|
||||
|
|
|
@ -4,8 +4,26 @@ request = require "request"
|
|||
settings = require "settings-sharelatex"
|
||||
|
||||
module.exports =
|
||||
# Note: notification keys should be url-safe
|
||||
|
||||
featuresUpgradedByAffiliation: (affiliation, user) ->
|
||||
key: "features-updated-by=#{affiliation.institutionId}"
|
||||
create: (callback=()->) ->
|
||||
messageOpts =
|
||||
institutionName: affiliation.institutionName
|
||||
NotificationsHandler.createNotification user._id, @key, "notification_features_upgraded_by_affiliation", messageOpts, null, false, callback
|
||||
read: (callback=()->) ->
|
||||
NotificationsHandler.markAsRead @key, callback
|
||||
|
||||
redundantPersonalSubscription: (affiliation, user) ->
|
||||
key: "redundant-personal-subscription-#{affiliation.institutionId}"
|
||||
create: (callback=()->) ->
|
||||
messageOpts =
|
||||
institutionName: affiliation.institutionName
|
||||
NotificationsHandler.createNotification user._id, @key, "notification_personal_subscription_not_required_due_to_affiliation", messageOpts, null, false, callback
|
||||
read: (callback=()->) ->
|
||||
NotificationsHandler.markAsRead @key, callback
|
||||
|
||||
# Note: notification keys should be url-safe
|
||||
projectInvite: (invite, project, sendingUser, user) ->
|
||||
key: "project-invite-#{invite._id}"
|
||||
create: (callback=()->) ->
|
||||
|
|
|
@ -12,7 +12,7 @@ InstitutionsFeatures = require '../Institutions/InstitutionsFeatures'
|
|||
oneMonthInSeconds = 60 * 60 * 24 * 30
|
||||
|
||||
module.exports = FeaturesUpdater =
|
||||
refreshFeatures: (user_id, notifyV1 = true, callback = () ->)->
|
||||
refreshFeatures: (user_id, notifyV1 = true, callback = (error, features, featuresChanged) ->)->
|
||||
if typeof notifyV1 == 'function'
|
||||
callback = notifyV1
|
||||
notifyV1 = true
|
||||
|
|
|
@ -2,11 +2,11 @@ logger = require("logger-sharelatex")
|
|||
User = require('../../models/User').User
|
||||
|
||||
module.exports =
|
||||
updateFeatures: (user_id, features, callback = (err, features)->)->
|
||||
updateFeatures: (user_id, features, callback = (err, features, featuresChanged)->)->
|
||||
conditions = _id:user_id
|
||||
update = {}
|
||||
logger.log user_id:user_id, features:features, "updating users features"
|
||||
update["features.#{key}"] = value for key, value of features
|
||||
User.update conditions, update, (err)->
|
||||
callback err, features
|
||||
User.update conditions, update, (err, result)->
|
||||
callback err, features, result?.nModified == 1
|
||||
|
||||
|
|
|
@ -9,30 +9,83 @@ describe "InstitutionsManager", ->
|
|||
beforeEach ->
|
||||
@institutionId = 123
|
||||
@logger = log: ->
|
||||
@user = {}
|
||||
@getInstitutionAffiliations = sinon.stub()
|
||||
@refreshFeatures = sinon.stub().yields()
|
||||
@getUsersByAnyConfirmedEmail = sinon.stub().yields()
|
||||
@UserGetter =
|
||||
getUsersByAnyConfirmedEmail: sinon.stub().yields()
|
||||
getUser: sinon.stub().callsArgWith(1, null, @user)
|
||||
@creator =
|
||||
create: sinon.stub().callsArg(0)
|
||||
@NotificationsBuilder =
|
||||
featuresUpgradedByAffiliation: sinon.stub().returns(@creator)
|
||||
redundantPersonalSubscription: sinon.stub().returns(@creator)
|
||||
@SubscriptionLocator =
|
||||
getUsersSubscription: sinon.stub().callsArg(1)
|
||||
@institutionWithV1Data =
|
||||
name: 'Wombat University'
|
||||
@institution =
|
||||
fetchV1Data: sinon.stub().callsArgWith(0, null, @institutionWithV1Data)
|
||||
@InstitutionModel =
|
||||
Institution:
|
||||
findOne: sinon.stub().callsArgWith(1, null, @institution)
|
||||
@Mongo =
|
||||
ObjectId: sinon.stub().returnsArg(0)
|
||||
|
||||
@InstitutionsManager = SandboxedModule.require modulePath, requires:
|
||||
'logger-sharelatex': @logger
|
||||
'./InstitutionsAPI':
|
||||
getInstitutionAffiliations: @getInstitutionAffiliations
|
||||
'../Subscription/FeaturesUpdater':
|
||||
refreshFeatures: @refreshFeatures
|
||||
'../User/UserGetter':
|
||||
getUsersByAnyConfirmedEmail: @getUsersByAnyConfirmedEmail
|
||||
'../User/UserGetter': @UserGetter
|
||||
'../Notifications/NotificationsBuilder': @NotificationsBuilder
|
||||
'../Subscription/SubscriptionLocator': @SubscriptionLocator
|
||||
'../../models/Institution': @InstitutionModel
|
||||
'../../infrastructure/mongojs': @Mongo
|
||||
|
||||
describe 'upgradeInstitutionUsers', ->
|
||||
it 'refresh all users Features', (done) ->
|
||||
affiliations = [
|
||||
{ user_id: '123abc123abc123abc123abc' }
|
||||
{ user_id: '456def456def456def456def' }
|
||||
beforeEach ->
|
||||
@user1Id = '123abc123abc123abc123abc'
|
||||
@user2Id = '456def456def456def456def'
|
||||
@affiliations = [
|
||||
{ user_id: @user1Id }
|
||||
{ user_id: @user2Id }
|
||||
]
|
||||
@getInstitutionAffiliations.yields(null, affiliations)
|
||||
@user1 =
|
||||
_id: @user1Id
|
||||
@user2 =
|
||||
_id: @user2Id
|
||||
@subscription =
|
||||
planCode: 'pro'
|
||||
groupPlan: false
|
||||
@UserGetter.getUser.withArgs(@user1Id).callsArgWith(1, null, @user1)
|
||||
@UserGetter.getUser.withArgs(@user2Id).callsArgWith(1, null, @user2)
|
||||
@SubscriptionLocator.getUsersSubscription.withArgs(@user2).callsArgWith(1, null, @subscription)
|
||||
@refreshFeatures.withArgs(@user1Id).callsArgWith(2, null, {}, true)
|
||||
@getInstitutionAffiliations.yields(null, @affiliations)
|
||||
|
||||
it 'refresh all users Features', (done) ->
|
||||
@InstitutionsManager.upgradeInstitutionUsers @institutionId, (error) =>
|
||||
should.not.exist(error)
|
||||
sinon.assert.calledTwice(@refreshFeatures)
|
||||
done()
|
||||
|
||||
it "notifies users if their features have been upgraded", (done) ->
|
||||
@InstitutionsManager.upgradeInstitutionUsers @institutionId, (error) =>
|
||||
should.not.exist(error)
|
||||
sinon.assert.calledOnce(@NotificationsBuilder.featuresUpgradedByAffiliation)
|
||||
sinon.assert.calledWith(@NotificationsBuilder.featuresUpgradedByAffiliation, @affiliations[0], @user1)
|
||||
done()
|
||||
|
||||
it "notifies users if they have a subscription that should be cancelled", (done) ->
|
||||
@InstitutionsManager.upgradeInstitutionUsers @institutionId, (error) =>
|
||||
should.not.exist(error)
|
||||
sinon.assert.calledOnce(@NotificationsBuilder.redundantPersonalSubscription)
|
||||
sinon.assert.calledWith(@NotificationsBuilder.redundantPersonalSubscription, @affiliations[1], @user2)
|
||||
done()
|
||||
|
||||
|
||||
describe 'checkInstitutionUsers', ->
|
||||
it 'check all users Features', (done) ->
|
||||
affiliations = [
|
||||
|
@ -54,7 +107,7 @@ describe "InstitutionsManager", ->
|
|||
}
|
||||
]
|
||||
@getInstitutionAffiliations.yields(null, affiliations)
|
||||
@getUsersByAnyConfirmedEmail.yields(null, stubbedUsers)
|
||||
@UserGetter.getUsersByAnyConfirmedEmail.yields(null, stubbedUsers)
|
||||
@InstitutionsManager.checkInstitutionUsers @institutionId, (error, usersSummary) =>
|
||||
should.not.exist(error)
|
||||
usersSummary.totalConfirmedUsers.should.equal 3
|
||||
|
|
Loading…
Reference in a new issue