Przeglądaj źródła

Merge pull request #1531 from sharelatex/spd-notify-users-on-affiliation-upgrade

Notify users when affiliations are upgraded

GitOrigin-RevId: 0f9e92b6a49f2ddef559e9e23fc73436910fb9f6
Simon Detheridge 7 lat temu
rodzic
commit
838fe00058

+ 56 - 5
services/web/app/coffee/Features/Institutions/InstitutionsManager.coffee

@@ -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 = {
@@ -39,4 +90,4 @@ checkFeatures = (users) ->
 			usersSummary.totalConfirmedNonProUsers += 1
 			usersSummary.confirmedNonProUsers.push user._id
 	)
-	return usersSummary
+	return usersSummary

+ 19 - 1
services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee

@@ -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=()->) ->

+ 1 - 1
services/web/app/coffee/Features/Subscription/FeaturesUpdater.coffee

@@ -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

+ 3 - 3
services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee

@@ -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
 

+ 63 - 10
services/web/test/unit/coffee/Institutions/InstitutionsManagerTests.coffee

@@ -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,11 +107,11 @@ 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
 				usersSummary.totalConfirmedProUsers.should.equal 1
 				usersSummary.totalConfirmedNonProUsers.should.equal 2
 				expect(usersSummary.confirmedNonProUsers).to.deep.equal ['456def456def456def456def', '789def789def789def789def']
-				done()
+				done()