Skip to content
Snippets Groups Projects
Commit c325b1be authored by Sebastian's avatar Sebastian
Browse files

feat(component-invite): add teams

parent fe74fe27
No related branches found
No related tags found
No related merge requests found
const logger = require('@pubsweet/logger')
const config = require('config')
const helpers = require('../helpers/helpers')
const teamHelper = require('../helpers/Team')
const mailService = require('pubsweet-component-mail-service')
const configRoles = config.get('roles')
......@@ -29,6 +30,27 @@ module.exports = async (
.json({ error: `Role ${role} cannot be set on collections` })
}
if (!reqUser.editorInChief && reqUser.teams === undefined) {
return res
.status(403)
.json({ error: `User ${reqUser.username} is not part of any teams` })
} else if (reqUser.editorInChief === false) {
const matchingTeams = await teamHelper.getMatchingTeams(
reqUser.teams,
models.Team,
collectionId,
role,
)
if (matchingTeams.length === 0) {
return res.status(403).json({
error: `User ${
reqUser.email
} cannot invite a ${role} to ${collectionId}`,
})
}
}
try {
await models.Collection.find(collectionId)
} catch (e) {
......@@ -40,7 +62,6 @@ module.exports = async (
try {
let user = await models.User.findByEmail(email)
user.roles.push(role)
const assignation = {
type: role,
hasAnswer: false,
......@@ -56,8 +77,9 @@ module.exports = async (
url,
)
// TODO: create a team and add the team id to the user's teams array
await teamHelper.setupManuscriptTeam(models, user, collectionId, role)
user = await models.User.find(user)
return res.status(200).json(user)
} catch (e) {
const notFoundError = await helpers.handleNotFoundError(e, 'user')
......
const logger = require('@pubsweet/logger')
const helpers = require('../helpers/helpers')
const mailService = require('pubsweet-component-mail-service')
const config = require('config')
module.exports = async (body, UserModel, res, url) => {
const configRoles = config.get('roles')
module.exports = async (body, models, res, url) => {
const { email, role, firstName, lastName, affiliation, title } = body
if (!configRoles.inviteRights.admin.includes(role)) {
logger.error(`admin ${email} tried to invite a ${role}`)
return res
.status(403)
.json({ error: `admin tried to invite an invalid role: ${role}` })
}
try {
const user = await UserModel.findByEmail(email)
const user = await models.User.findByEmail(email)
if (user) {
logger.error('someone tried to invite existing user')
logger.error(`admin tried to invite existing user: ${email}`)
return res.status(400).json({ error: 'User already exists' })
}
} catch (e) {
......@@ -20,12 +30,12 @@ module.exports = async (body, UserModel, res, url) => {
const newUser = await helpers.createNewUser(
email,
role,
firstName,
lastName,
affiliation,
title,
UserModel,
models.User,
role,
)
await mailService.setupInviteEmail(
......
const logger = require('@pubsweet/logger')
const config = require('config')
const configRoles = config.get('roles')
const createNewTeam = async (collectionId, role, userId, TeamModel) => {
let permissions, group, name
switch (role) {
case 'handlingEditor':
permissions = 'handlingEditor'
group = 'handlingEditor'
name = 'Handling Editor'
break
case 'reviewer':
permissions = 'reviewer'
group = 'reviewer'
name = 'Reviewer'
break
default:
break
}
const teamBody = {
teamType: {
name: role,
permissions,
},
group,
name,
object: {
type: 'collection',
id: collectionId,
},
members: [userId],
}
const team = new TeamModel(teamBody)
try {
await team.save()
} catch (e) {
logger.error(e)
}
}
const setupEiCTeams = async (models, user) => {
const collections = await models.Collection.all()
const teams = await models.Team.all()
user.teams = []
const collectionIDs = []
/* eslint-disable */
for (const collection of collections) {
for (let team of teams) {
if (
team.group === 'editorInChief' &&
team.object.type === 'collection' &&
team.object.id === collection.id
) {
collectionIDs.push(collection.id)
team.members.push(user.id)
try {
team = await team.updateProperties(team)
team = await team.save()
} catch (e) {
logger.error(e)
}
}
}
if (!collectionIDs.includes(collection.id)) {
await createNewTeam(collection.id, 'editorInChief', user.id, models.Team)
}
}
/* eslint-enable */
user = await models.User.find(user.id)
return user
}
const setupManuscriptTeam = async (models, user, collectionId, role) => {
const teams = await models.Team.all()
user.teams = []
const filteredTeams = teams.filter(
team =>
team.group === role &&
team.object.type === 'collection' &&
team.object.id === collectionId,
)
if (filteredTeams.length > 0) {
let team = filteredTeams[0]
team.members.push(user.id)
try {
team = await team.updateProperties(team)
team = await team.save()
} catch (e) {
logger.error(e)
}
} else {
await createNewTeam(collectionId, role, user.id, models.Team)
}
}
const getMatchingTeams = (teams, TeamModel, collectionId, role) =>
teams
.map(async teamId => {
const team = await TeamModel.find(teamId)
if (
team.object.id === collectionId &&
configRoles.inviteRights[team.group].includes(role)
) {
return team
}
return null
})
.filter(Boolean)
module.exports = {
createNewTeam,
setupEiCTeams,
setupManuscriptTeam,
getMatchingTeams,
}
......@@ -58,26 +58,6 @@ const validateEmailAndToken = async (email, token, userModel) => {
}
}
const hasInviteRight = (configRoles, userRoles, role) => {
const includesRole = existingRole =>
configRoles.inviteRights[existingRole].includes(role)
if (!userRoles.some(includesRole)) {
logger.error(`incorrect role when inviting a user`)
return {
success: false,
status: 403,
message: `${userRoles} cannot invite a ${role}`,
}
}
return {
success: true,
status: null,
message: null,
}
}
const handleNotFoundError = async (error, item) => {
const response = {
success: false,
......@@ -95,72 +75,40 @@ const handleNotFoundError = async (error, item) => {
return response
}
const createNewTeam = async (collectionId, user, TeamModel) => {
let permissions, group, name
switch (user.roles[0]) {
case 'handlingEditor':
permissions = 'editor'
group = 'editor'
name = 'Handling Editor'
break
case 'reviewer':
permissions = 'reviewer'
group = 'reviewer'
name = 'Reviewer'
break
default:
break
}
const teamBody = {
teamType: {
name: user.roles[0],
permissions,
},
group,
name,
object: {
type: 'collection',
id: collectionId,
},
members: [user.id],
}
const team = new TeamModel(teamBody)
await team.save()
}
const createNewUser = async (
email,
role,
firstName,
lastName,
affiliation,
title,
UserModel,
role,
) => {
const userBody = {
username: uuid.v4().slice(0, 8),
email,
password: uuid.v4(),
roles: [role],
passwordResetToken: crypto.randomBytes(32).toString('hex'),
isConfirmed: false,
firstName,
lastName,
affiliation,
title,
editorInChief: role === 'editorInChief',
admin: role === 'admin',
}
let newUser = new UserModel(userBody)
newUser = await newUser.save()
return newUser
try {
newUser = await newUser.save()
return newUser
} catch (e) {
logger.error(e)
}
}
module.exports = {
checkForUndefinedParams,
validateEmailAndToken,
hasInviteRight,
handleNotFoundError,
createNewTeam,
createNewUser,
}
const logger = require('@pubsweet/logger')
const get = require('lodash/get')
const config = require('config')
const helpers = require('../helpers/helpers')
const configRoles = config.get('roles')
module.exports = models => async (req, res) => {
const { email, role } = req.body
......@@ -16,15 +13,6 @@ module.exports = models => async (req, res) => {
const reqUser = await models.User.find(req.user)
const collectionId = get(req, 'params.collectionId')
if (reqUser.admin) reqUser.roles = reqUser.roles || ['admin']
const inviteRight = helpers.hasInviteRight(configRoles, reqUser.roles, role)
if (!inviteRight.success) {
res.status(inviteRight.status).json({
error: inviteRight.message,
})
logger.error(`incorrect role when inviting a ${role}`)
return
}
const url = `${req.protocol}://${req.get('host')}`
if (collectionId) {
......@@ -42,12 +30,12 @@ module.exports = models => async (req, res) => {
if (reqUser.admin)
return require('../controllers/inviteGlobalRole')(
req.body,
models.User,
models,
res,
url,
)
res.status(403).json({
error: `${reqUser.roles} cannot invite a ${role} without a collection`,
error: `${reqUser.username} cannot invite a ${role} without a collection`,
})
}
const users = require('./users')
module.exports = {
teamType: {
name: 'editorInChief',
permissions: 'editor',
const { editorInChief, handlingEditor, reviewer } = users
const teams = {
eicTeam: {
teamType: {
name: 'editorInChief',
permissions: 'editorInChief',
},
group: 'editorInChief',
name: 'Editor in Chief',
object: {
type: 'collection',
id: '123',
},
members: [editorInChief.id],
save: jest.fn(() => teams.eicTeam),
updateProperties: jest.fn(() => teams.eicTeam),
},
group: 'editor',
name: 'Editor in Chief',
object: {
type: 'collection',
id: '123',
heTeam: {
teamType: {
name: 'handlingEditor',
permissions: 'handlingEditor',
},
group: 'handlingEditor',
name: 'HandlingEditor',
object: {
type: 'collection',
id: '123',
},
members: [handlingEditor.id],
save: jest.fn(() => teams.heTeam),
updateProperties: jest.fn(() => teams.heTeam),
},
reviewerTeam: {
teamType: {
name: 'reviewer',
permissions: 'reviewer',
},
group: 'reviewer',
name: 'Reviewer',
object: {
type: 'collection',
id: '123',
},
members: [reviewer.id],
save: jest.fn(() => teams.reviewerTeam),
updateProperties: jest.fn(() => teams.reviewerTeam),
},
members: [users.editorInChief.id],
}
module.exports = teams
const collections = require('./collections')
const { standardCollection } = collections
const users = {
admin: {
type: 'user',
......@@ -17,14 +18,14 @@ const users = {
password: 'test1234',
admin: false,
id: 'editor123',
roles: ['editorInChief'],
passwordResetToken: 'token123',
firstName: 'vlad',
lastName: 'dracul',
firstName: 'john',
lastName: 'smith',
affiliation: 'MIT',
title: 'prof',
title: 'Mr',
save: jest.fn(() => users.editorInChief),
isConfirmed: false,
editorInChief: true,
},
handlingEditor: {
type: 'user',
......@@ -33,7 +34,6 @@ const users = {
password: 'test',
admin: false,
id: 'handling123',
roles: ['handlingEditor'],
assignations: [
{
type: 'handlingEditor',
......@@ -43,6 +43,7 @@ const users = {
},
],
save: jest.fn(() => users.handlingEditor),
editorInChief: false,
},
author: {
type: 'user',
......@@ -51,15 +52,30 @@ const users = {
password: 'test',
admin: false,
id: 'author123',
roles: ['author'],
passwordResetToken: 'token123',
firstName: 'leopold',
lastName: 'smith',
affiliation: 'MIT',
title: 'mr',
title: 'Mr',
save: jest.fn(() => users.author),
isConfirmed: false,
},
reviewer: {
type: 'user',
username: 'reviewer',
email: 'reviewer@example.com',
password: 'test',
admin: false,
id: 'reviewer123',
passwordResetToken: 'token123',
firstName: 'angela',
lastName: 'smith',
affiliation: 'MIT',
title: 'Ms',
save: jest.fn(() => users.reviewer),
isConfirmed: false,
teams: [],
},
}
module.exports = users
......@@ -4,6 +4,7 @@ process.env.SUPPRESS_NO_CONFIG_WARNING = true
const httpMocks = require('node-mocks-http')
const random = require('lodash/random')
const fixtures = require('./fixtures/fixtures')
const UserMock = require('./mocks/User')
const Chance = require('chance')
const TeamMock = require('./mocks/Team')
......@@ -16,7 +17,7 @@ const chance = new Chance()
const globalRoles = ['editorInChief', 'author', 'admin']
const manuscriptRoles = ['handlingEditor', 'reviewer']
const buildModels = (collection, findUser, emailUser) => {
const buildModels = (collection, findUser, emailUser, team) => {
const models = {
User: {},
Collection: {
......@@ -29,12 +30,18 @@ const buildModels = (collection, findUser, emailUser) => {
},
Team: {},
}
UserMock.find = jest.fn(
() =>
findUser instanceof Error
? Promise.reject(findUser)
: Promise.resolve(findUser),
)
UserMock.find = jest.fn(user => {
const foundUser = Object.values(fixtures.users).find(
fixUser => fixUser.id === user.id,
)
if (foundUser === undefined) {
return Promise.reject(findUser)
}
return Promise.resolve(foundUser)
})
UserMock.findByEmail = jest.fn(
() =>
emailUser instanceof Error
......@@ -42,6 +49,12 @@ const buildModels = (collection, findUser, emailUser) => {
: Promise.resolve(emailUser),
)
TeamMock.find = jest.fn(
() =>
team instanceof Error ? Promise.reject(team) : Promise.resolve(team),
)
TeamMock.all = jest.fn(() => Object.values(fixtures.teams))
models.User = UserMock
models.Team = TeamMock
return models
......@@ -63,6 +76,7 @@ notFoundError.status = 404
const { admin, editorInChief, handlingEditor, author } = fixtures.users
const { standardCollection } = fixtures.collections
const { heTeam } = fixtures.teams
const postInvitePath = '../routes/postInvite'
describe('Post invite route handler', () => {
it('should return success when the admin invites a global role', async () => {
......@@ -76,7 +90,6 @@ describe('Post invite route handler', () => {
expect(res.statusCode).toBe(200)
const data = JSON.parse(res._getData())
expect(data.roles[0]).toEqual(body.role)
expect(data.firstName).toEqual(body.firstName)
expect(data.email).toEqual(body.email)
expect(data.admin).toEqual(body.admin)
......@@ -108,7 +121,9 @@ describe('Post invite route handler', () => {
await require(postInvitePath)(models)(req, res)
expect(res.statusCode).toBe(403)
const data = JSON.parse(res._getData())
expect(data.error).toEqual(`admin cannot invite a ${body.role}`)
expect(data.error).toEqual(
`admin tried to invite an invalid role: ${body.role}`,
)
})
it('should return an error params are missing', async () => {
delete body.email
......@@ -137,9 +152,7 @@ describe('Post invite route handler', () => {
await require(postInvitePath)(models)(req, res)
expect(res.statusCode).toBe(403)
const data = JSON.parse(res._getData())
expect(data.error).toEqual(
`${req.user.roles[0]} cannot invite a ${body.role}`,
)
expect(data.error).toEqual(`Role ${body.role} cannot be set on collections`)
})
it('should return an error when an editorInChief invites a handlingEditor without a collection', async () => {
body.role = 'handlingEditor'
......@@ -155,7 +168,7 @@ describe('Post invite route handler', () => {
expect(res.statusCode).toBe(403)
const data = JSON.parse(res._getData())
expect(data.error).toEqual(
`${req.user.roles} cannot invite a ${body.role} without a collection`,
`${req.user.username} cannot invite a ${body.role} without a collection`,
)
})
it('should return an error when an handlingEditor invites a reviewer without a collection', async () => {
......@@ -172,7 +185,7 @@ describe('Post invite route handler', () => {
expect(res.statusCode).toBe(403)
const data = JSON.parse(res._getData())
expect(data.error).toEqual(
`${req.user.roles} cannot invite a ${body.role} without a collection`,
`${req.user.username} cannot invite a ${body.role} without a collection`,
)
})
it('should return an error when inviting an existing user', async () => {
......@@ -201,12 +214,11 @@ describe('Post invite route handler', () => {
req.user = editorInChief
req.params.collectionId = '123'
const res = httpMocks.createResponse()
const models = buildModels(standardCollection, editorInChief, author)
const models = buildModels(standardCollection, author, author)
await require(postInvitePath)(models)(req, res)
expect(res.statusCode).toBe(200)
const data = JSON.parse(res._getData())
expect(data.roles).toContain(body.role)
expect(data.email).toEqual(body.email)
expect(data.assignations[0].collectionId).toEqual(req.params.collectionId)
})
......@@ -218,6 +230,7 @@ describe('Post invite route handler', () => {
const req = httpMocks.createRequest({
body,
})
handlingEditor.teams = [heTeam.id]
req.user = handlingEditor
req.params.collectionId = '123'
const res = httpMocks.createResponse()
......@@ -226,7 +239,6 @@ describe('Post invite route handler', () => {
expect(res.statusCode).toBe(200)
const data = JSON.parse(res._getData())
expect(data.roles).toContain(body.role)
expect(data.email).toEqual(body.email)
expect(data.assignations[0].collectionId).toEqual(req.params.collectionId)
})
......
......@@ -4,7 +4,7 @@ module.exports = {
from: process.env.EMAIL_SENDER,
transport: {
SES: new AWS.SES({
accessKeyId: process.env.AWS_SES_ACCESS_KEYs,
accessKeyId: process.env.AWS_SES_ACCESS_KEY,
secretAccessKey: process.env.AWS_SES_SECRET_KEY,
region: process.env.AWS_SES_REGION,
}),
......
......@@ -87,13 +87,14 @@ module.exports = {
],
user: {
name: Joi.string(),
roles: Joi.array(),
isConfirmed: Joi.boolean(),
firstName: Joi.string().allow(''),
lastName: Joi.string().allow(''),
affiliation: Joi.string().allow(''),
title: Joi.string().allow(''),
assignations: Joi.array(),
teams: Joi.array(),
editorInChief: Joi.boolean(),
},
team: {
group: Joi.string(),
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment