From fc4b82a135395e535fd67e2c3c7879ed04778205 Mon Sep 17 00:00:00 2001 From: Muhamad Fikri Alhawarizmi Date: Sat, 21 Sep 2019 18:58:39 +0700 Subject: [PATCH 1/5] implement new roles --- src/constants.js | 21 ++++++++ src/events/projectMembers/index.js | 16 +++++- src/permissions/constants.js | 3 ++ src/permissions/project.delete.js | 8 ++- src/permissions/projectMember.delete.js | 7 ++- src/routes/projectMemberInvites/create.js | 2 +- src/routes/projectMembers/create.js | 64 +++++++++++++++++++++-- src/routes/projectMembers/update.js | 13 ++++- src/util.js | 5 +- 9 files changed, 126 insertions(+), 13 deletions(-) diff --git a/src/constants.js b/src/constants.js index 129a83d3..afbcaf99 100644 --- a/src/constants.js +++ b/src/constants.js @@ -27,12 +27,20 @@ export const PROJECT_MEMBER_ROLE = { CUSTOMER: 'customer', COPILOT: 'copilot', ACCOUNT_MANAGER: 'account_manager', + PROGRAM_MANAGER: 'program_manager', + ACCOUNT_EXECUTIVE: 'account_executive', + SOLUTION_ARCHITECT: 'solution_architect', + PROJECT_MANAGER: 'project_manager', }; export const PROJECT_MEMBER_MANAGER_ROLES = [ PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.OBSERVER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, ]; export const USER_ROLE = { @@ -42,6 +50,12 @@ export const USER_ROLE = { COPILOT: 'Connect Copilot', CONNECT_ADMIN: 'Connect Admin', COPILOT_MANAGER: 'Connect Copilot Manager', + BUSINESS_DEVELOPMENT_REPRESENTATIVE: 'Business Development Representative', + PRESALES: 'Presales', + ACCOUNT_EXECUTIVE: 'Account Executive', + PROGRAM_MANAGER: 'Program Manager', + SOLUTION_ARCHITECT: 'Solution Architect', + PROJECT_MANAGER: 'Project Manager', }; export const ADMIN_ROLES = [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN]; @@ -51,6 +65,13 @@ export const MANAGER_ROLES = [ USER_ROLE.MANAGER, USER_ROLE.TOPCODER_ACCOUNT_MANAGER, USER_ROLE.COPILOT_MANAGER, + USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE, + USER_ROLE.PRESALES, + USER_ROLE.ACCOUNT_EXECUTIVE, + + USER_ROLE.PROGRAM_MANAGER, + USER_ROLE.SOLUTION_ARCHITECT, + USER_ROLE.PROJECT_MANAGER, ]; export const EVENT = { diff --git a/src/events/projectMembers/index.js b/src/events/projectMembers/index.js index 92380e7d..f5750362 100644 --- a/src/events/projectMembers/index.js +++ b/src/events/projectMembers/index.js @@ -44,7 +44,13 @@ const projectMemberAddedHandler = Promise.coroutine(function* a(logger, msg, cha const newMember = JSON.parse(msg.content.toString()); const projectId = newMember.projectId; const directUpdatePromise = Promise.coroutine(function* () { // eslint-disable-line func-names - if (_.indexOf([PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.MANAGER], newMember.role) > -1) { + if (_.indexOf([ + PROJECT_MEMBER_ROLE.COPILOT, + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ], newMember.role) > -1) { // add copilot/update manager permissions operation promise const directProjectId = yield models.Project.getDirectProjectId(projectId); if (directProjectId) { @@ -116,7 +122,13 @@ const projectMemberRemovedHandler = Promise.coroutine(function* (logger, msg, ch const projectId = member.projectId; // remove copilot/manager operation promise const updateDirectProjectPromise = Promise.coroutine(function* () { // eslint-disable-line func-names - if (_.indexOf([PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.MANAGER], member.role) > -1) { + if (_.indexOf([ + PROJECT_MEMBER_ROLE.COPILOT, + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ], member.role) > -1) { const directProjectId = yield models.Project.getDirectProjectId(projectId); if (directProjectId) { const token = yield util.getM2MToken(); diff --git a/src/permissions/constants.js b/src/permissions/constants.js index a047452d..570bc152 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -25,6 +25,9 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export ROLES_COPILOT_AND_ABOVE: { topcoderRoles: ADMIN_ROLES, projectRoles: [ + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.COPILOT, ], diff --git a/src/permissions/project.delete.js b/src/permissions/project.delete.js index 9fe49fc8..1d0841ca 100644 --- a/src/permissions/project.delete.js +++ b/src/permissions/project.delete.js @@ -23,7 +23,13 @@ module.exports = freq => new Promise((resolve, reject) => { const hasAccess = util.hasAdminRole(req) || !_.isUndefined(_.find(members, m => m.userId === req.authUser.userId && ((m.role === PROJECT_MEMBER_ROLE.CUSTOMER && m.isPrimary) || - m.role === PROJECT_MEMBER_ROLE.MANAGER))); + [ + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ].includes(m.role) + ))); if (!hasAccess) { // user is not an admin nor is a registered project member diff --git a/src/permissions/projectMember.delete.js b/src/permissions/projectMember.delete.js index 0f0ec42c..5f4bb946 100644 --- a/src/permissions/projectMember.delete.js +++ b/src/permissions/projectMember.delete.js @@ -25,7 +25,12 @@ module.exports = freq => new Promise((resolve, reject) => { const memberToBeRemoved = _.find(members, m => m.id === prjMemberId); // check if auth user has acecss to this project const hasAccess = util.hasAdminRole(req) - || (authMember && memberToBeRemoved && (authMember.role === PROJECT_MEMBER_ROLE.MANAGER || + || (authMember && memberToBeRemoved && ([ + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ].includes(authMember.role) || (authMember.role === PROJECT_MEMBER_ROLE.CUSTOMER && authMember.isPrimary && memberToBeRemoved.role === PROJECT_MEMBER_ROLE.CUSTOMER) || memberToBeRemoved.userId === req.authUser.userId)); diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index fa5f6dcd..6df85147 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -289,7 +289,7 @@ module.exports = [ } return Promise.all(promises).then((rolesList) => { if (!!invite.userIds && _.includes(PROJECT_MEMBER_MANAGER_ROLES, invite.role)) { - req.log.debug('Chekcing if userId is allowed as manager'); + req.log.debug('Checking if userId is allowed as manager'); const forbidUserList = []; _.zip(invite.userIds, rolesList).forEach((data) => { const [userId, roles] = data; diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index f3a1fc0c..93510e86 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -18,7 +18,15 @@ const createProjectMemberValidations = { param: Joi.object() .keys({ role: Joi.any() - .valid(PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT), + .valid( + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, + PROJECT_MEMBER_ROLE.COPILOT, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE, + ), }), }, }; @@ -39,9 +47,45 @@ module.exports = [ return next(err); } + if (PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT === targetRole && + !util.hasRoles(req, [USER_ROLE.SOLUTION_ARCHITECT])) { + const err = new Error(`Only solution architect is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + + if (PROJECT_MEMBER_ROLE.PROJECT_MANAGER === targetRole && + !util.hasRoles(req, [USER_ROLE.PROJECT_MANAGER])) { + const err = new Error(`Only project manager is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + + if (PROJECT_MEMBER_ROLE.PROGRAM_MANAGER === targetRole && + !util.hasRoles(req, [USER_ROLE.PROGRAM_MANAGER])) { + const err = new Error(`Only program manager is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + + if (PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE === targetRole && + !util.hasRoles(req, [USER_ROLE.ACCOUNT_EXECUTIVE])) { + const err = new Error(`Only account executive is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + if (PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER === targetRole && - !util.hasRoles(req, [USER_ROLE.MANAGER, USER_ROLE.TOPCODER_ACCOUNT_MANAGER])) { - const err = new Error(`Only manager or account manager is able to join as ${targetRole}`); + !util.hasRoles(req, [ + USER_ROLE.MANAGER, + USER_ROLE.TOPCODER_ACCOUNT_MANAGER, + USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE, + USER_ROLE.PRESALES, + ])) { + const err = new Error( + `Only manager, account manager, business development representative, + or presales are able to join as ${targetRole}`, + ); err.status = 401; return next(err); } @@ -53,10 +97,22 @@ module.exports = [ } } else if (util.hasRoles(req, [USER_ROLE.MANAGER, USER_ROLE.CONNECT_ADMIN])) { targetRole = PROJECT_MEMBER_ROLE.MANAGER; - } else if (util.hasRoles(req, [USER_ROLE.TOPCODER_ACCOUNT_MANAGER])) { + } else if (util.hasRoles(req, [ + USER_ROLE.TOPCODER_ACCOUNT_MANAGER, + USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE, + USER_ROLE.PRESALES, + ])) { targetRole = PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER; } else if (util.hasRoles(req, [USER_ROLE.COPILOT, USER_ROLE.CONNECT_ADMIN])) { targetRole = PROJECT_MEMBER_ROLE.COPILOT; + } else if (util.hasRoles(req, [USER_ROLE.ACCOUNT_EXECUTIVE])) { + targetRole = PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE; + } else if (util.hasRoles(req, [USER_ROLE.PROGRAM_MANAGER])) { + targetRole = PROJECT_MEMBER_ROLE.PROGRAM_MANAGER; + } else if (util.hasRoles(req, [USER_ROLE.SOLUTION_ARCHITECT])) { + targetRole = PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT; + } else if (util.hasRoles(req, [USER_ROLE.PROJECT_MANAGER])) { + targetRole = PROJECT_MEMBER_ROLE.PROJECT_MANAGER; } else { const err = new Error('Only copilot or manager is able to call this endpoint'); err.status = 401; diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index d187814b..f0146473 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -16,8 +16,17 @@ const updateProjectMemberValdiations = { body: { param: Joi.object().keys({ isPrimary: Joi.boolean(), - role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, - PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.OBSERVER).required(), + role: Joi.any().valid( + PROJECT_MEMBER_ROLE.CUSTOMER, + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, + PROJECT_MEMBER_ROLE.COPILOT, + PROJECT_MEMBER_ROLE.OBSERVER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + ).required(), }), }, }; diff --git a/src/util.js b/src/util.js index 8e2507f5..fde9073b 100644 --- a/src/util.js +++ b/src/util.js @@ -16,12 +16,12 @@ import config from 'config'; import urlencode from 'urlencode'; import elasticsearch from 'elasticsearch'; import Promise from 'bluebird'; +import models from './models'; // import AWS from 'aws-sdk'; import { ADMIN_ROLES, TOKEN_SCOPES, EVENT, PROJECT_MEMBER_ROLE, VALUE_TYPE, ESTIMATION_TYPE } from './constants'; const exec = require('child_process').exec; -const models = require('./models').default; const tcCoreLibAuth = require('tc-core-library-js').auth; const m2m = tcCoreLibAuth.m2m(config); @@ -474,7 +474,8 @@ _.assignIn(util, { req.log.debug('creating member', member); let newMember = null; // register member - + console.dir('models') + console.dir(models) return models.ProjectMember.create(member) .then((_newMember) => { newMember = _newMember.get({ plain: true }); From dd7a3bfe7c7b74dc89c1c16eb11d9bc9e9a8302b Mon Sep 17 00:00:00 2001 From: Muhamad Fikri Alhawarizmi Date: Sun, 22 Sep 2019 07:51:46 +0700 Subject: [PATCH 2/5] remove console --- src/util.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/util.js b/src/util.js index fde9073b..3b7cdeef 100644 --- a/src/util.js +++ b/src/util.js @@ -474,8 +474,6 @@ _.assignIn(util, { req.log.debug('creating member', member); let newMember = null; // register member - console.dir('models') - console.dir(models) return models.ProjectMember.create(member) .then((_newMember) => { newMember = _newMember.get({ plain: true }); From fc272ce88da0e1c6ad025ea871dfb7731c8d7522 Mon Sep 17 00:00:00 2001 From: Muhamad Fikri Alhawarizmi Date: Tue, 1 Oct 2019 10:32:55 +0700 Subject: [PATCH 3/5] add roles that can join as manager --- src/routes/projectMembers/create.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 93510e86..1309593f 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -81,10 +81,14 @@ module.exports = [ USER_ROLE.TOPCODER_ACCOUNT_MANAGER, USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE, USER_ROLE.PRESALES, + USER_ROLE.ACCOUNT_EXECUTIVE, + USER_ROLE.PROGRAM_MANAGER, + USER_ROLE.SOLUTION_ARCHITECT, + USER_ROLE.PROJECT_MANAGER, ])) { const err = new Error( - `Only manager, account manager, business development representative, - or presales are able to join as ${targetRole}`, + // eslint-disable-next-line max-len + `Only manager, account manager, business development representative, account executive, program manager, project manager, solution architect, or presales are able to join as ${targetRole}`, ); err.status = 401; return next(err); From d7279f269cdac99cf7b4d70e1aafc1e631643d47 Mon Sep 17 00:00:00 2001 From: Muhamad Fikri Alhawarizmi Date: Tue, 1 Oct 2019 10:35:10 +0700 Subject: [PATCH 4/5] add program manager, project manager, and solution architect roles to whom can activate project --- src/routes/projects/update.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 2567cbfb..7839e359 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -221,7 +221,9 @@ module.exports = [ const members = req.context.currentProjectMembers; const validRoles = [ PROJECT_MEMBER_ROLE.MANAGER, - PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, ].map(x => x.toLowerCase()); const matchRole = role => _.indexOf(validRoles, role.toLowerCase()) >= 0; if (updatedProps.status === PROJECT_STATUS.ACTIVE && From f95a249b013d7bbee1f202e840b59e2841b8839e Mon Sep 17 00:00:00 2001 From: Muhamad Fikri Alhawarizmi Date: Tue, 1 Oct 2019 10:39:44 +0700 Subject: [PATCH 5/5] MEMBER_JOINED_EVENT event should be sent when user has any manager role including new manager roles --- src/events/busApi.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/events/busApi.js b/src/events/busApi.js index 978e7a92..926285e8 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -122,16 +122,17 @@ module.exports = (app, logger) => { logger.debug('receive PROJECT_MEMBER_ADDED event'); let eventType; - switch (member.role) { - case PROJECT_MEMBER_ROLE.MANAGER: - eventType = BUS_API_EVENT.MEMBER_JOINED_MANAGER; - break; - case PROJECT_MEMBER_ROLE.COPILOT: - eventType = BUS_API_EVENT.MEMBER_JOINED_COPILOT; - break; - default: - eventType = BUS_API_EVENT.MEMBER_JOINED; - break; + if ([ + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ].includes(member.role)) { + eventType = BUS_API_EVENT.MEMBER_JOINED_MANAGER; + } else if (member.role === PROJECT_MEMBER_ROLE.COPILOT) { + eventType = BUS_API_EVENT.MEMBER_JOINED_COPILOT; + } else { + eventType = BUS_API_EVENT.MEMBER_JOINED; } const projectId = _.parseInt(req.params.projectId);