diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.js b/services/web/app/src/Features/Subscription/FeaturesUpdater.js index 0a7698f7fb..63568f1697 100644 --- a/services/web/app/src/Features/Subscription/FeaturesUpdater.js +++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.js @@ -50,6 +50,9 @@ const FeaturesUpdater = { }, samlFeatures(cb) { return FeaturesUpdater._getSamlFeatures(user_id, cb) + }, + featuresOverrides(cb) { + return FeaturesUpdater._getFeaturesOverrides(user_id, cb) } } return async.series(jobs, function(err, results) { @@ -67,7 +70,8 @@ const FeaturesUpdater = { institutionFeatures, v1Features, bonusFeatures, - samlFeatures + samlFeatures, + featuresOverrides } = results logger.log( { @@ -77,7 +81,8 @@ const FeaturesUpdater = { institutionFeatures, v1Features, bonusFeatures, - samlFeatures + samlFeatures, + featuresOverrides }, 'merging user features' ) @@ -86,7 +91,8 @@ const FeaturesUpdater = { institutionFeatures, v1Features, bonusFeatures, - samlFeatures + samlFeatures, + featuresOverrides ]) const features = _.reduce( featureSets, @@ -143,6 +149,36 @@ const FeaturesUpdater = { }) }, + _getFeaturesOverrides(user_id, callback) { + UserGetter.getUser(user_id, { featuresOverrides: 1 }, (error, user) => { + if (error) { + return callback(error) + } + if ( + !user || + !user.featuresOverrides || + user.featuresOverrides.length === 0 + ) { + return callback(null, {}) + } + let activeFeaturesOverrides = [] + for (let featuresOverride of user.featuresOverrides) { + if ( + !featuresOverride.expiresAt || + featuresOverride.expiresAt > new Date() + ) { + activeFeaturesOverrides.push(featuresOverride.features) + } + } + const features = _.reduce( + activeFeaturesOverrides, + FeaturesUpdater._mergeFeatures, + {} + ) + return callback(null, features) + }) + }, + _getV1Features(user_id, callback) { if (callback == null) { callback = function(error, features) {} diff --git a/services/web/app/src/models/User.js b/services/web/app/src/models/User.js index a18b1eb06a..4637cb896e 100644 --- a/services/web/app/src/models/User.js +++ b/services/web/app/src/models/User.js @@ -91,6 +91,32 @@ const UserSchema = new Schema({ default: Settings.defaultFeatures.referencesSearch } }, + featuresOverrides: [ + { + createdAt: { + type: Date, + default() { + return new Date() + } + }, + expiresAt: { type: Date }, + note: { type: String }, + features: { + collaborators: { type: Number }, + versioning: { type: Boolean }, + dropbox: { type: Boolean }, + github: { type: Boolean }, + gitBridge: { type: Boolean }, + compileTimeout: { type: Number }, + compileGroup: { type: String }, + templates: { type: Boolean }, + trackChanges: { type: Boolean }, + mendeley: { type: Boolean }, + zotero: { type: Boolean }, + referencesSearch: { type: Boolean } + } + } + ], // when auto-merged from SL and must-reconfirm is set, we may end up using // `sharelatexHashedPassword` to recover accounts... sharelatexHashedPassword: String, diff --git a/services/web/test/acceptance/src/FeatureUpdaterTests.js b/services/web/test/acceptance/src/FeatureUpdaterTests.js index 1357a94557..6b365fb2ad 100644 --- a/services/web/test/acceptance/src/FeatureUpdaterTests.js +++ b/services/web/test/acceptance/src/FeatureUpdaterTests.js @@ -338,4 +338,51 @@ describe('FeatureUpdater.refreshFeatures', function() { ) }) // returns a promise }) + + describe('when the user has features overrides', function() { + beforeEach(function() { + const futureDate = new Date() + futureDate.setDate(futureDate.getDate() + 1) + return User.update( + { + _id: this.user._id + }, + { + featuresOverrides: [ + { + features: { + github: true + } + }, + { + features: { + dropbox: true + }, + expiresAt: new Date(1990, 12, 25) + }, + { + features: { + trackChanges: true + }, + expiresAt: futureDate + } + ] + } + ) + }) // returns a promise + + it('should set their features to the overridden set', function(done) { + return syncUserAndGetFeatures(this.user, (error, features) => { + if (error != null) { + throw error + } + let expectedFeatures = Object.assign(settings.defaultFeatures, { + github: true, + trackChanges: true + }) + expect(features).to.deep.equal(expectedFeatures) + return done() + }) + }) + }) }) diff --git a/services/web/test/acceptance/src/helpers/User.js b/services/web/test/acceptance/src/helpers/User.js index 69ca50be9a..2448a352d5 100644 --- a/services/web/test/acceptance/src/helpers/User.js +++ b/services/web/test/acceptance/src/helpers/User.js @@ -142,6 +142,11 @@ class User { UserModel.update({ _id: this.id }, update, callback) } + setFeaturesOverride(featuresOverride, callback) { + const update = { $push: { featuresOverrides: featuresOverride } } + UserModel.update({ _id: this.id }, update, callback) + } + setOverleafId(overleafId, callback) { UserModel.update({ _id: this.id }, { 'overleaf.id': overleafId }, callback) }