mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #670 from sharelatex/ta-manage-affiliations
Add & Remove Affiliations
This commit is contained in:
commit
f2da3155ff
8 changed files with 222 additions and 29 deletions
|
@ -20,7 +20,11 @@ module.exports = UserEmailsController =
|
|||
email = EmailHelper.parseEmail(req.body.email)
|
||||
return res.sendStatus 422 unless email?
|
||||
|
||||
UserUpdater.addEmailAddress userId, email, (error)->
|
||||
affiliationOptions =
|
||||
university: req.body.university
|
||||
role: req.body.role
|
||||
department: req.body.department
|
||||
UserUpdater.addEmailAddress userId, email, affiliationOptions, (error)->
|
||||
return next(error) if error?
|
||||
UserEmailsConfirmationHandler.sendConfirmationEmail userId, email, (err) ->
|
||||
return next(error) if error?
|
||||
|
@ -65,4 +69,4 @@ module.exports = UserEmailsController =
|
|||
else
|
||||
next(error)
|
||||
else
|
||||
res.sendStatus 200
|
||||
res.sendStatus 200
|
||||
|
|
|
@ -3,6 +3,8 @@ metrics = require('metrics-sharelatex')
|
|||
logger = require('logger-sharelatex')
|
||||
db = mongojs.db
|
||||
ObjectId = mongojs.ObjectId
|
||||
settings = require "settings-sharelatex"
|
||||
request = require "request"
|
||||
|
||||
module.exports = UserGetter =
|
||||
getUser: (query, projection, callback = (error, user) ->) ->
|
||||
|
@ -30,11 +32,9 @@ module.exports = UserGetter =
|
|||
return callback error if error?
|
||||
return callback new Error('User not Found') unless user
|
||||
|
||||
fullEmails = user.emails.map (emailData) ->
|
||||
emailData.default = emailData.email == user.email
|
||||
emailData
|
||||
|
||||
callback null, fullEmails
|
||||
getAffiliations userId, (error, affiliationsData) ->
|
||||
return callback error if error?
|
||||
callback null, decorateFullEmails(user.email, user.emails, affiliationsData)
|
||||
|
||||
getUserByMainEmail: (email, projection, callback = (error, user) ->) ->
|
||||
email = email.trim()
|
||||
|
@ -81,6 +81,35 @@ module.exports = UserGetter =
|
|||
return callback(message: 'alread_exists') if user?
|
||||
callback(error)
|
||||
|
||||
decorateFullEmails = (defaultEmail, emailsData, affiliationsData) ->
|
||||
emailsData.map (emailData) ->
|
||||
emailData.default = emailData.email == defaultEmail
|
||||
|
||||
affiliation = affiliationsData.find (aff) -> aff.email == emailData.email
|
||||
if affiliation?
|
||||
{ institution, inferred, role, department } = affiliation
|
||||
emailData.affiliation = { institution, inferred, role, department }
|
||||
else
|
||||
emailsData.affiliation = null
|
||||
|
||||
emailData
|
||||
|
||||
getAffiliations = (userId, callback = (error) ->) ->
|
||||
return callback(null, []) unless settings?.apis?.v1?.url # service is not configured
|
||||
request {
|
||||
method: 'GET'
|
||||
url: "#{settings.apis.v1.url}/api/v2/users/#{userId.toString()}/affiliations"
|
||||
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass }
|
||||
json: true,
|
||||
timeout: 20 * 1000
|
||||
}, (error, response, body) ->
|
||||
return callback(error) if error?
|
||||
unless 200 <= response.statusCode < 300
|
||||
errorMessage = "Couldn't get affiliations: #{response.statusCode}"
|
||||
return callback(new Error(errorMessage))
|
||||
|
||||
callback(null, body)
|
||||
|
||||
[
|
||||
'getUser',
|
||||
'getUserEmail',
|
||||
|
|
|
@ -7,6 +7,8 @@ ObjectId = mongojs.ObjectId
|
|||
UserGetter = require("./UserGetter")
|
||||
EmailHelper = require "../Helpers/EmailHelper"
|
||||
Errors = require "../Errors/Errors"
|
||||
settings = require "settings-sharelatex"
|
||||
request = require "request"
|
||||
|
||||
module.exports = UserUpdater =
|
||||
updateUser: (query, update, callback = (error) ->) ->
|
||||
|
@ -44,29 +46,43 @@ module.exports = UserUpdater =
|
|||
|
||||
# Add a new email address for the user. Email cannot be already used by this
|
||||
# or any other user
|
||||
addEmailAddress: (userId, newEmail, callback) ->
|
||||
addEmailAddress: (userId, newEmail, affiliationOptions, callback) ->
|
||||
unless callback? # affiliationOptions is optional
|
||||
callback = affiliationOptions
|
||||
affiliationOptions = {}
|
||||
|
||||
UserGetter.ensureUniqueEmailAddress newEmail, (error) =>
|
||||
return callback(error) if error?
|
||||
|
||||
update = $push: emails: email: newEmail, createdAt: new Date()
|
||||
@updateUser userId, update, (error) ->
|
||||
addAffiliation userId, newEmail, affiliationOptions, (error) =>
|
||||
if error?
|
||||
logger.err error: error, 'problem updating users emails'
|
||||
logger.err error: error, 'problem adding affiliation'
|
||||
return callback(error)
|
||||
callback()
|
||||
|
||||
update = $push: emails: email: newEmail, createdAt: new Date()
|
||||
@updateUser userId, update, (error) ->
|
||||
if error?
|
||||
logger.err error: error, 'problem updating users emails'
|
||||
return callback(error)
|
||||
callback()
|
||||
|
||||
# remove one of the user's email addresses. The email cannot be the user's
|
||||
# default email address
|
||||
removeEmailAddress: (userId, email, callback) ->
|
||||
query = _id: userId, email: $ne: email
|
||||
update = $pull: emails: email: email
|
||||
@updateUser query, update, (error, res) ->
|
||||
removeAffiliation userId, email, (error) =>
|
||||
if error?
|
||||
logger.err error:error, 'problem removing users email'
|
||||
logger.err error: error, 'problem removing affiliation'
|
||||
return callback(error)
|
||||
if res.n == 0
|
||||
return callback(new Error('Cannot remove default email'))
|
||||
callback()
|
||||
|
||||
query = _id: userId, email: $ne: email
|
||||
update = $pull: emails: email: email
|
||||
@updateUser query, update, (error, res) ->
|
||||
if error?
|
||||
logger.err error:error, 'problem removing users email'
|
||||
return callback(error)
|
||||
if res.n == 0
|
||||
return callback(new Error('Cannot remove email'))
|
||||
callback()
|
||||
|
||||
|
||||
# set the default email address by setting the `email` attribute. The email
|
||||
|
@ -99,6 +115,45 @@ module.exports = UserUpdater =
|
|||
return callback(new Errors.NotFoundError('user id and email do no match'))
|
||||
callback()
|
||||
|
||||
addAffiliation = (userId, email, { university, department, role }, callback = (error) ->) ->
|
||||
makeAffiliationRequest {
|
||||
method: 'POST'
|
||||
path: "/api/v2/users/#{userId.toString()}/affiliations"
|
||||
body: { email, university, department, role }
|
||||
defaultErrorMessage: "Couldn't create affiliation"
|
||||
}, callback
|
||||
|
||||
removeAffiliation = (userId, email, callback = (error) ->) ->
|
||||
email = encodeURIComponent(email)
|
||||
makeAffiliationRequest {
|
||||
method: 'DELETE'
|
||||
path: "/api/v2/users/#{userId.toString()}/affiliations/#{email}"
|
||||
extraSuccessStatusCodes: [404] # `Not Found` responses are considered successful
|
||||
defaultErrorMessage: "Couldn't remove affiliation"
|
||||
}, callback
|
||||
|
||||
makeAffiliationRequest = (requestOptions, callback = (error) ->) ->
|
||||
return callback(null) unless settings?.apis?.v1?.url # service is not configured
|
||||
requestOptions.extraSuccessStatusCodes ||= []
|
||||
request {
|
||||
method: requestOptions.method
|
||||
url: "#{settings.apis.v1.url}#{requestOptions.path}"
|
||||
body: requestOptions.body
|
||||
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass }
|
||||
json: true,
|
||||
timeout: 20 * 1000
|
||||
}, (error, response, body) ->
|
||||
return callback(error) if error?
|
||||
isSuccess = 200 <= response.statusCode < 300
|
||||
isSuccess ||= response.statusCode in requestOptions.extraSuccessStatusCodes
|
||||
unless isSuccess
|
||||
if body?.errors
|
||||
errorMessage = "#{response.statusCode}: #{body.errors}"
|
||||
else
|
||||
errorMessage = "#{requestOptions.defaultErrorMessage}: #{response.statusCode}"
|
||||
return callback(new Error(errorMessage))
|
||||
|
||||
callback(null)
|
||||
|
||||
[
|
||||
'updateUser'
|
||||
|
|
|
@ -4,6 +4,7 @@ User = require "./helpers/User"
|
|||
request = require "./helpers/request"
|
||||
settings = require "settings-sharelatex"
|
||||
{db, ObjectId} = require("../../../app/js/infrastructure/mongojs")
|
||||
MockV1Api = require "./helpers/MockV1Api"
|
||||
|
||||
describe "UserEmails", ->
|
||||
beforeEach (done) ->
|
||||
|
|
|
@ -42,6 +42,14 @@ module.exports = MockV1Api =
|
|||
@exportParams = Object.assign({}, req.body)
|
||||
res.json exportId: @exportId
|
||||
|
||||
app.get "/api/v2/users/:userId/affiliations", (req, res, next) =>
|
||||
res.json []
|
||||
|
||||
app.post "/api/v2/users/:userId/affiliations", (req, res, next) =>
|
||||
res.sendStatus 201
|
||||
|
||||
app.delete "/api/v2/users/:userId/affiliations/:email", (req, res, next) =>
|
||||
res.sendStatus 204
|
||||
|
||||
app.listen 5000, (error) ->
|
||||
throw error if error?
|
||||
|
|
|
@ -53,10 +53,14 @@ describe "UserEmailsController", ->
|
|||
describe 'Add', ->
|
||||
beforeEach ->
|
||||
@newEmail = 'new_email@baz.com'
|
||||
@req.body.email = @newEmail
|
||||
@req.body =
|
||||
email: @newEmail
|
||||
university: { name: 'University Name' }
|
||||
department: 'Department'
|
||||
role: 'Role'
|
||||
@EmailHelper.parseEmail.returns @newEmail
|
||||
@UserUpdater.addEmailAddress.callsArgWith 2, null
|
||||
@UserEmailsConfirmationHandler.sendConfirmationEmail = sinon.stub().yields()
|
||||
@UserUpdater.addEmailAddress.callsArgWith 3, null
|
||||
|
||||
it 'adds new email', (done) ->
|
||||
@UserEmailsController.add @req,
|
||||
|
@ -64,6 +68,13 @@ describe "UserEmailsController", ->
|
|||
code.should.equal 204
|
||||
assertCalledWith @EmailHelper.parseEmail, @newEmail
|
||||
assertCalledWith @UserUpdater.addEmailAddress, @user._id, @newEmail
|
||||
|
||||
affiliationOptions = @UserUpdater.addEmailAddress.lastCall.args[2]
|
||||
Object.keys(affiliationOptions).length.should.equal 3
|
||||
affiliationOptions.university.should.equal @req.body.university
|
||||
affiliationOptions.department.should.equal @req.body.department
|
||||
affiliationOptions.role.should.equal @req.body.role
|
||||
|
||||
done()
|
||||
|
||||
it 'sends an email confirmation', (done) ->
|
||||
|
@ -75,7 +86,6 @@ describe "UserEmailsController", ->
|
|||
|
||||
it 'handles email parse error', (done) ->
|
||||
@EmailHelper.parseEmail.returns null
|
||||
|
||||
@UserEmailsController.add @req,
|
||||
sendStatus: (code) =>
|
||||
code.should.equal 422
|
||||
|
|
|
@ -20,11 +20,15 @@ describe "UserGetter", ->
|
|||
@Mongo =
|
||||
db: users: findOne: @findOne
|
||||
ObjectId: (id) -> return id
|
||||
settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } }
|
||||
@request = sinon.stub()
|
||||
|
||||
@UserGetter = SandboxedModule.require modulePath, requires:
|
||||
"logger-sharelatex": log:->
|
||||
"../../infrastructure/mongojs": @Mongo
|
||||
"metrics-sharelatex": timeAsyncMethod: sinon.stub()
|
||||
'settings-sharelatex': settings
|
||||
'request': @request
|
||||
|
||||
describe "getUser", ->
|
||||
it "should get user", (done)->
|
||||
|
@ -42,6 +46,9 @@ describe "UserGetter", ->
|
|||
done()
|
||||
|
||||
describe "getUserFullEmails", -
|
||||
beforeEach ->
|
||||
@request.callsArgWith(1, null, { statusCode: 200 }, [])
|
||||
|
||||
it "should get user", (done)->
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @fakeUser)
|
||||
projection = email: 1, emails: 1
|
||||
|
@ -59,6 +66,33 @@ describe "UserGetter", ->
|
|||
]
|
||||
done()
|
||||
|
||||
it "should merge affiliation data", (done)->
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @fakeUser)
|
||||
affiliationsData = [
|
||||
{
|
||||
email: 'email1@foo.bar'
|
||||
role: 'Prof'
|
||||
department: 'Maths'
|
||||
inferred: false
|
||||
institution: { name: 'University Name', isUniversity: true }
|
||||
}
|
||||
]
|
||||
@request.callsArgWith(1, null, { statusCode: 200 }, affiliationsData)
|
||||
@UserGetter.getUserFullEmails @fakeUser._id, (error, fullEmails) =>
|
||||
assert.deepEqual fullEmails, [
|
||||
{
|
||||
email: 'email1@foo.bar'
|
||||
default: false
|
||||
affiliation:
|
||||
institution: affiliationsData[0].institution
|
||||
inferred: affiliationsData[0].inferred
|
||||
department: affiliationsData[0].department
|
||||
role: affiliationsData[0].role
|
||||
}
|
||||
{ email: 'email2@foo.bar', default: true }
|
||||
]
|
||||
done()
|
||||
|
||||
describe "getUserbyMainEmail", ->
|
||||
it "query user by main email", (done)->
|
||||
email = 'hello@world.com'
|
||||
|
|
|
@ -11,7 +11,6 @@ describe "UserUpdater", ->
|
|||
|
||||
beforeEach ->
|
||||
tk.freeze(Date.now())
|
||||
@settings = {}
|
||||
@mongojs =
|
||||
db:{}
|
||||
ObjectId:(id)-> return id
|
||||
|
@ -20,12 +19,15 @@ describe "UserUpdater", ->
|
|||
getUserByAnyEmail: sinon.stub()
|
||||
ensureUniqueEmailAddress: sinon.stub()
|
||||
@logger = err: sinon.stub(), log: ->
|
||||
settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } }
|
||||
@request = sinon.stub()
|
||||
@UserUpdater = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex":@settings
|
||||
"logger-sharelatex": @logger
|
||||
"./UserGetter": @UserGetter
|
||||
"../../infrastructure/mongojs":@mongojs
|
||||
"metrics-sharelatex": timeAsyncMethod: sinon.stub()
|
||||
'settings-sharelatex': settings
|
||||
'request': @request
|
||||
|
||||
@stubbedUser =
|
||||
_id: "3131231"
|
||||
|
@ -66,10 +68,10 @@ describe "UserUpdater", ->
|
|||
describe 'addEmailAddress', ->
|
||||
beforeEach ->
|
||||
@UserGetter.ensureUniqueEmailAddress = sinon.stub().callsArgWith(1)
|
||||
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null)
|
||||
@request.callsArgWith(1, null, { statusCode: 201 })
|
||||
|
||||
it 'add email', (done)->
|
||||
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null)
|
||||
|
||||
@UserUpdater.addEmailAddress @stubbedUser._id, @newEmail, (err)=>
|
||||
@UserGetter.ensureUniqueEmailAddress.called.should.equal true
|
||||
should.not.exist(err)
|
||||
|
@ -79,6 +81,27 @@ describe "UserUpdater", ->
|
|||
).should.equal true
|
||||
done()
|
||||
|
||||
it 'add affiliation', (done)->
|
||||
affiliationOptions =
|
||||
university: { id: 1 }
|
||||
role: 'Prof'
|
||||
department: 'Math'
|
||||
@UserUpdater.addEmailAddress @stubbedUser._id, @newEmail, affiliationOptions, (err)=>
|
||||
should.not.exist(err)
|
||||
@request.calledOnce.should.equal true
|
||||
requestOptions = @request.lastCall.args[0]
|
||||
expectedUrl = "v1.url/api/v2/users/#{@stubbedUser._id}/affiliations"
|
||||
requestOptions.url.should.equal expectedUrl
|
||||
requestOptions.method.should.equal 'POST'
|
||||
|
||||
body = requestOptions.body
|
||||
Object.keys(body).length.should.equal 4
|
||||
body.email.should.equal @newEmail
|
||||
body.university.should.equal affiliationOptions.university
|
||||
body.department.should.equal affiliationOptions.department
|
||||
body.role.should.equal affiliationOptions.role
|
||||
done()
|
||||
|
||||
it 'handle error', (done)->
|
||||
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, new Error('nope'))
|
||||
|
||||
|
@ -87,10 +110,21 @@ describe "UserUpdater", ->
|
|||
should.exist(err)
|
||||
done()
|
||||
|
||||
describe 'removeEmailAddress', ->
|
||||
it 'remove email', (done)->
|
||||
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, nMatched: 1)
|
||||
it 'handle affiliation error', (done)->
|
||||
body = errors: 'affiliation error message'
|
||||
@request.callsArgWith(1, null, { statusCode: 422 }, body)
|
||||
@UserUpdater.addEmailAddress @stubbedUser._id, @newEmail, (err)=>
|
||||
err.message.should.have.string 422
|
||||
err.message.should.have.string body.errors
|
||||
@UserUpdater.updateUser.called.should.equal false
|
||||
done()
|
||||
|
||||
describe 'removeEmailAddress', ->
|
||||
beforeEach ->
|
||||
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, nMatched: 1)
|
||||
@request.callsArgWith(1, null, { statusCode: 404 })
|
||||
|
||||
it 'remove email', (done)->
|
||||
@UserUpdater.removeEmailAddress @stubbedUser._id, @newEmail, (err)=>
|
||||
should.not.exist(err)
|
||||
@UserUpdater.updateUser.calledWith(
|
||||
|
@ -99,6 +133,17 @@ describe "UserUpdater", ->
|
|||
).should.equal true
|
||||
done()
|
||||
|
||||
it 'remove affiliation', (done)->
|
||||
@UserUpdater.removeEmailAddress @stubbedUser._id, @newEmail, (err)=>
|
||||
should.not.exist(err)
|
||||
@request.calledOnce.should.equal true
|
||||
requestOptions = @request.lastCall.args[0]
|
||||
expectedUrl = "v1.url/api/v2/users/#{@stubbedUser._id}/affiliations/"
|
||||
expectedUrl += encodeURIComponent(@newEmail)
|
||||
requestOptions.url.should.equal expectedUrl
|
||||
requestOptions.method.should.equal 'DELETE'
|
||||
done()
|
||||
|
||||
it 'handle error', (done)->
|
||||
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, new Error('nope'))
|
||||
|
||||
|
@ -113,6 +158,13 @@ describe "UserUpdater", ->
|
|||
should.exist(err)
|
||||
done()
|
||||
|
||||
it 'handle affiliation error', (done)->
|
||||
@request.callsArgWith(1, null, { statusCode: 500 })
|
||||
@UserUpdater.removeEmailAddress @stubbedUser._id, @newEmail, (err)=>
|
||||
err.message.should.exist
|
||||
@UserUpdater.updateUser.called.should.equal false
|
||||
done()
|
||||
|
||||
describe 'setDefaultEmailAddress', ->
|
||||
it 'set default', (done)->
|
||||
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1)
|
||||
|
|
Loading…
Reference in a new issue