diff --git a/services/web/app/coffee/Features/User/UserAffiliationsManager.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee similarity index 82% rename from services/web/app/coffee/Features/User/UserAffiliationsManager.coffee rename to services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index 77dad7a8ef..8ce39d68e3 100644 --- a/services/web/app/coffee/Features/User/UserAffiliationsManager.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -3,12 +3,20 @@ metrics = require("metrics-sharelatex") settings = require "settings-sharelatex" request = require "request" -module.exports = UserAffiliationsManager = - getAffiliations: (userId, callback = (error, body) ->) -> +module.exports = InstitutionsAPI = + getInstitutionAffiliations: (institutionId, callback = (error, body) ->) -> + makeAffiliationRequest { + method: 'GET' + path: "/api/v2/institutions/#{institutionId.toString()}/affiliations" + defaultErrorMessage: "Couldn't get institution affiliations" + }, callback + + + getUserAffiliations: (userId, callback = (error, body) ->) -> makeAffiliationRequest { method: 'GET' path: "/api/v2/users/#{userId.toString()}/affiliations" - defaultErrorMessage: "Couldn't get affiliations" + defaultErrorMessage: "Couldn't get user affiliations" }, callback @@ -77,10 +85,11 @@ makeAffiliationRequest = (requestOptions, callback = (error) ->) -> callback(null, body) [ - 'getAffiliations', + 'getInstitutionAffiliations' + 'getUserAffiliations', 'addAffiliation', 'removeAffiliation', ].map (method) -> metrics.timeAsyncMethod( - UserAffiliationsManager, method, 'mongo.UserAffiliationsManager', logger + InstitutionsAPI, method, 'mongo.InstitutionsAPI', logger ) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsManager.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsManager.coffee new file mode 100644 index 0000000000..15233c8849 --- /dev/null +++ b/services/web/app/coffee/Features/Institutions/InstitutionsManager.coffee @@ -0,0 +1,17 @@ +logger = require 'logger-sharelatex' +async = require 'async' +db = require("../../infrastructure/mongojs").db +ObjectId = require("../../infrastructure/mongojs").ObjectId +{ getInstitutionAffiliations } = require('./InstitutionsAPI') +FeaturesUpdater = require('../Subscription/FeaturesUpdater') + +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 + +refreshFeatures = (affiliation, callback) -> + userId = ObjectId(affiliation.user_id) + FeaturesUpdater.refreshFeatures(userId, true, callback) diff --git a/services/web/app/coffee/Features/User/UserCreator.coffee b/services/web/app/coffee/Features/User/UserCreator.coffee index b0676e09e0..2dfa7f5ac0 100644 --- a/services/web/app/coffee/Features/User/UserCreator.coffee +++ b/services/web/app/coffee/Features/User/UserCreator.coffee @@ -1,7 +1,7 @@ User = require("../../models/User").User logger = require("logger-sharelatex") metrics = require('metrics-sharelatex') -{ addAffiliation } = require("./UserAffiliationsManager") +{ addAffiliation } = require("../Institutions/InstitutionsAPI") module.exports = UserCreator = diff --git a/services/web/app/coffee/Features/User/UserDeleter.coffee b/services/web/app/coffee/Features/User/UserDeleter.coffee index c09bebbd8c..149959c00f 100644 --- a/services/web/app/coffee/Features/User/UserDeleter.coffee +++ b/services/web/app/coffee/Features/User/UserDeleter.coffee @@ -4,7 +4,7 @@ ProjectDeleter = require("../Project/ProjectDeleter") logger = require("logger-sharelatex") SubscriptionHandler = require("../Subscription/SubscriptionHandler") async = require("async") -{ deleteAffiliations } = require("./UserAffiliationsManager") +{ deleteAffiliations } = require("../Institutions/InstitutionsAPI") module.exports = diff --git a/services/web/app/coffee/Features/User/UserEmailsController.coffee b/services/web/app/coffee/Features/User/UserEmailsController.coffee index 780960c3e2..e37a0452e7 100644 --- a/services/web/app/coffee/Features/User/UserEmailsController.coffee +++ b/services/web/app/coffee/Features/User/UserEmailsController.coffee @@ -3,7 +3,7 @@ UserGetter = require("./UserGetter") UserUpdater = require("./UserUpdater") EmailHelper = require("../Helpers/EmailHelper") UserEmailsConfirmationHandler = require "./UserEmailsConfirmationHandler" -{ endorseAffiliation } = require("./UserAffiliationsManager") +{ endorseAffiliation } = require("../Institutions/InstitutionsAPI") logger = require("logger-sharelatex") Errors = require "../Errors/Errors" diff --git a/services/web/app/coffee/Features/User/UserGetter.coffee b/services/web/app/coffee/Features/User/UserGetter.coffee index 13b81b399c..f3aa4ad4d6 100644 --- a/services/web/app/coffee/Features/User/UserGetter.coffee +++ b/services/web/app/coffee/Features/User/UserGetter.coffee @@ -3,7 +3,7 @@ metrics = require('metrics-sharelatex') logger = require('logger-sharelatex') db = mongojs.db ObjectId = mongojs.ObjectId -{ getAffiliations } = require("./UserAffiliationsManager") +{ getUserAffiliations } = require("../Institutions/InstitutionsAPI") Errors = require("../Errors/Errors") module.exports = UserGetter = @@ -32,7 +32,7 @@ module.exports = UserGetter = return callback error if error? return callback new Error('User not Found') unless user - getAffiliations userId, (error, affiliationsData) -> + getUserAffiliations userId, (error, affiliationsData) -> return callback error if error? callback null, decorateFullEmails(user.email, user.emails, affiliationsData) diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 958d5a4edf..8d5e3658f9 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -5,7 +5,7 @@ db = mongojs.db async = require("async") ObjectId = mongojs.ObjectId UserGetter = require("./UserGetter") -{ addAffiliation, removeAffiliation } = require("./UserAffiliationsManager") +{ addAffiliation, removeAffiliation } = require("../Institutions/InstitutionsAPI") FeaturesUpdater = require("../Subscription/FeaturesUpdater") EmailHelper = require "../Helpers/EmailHelper" Errors = require "../Errors/Errors" diff --git a/services/web/scripts/upgrade_institution_users.js b/services/web/scripts/upgrade_institution_users.js new file mode 100644 index 0000000000..8cf098ca2b --- /dev/null +++ b/services/web/scripts/upgrade_institution_users.js @@ -0,0 +1,16 @@ +const InstitutionsManager = require( + '../app/js/Features/Institutions/InstitutionsManager' +) + +const institutionId = parseInt(process.argv[2]) +if (isNaN(institutionId)) throw new Error('No institution id') +console.log('Upgrading users of institution', institutionId) + +InstitutionsManager.upgradeInstitutionUsers(institutionId, function (error) { + if (error) { + console.log(error) + } else { + console.log('DONE 👌') + } + process.exit() +}) diff --git a/services/web/test/unit/coffee/User/UserAffiliationsManagerTests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee similarity index 73% rename from services/web/test/unit/coffee/User/UserAffiliationsManagerTests.coffee rename to services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index ff3d96a283..7500e46e40 100644 --- a/services/web/test/unit/coffee/User/UserAffiliationsManagerTests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -4,16 +4,16 @@ SandboxedModule = require('sandboxed-module') assert = require('assert') path = require('path') sinon = require('sinon') -modulePath = path.join __dirname, "../../../../app/js/Features/User/UserAffiliationsManager" +modulePath = path.join __dirname, "../../../../app/js/Features/Institutions/InstitutionsAPI" expect = require("chai").expect -describe "UserAffiliationsManager", -> +describe "InstitutionsAPI", -> beforeEach -> @logger = err: sinon.stub(), log: -> settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } } @request = sinon.stub() - @UserAffiliationsManager = SandboxedModule.require modulePath, requires: + @InstitutionsAPI = SandboxedModule.require modulePath, requires: "logger-sharelatex": @logger "metrics-sharelatex": timeAsyncMethod: sinon.stub() 'settings-sharelatex': settings @@ -25,11 +25,27 @@ describe "UserAffiliationsManager", -> email:"hello@world.com" @newEmail = "bob@bob.com" - describe 'getAffiliations', -> + describe 'getInstitutionAffiliations', -> + it 'get affiliations', (done)-> + @institutionId = 123 + responseBody = ['123abc', '456def'] + @request.yields(null, { statusCode: 200 }, responseBody) + @InstitutionsAPI.getInstitutionAffiliations @institutionId, (err, body) => + should.not.exist(err) + @request.calledOnce.should.equal true + requestOptions = @request.lastCall.args[0] + expectedUrl = "v1.url/api/v2/institutions/#{@institutionId}/affiliations" + requestOptions.url.should.equal expectedUrl + requestOptions.method.should.equal 'GET' + should.not.exist(requestOptions.body) + body.should.equal responseBody + done() + + describe 'getUserAffiliations', -> it 'get affiliations', (done)-> responseBody = [{ foo: 'bar' }] @request.callsArgWith(1, null, { statusCode: 201 }, responseBody) - @UserAffiliationsManager.getAffiliations @stubbedUser._id, (err, body) => + @InstitutionsAPI.getUserAffiliations @stubbedUser._id, (err, body) => should.not.exist(err) @request.calledOnce.should.equal true requestOptions = @request.lastCall.args[0] @@ -43,7 +59,7 @@ describe "UserAffiliationsManager", -> it 'handle error', (done)-> body = errors: 'affiliation error message' @request.callsArgWith(1, null, { statusCode: 503 }, body) - @UserAffiliationsManager.getAffiliations @stubbedUser._id, (err) => + @InstitutionsAPI.getUserAffiliations @stubbedUser._id, (err) => should.exist(err) err.message.should.have.string 503 err.message.should.have.string body.errors @@ -58,7 +74,7 @@ describe "UserAffiliationsManager", -> university: { id: 1 } role: 'Prof' department: 'Math' - @UserAffiliationsManager.addAffiliation @stubbedUser._id, @newEmail, affiliationOptions, (err)=> + @InstitutionsAPI.addAffiliation @stubbedUser._id, @newEmail, affiliationOptions, (err)=> should.not.exist(err) @request.calledOnce.should.equal true requestOptions = @request.lastCall.args[0] @@ -77,7 +93,7 @@ describe "UserAffiliationsManager", -> it 'handle error', (done)-> body = errors: 'affiliation error message' @request.callsArgWith(1, null, { statusCode: 422 }, body) - @UserAffiliationsManager.addAffiliation @stubbedUser._id, @newEmail, {}, (err)=> + @InstitutionsAPI.addAffiliation @stubbedUser._id, @newEmail, {}, (err)=> should.exist(err) err.message.should.have.string 422 err.message.should.have.string body.errors @@ -88,7 +104,7 @@ describe "UserAffiliationsManager", -> @request.callsArgWith(1, null, { statusCode: 404 }) it 'remove affiliation', (done)-> - @UserAffiliationsManager.removeAffiliation @stubbedUser._id, @newEmail, (err)=> + @InstitutionsAPI.removeAffiliation @stubbedUser._id, @newEmail, (err)=> should.not.exist(err) @request.calledOnce.should.equal true requestOptions = @request.lastCall.args[0] @@ -100,7 +116,7 @@ describe "UserAffiliationsManager", -> it 'handle error', (done)-> @request.callsArgWith(1, null, { statusCode: 500 }) - @UserAffiliationsManager.removeAffiliation @stubbedUser._id, @newEmail, (err)=> + @InstitutionsAPI.removeAffiliation @stubbedUser._id, @newEmail, (err)=> should.exist(err) err.message.should.exist done() @@ -108,7 +124,7 @@ describe "UserAffiliationsManager", -> describe 'deleteAffiliations', -> it 'delete affiliations', (done)-> @request.callsArgWith(1, null, { statusCode: 200 }) - @UserAffiliationsManager.deleteAffiliations @stubbedUser._id, (err) => + @InstitutionsAPI.deleteAffiliations @stubbedUser._id, (err) => should.not.exist(err) @request.calledOnce.should.equal true requestOptions = @request.lastCall.args[0] @@ -120,7 +136,7 @@ describe "UserAffiliationsManager", -> it 'handle error', (done)-> body = errors: 'affiliation error message' @request.callsArgWith(1, null, { statusCode: 518 }, body) - @UserAffiliationsManager.deleteAffiliations @stubbedUser._id, (err) => + @InstitutionsAPI.deleteAffiliations @stubbedUser._id, (err) => should.exist(err) err.message.should.have.string 518 err.message.should.have.string body.errors @@ -131,7 +147,7 @@ describe "UserAffiliationsManager", -> @request.callsArgWith(1, null, { statusCode: 204 }) it 'endorse affiliation', (done)-> - @UserAffiliationsManager.endorseAffiliation @stubbedUser._id, @newEmail, 'Student','Physics', (err)=> + @InstitutionsAPI.endorseAffiliation @stubbedUser._id, @newEmail, 'Student','Physics', (err)=> should.not.exist(err) @request.calledOnce.should.equal true requestOptions = @request.lastCall.args[0] diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsManagerTests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsManagerTests.coffee new file mode 100644 index 0000000000..acf0478ae2 --- /dev/null +++ b/services/web/test/unit/coffee/Institutions/InstitutionsManagerTests.coffee @@ -0,0 +1,30 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../../app/js/Features/Institutions/InstitutionsManager" + +describe "InstitutionsManager", -> + beforeEach -> + @institutionId = 123 + @logger = log: -> + @getInstitutionAffiliations = sinon.stub() + @refreshFeatures = sinon.stub().yields() + @InstitutionsManager = SandboxedModule.require modulePath, requires: + 'logger-sharelatex': @logger + './InstitutionsAPI': + getInstitutionAffiliations: @getInstitutionAffiliations + '../Subscription/FeaturesUpdater': + refreshFeatures: @refreshFeatures + + describe 'upgradeInstitutionUsers', -> + it 'refresh all users Features', (done) -> + affiliations = [ + { user_id: '123abc123abc123abc123abc' } + { user_id: '456def456def456def456def' } + ] + @getInstitutionAffiliations.yields(null, affiliations) + @InstitutionsManager.upgradeInstitutionUsers @institutionId, (error) => + should.not.exist(error) + sinon.assert.calledTwice(@refreshFeatures) + done() diff --git a/services/web/test/unit/coffee/User/UserCreatorTests.coffee b/services/web/test/unit/coffee/User/UserCreatorTests.coffee index 1945453b9e..f9d88e4cf8 100644 --- a/services/web/test/unit/coffee/User/UserCreatorTests.coffee +++ b/services/web/test/unit/coffee/User/UserCreatorTests.coffee @@ -22,7 +22,7 @@ describe "UserCreator", -> "../../models/User": User:@UserModel "logger-sharelatex":{log:->} 'metrics-sharelatex': {timeAsyncMethod: ()->} - "./UserAffiliationsManager": addAffiliation: @addAffiliation + "../Institutions/InstitutionsAPI": addAffiliation: @addAffiliation @email = "bob.oswald@gmail.com" diff --git a/services/web/test/unit/coffee/User/UserDeleterTests.coffee b/services/web/test/unit/coffee/User/UserDeleterTests.coffee index d2be2d211b..fc5f2ef370 100644 --- a/services/web/test/unit/coffee/User/UserDeleterTests.coffee +++ b/services/web/test/unit/coffee/User/UserDeleterTests.coffee @@ -31,7 +31,7 @@ describe "UserDeleter", -> "../Newsletter/NewsletterManager": @NewsletterManager "../Subscription/SubscriptionHandler": @SubscriptionHandler "../Project/ProjectDeleter": @ProjectDeleter - "./UserAffiliationsManager": + "../Institutions/InstitutionsAPI": deleteAffiliations: @deleteAffiliations "logger-sharelatex": @logger = { log: sinon.stub() } diff --git a/services/web/test/unit/coffee/User/UserEmailsControllerTests.coffee b/services/web/test/unit/coffee/User/UserEmailsControllerTests.coffee index 2f67131c65..800a44336f 100644 --- a/services/web/test/unit/coffee/User/UserEmailsControllerTests.coffee +++ b/services/web/test/unit/coffee/User/UserEmailsControllerTests.coffee @@ -34,7 +34,7 @@ describe "UserEmailsController", -> "./UserUpdater": @UserUpdater "../Helpers/EmailHelper": @EmailHelper "./UserEmailsConfirmationHandler": @UserEmailsConfirmationHandler = {} - "./UserAffiliationsManager": endorseAffiliation: @endorseAffiliation + "../Institutions/InstitutionsAPI": endorseAffiliation: @endorseAffiliation "../Errors/Errors": Errors "logger-sharelatex": log: -> console.log(arguments) diff --git a/services/web/test/unit/coffee/User/UserGetterTests.coffee b/services/web/test/unit/coffee/User/UserGetterTests.coffee index c754b9830b..79d9032283 100644 --- a/services/web/test/unit/coffee/User/UserGetterTests.coffee +++ b/services/web/test/unit/coffee/User/UserGetterTests.coffee @@ -22,15 +22,15 @@ describe "UserGetter", -> db: users: findOne: @findOne ObjectId: (id) -> return id settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } } - @getAffiliations = sinon.stub().callsArgWith(1, null, []) + @getUserAffiliations = sinon.stub().callsArgWith(1, null, []) @UserGetter = SandboxedModule.require modulePath, requires: "logger-sharelatex": log:-> "../../infrastructure/mongojs": @Mongo "metrics-sharelatex": timeAsyncMethod: sinon.stub() 'settings-sharelatex': settings - './UserAffiliationsManager': - getAffiliations: @getAffiliations + '../Institutions/InstitutionsAPI': + getUserAffiliations: @getUserAffiliations "../Errors/Errors": Errors describe "getUser", -> @@ -77,7 +77,7 @@ describe "UserGetter", -> institution: { name: 'University Name', isUniversity: true } } ] - @getAffiliations.callsArgWith(1, null, affiliationsData) + @getUserAffiliations.callsArgWith(1, null, affiliationsData) @UserGetter.getUserFullEmails @fakeUser._id, (error, fullEmails) => assert.deepEqual fullEmails, [ { diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index ae49d1d780..f2ac951727 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -25,7 +25,7 @@ describe "UserUpdater", -> @UserUpdater = SandboxedModule.require modulePath, requires: "logger-sharelatex": @logger "./UserGetter": @UserGetter - './UserAffiliationsManager': + '../Institutions/InstitutionsAPI': addAffiliation: @addAffiliation removeAffiliation: @removeAffiliation '../Subscription/FeaturesUpdater': refreshFeatures: @refreshFeatures