diff --git a/src/constants.js b/src/constants.js index 9fa3c75..140a1b4 100644 --- a/src/constants.js +++ b/src/constants.js @@ -44,6 +44,10 @@ module.exports = { COPILOT: 'copilot', MANAGER: 'manager', CUSTOMER: 'customer' + }, + CHALLENGE_ROLES: { + COPILOT: 'Copilot', + MANAGER: 'Manager' } }, VANILLA: { diff --git a/src/modules/user_management/handler.js b/src/modules/user_management/handler.js index 8720f24..11f0566 100644 --- a/src/modules/user_management/handler.js +++ b/src/modules/user_management/handler.js @@ -1,4 +1,5 @@ const config = require('config') +const _ = require('lodash') const util = require('util') const constants = require('../../constants') const logger = require('../../utils/logger.util') @@ -6,6 +7,8 @@ const { manageRocketUser } = require('../../services/rokect') const { manageVanillaUser } = require('../../services/vanilla') const { getTopcoderUserHandle: getUserHandle, + getAllChallengeRolesForUser, + getProjectRoleForUser, processPayload } = require('./helpers') @@ -34,6 +37,23 @@ function canProcessEvent (payload, topic) { return true } +async function processPayloadItem (item, topic) { + const data = processPayload(item, topic) + try { + data.handle = data.handle || (await getUserHandle(data.userId)) + data.projectRole = (await getProjectRoleForUser(data.challengeId, data.userId)) + data.challengeRoles = (await getAllChallengeRolesForUser(data.challengeId, data.userId)) + } catch (err) { + logger.error(util.inspect(err)) + } + for (const service of services) { + await service(data) + .catch(err => { + logger.error(util.inspect(err)) + }) + } +} + /** * Handle a set of messages from the Kafka topic * @param {Array} messageSet @@ -48,19 +68,12 @@ async function handler (messageSet, topic) { if (!canProcessEvent(item, topic)) { continue } - - const data = processPayload(item, topic) - try { - data.handle = data.handle || (await getUserHandle(data.userId)) - } catch (err) { - logger.error(util.inspect(err)) - continue - } - for (const service of services) { - await service(data) - .catch(err => { - logger.error(util.inspect(err)) - }) + if (_.isArray(item)) { + for (const i of item) { + await processPayloadItem(i, topic) + } + } else { + await processPayloadItem(item, topic) } } } diff --git a/src/modules/user_management/helpers.js b/src/modules/user_management/helpers.js index ca57e3f..90d6a3b 100644 --- a/src/modules/user_management/helpers.js +++ b/src/modules/user_management/helpers.js @@ -61,7 +61,62 @@ async function getTopcoderUserHandle (userId) { return userHandle } +/** + * Get a list of role names for User + * @param challengeId + * @param userId + * @returns {Promise<*[]>} + */ +async function getAllChallengeRolesForUser (challengeId, userId) { + const { body: challengeRoles, status: challengeRolesStatus } = await topcoderApi.getAllChallengeRoles(challengeId) + if (challengeRolesStatus !== 200) { + throw new Error('Couldn\'t load Topcoder Challenge Roles', challengeRoles) + } + const roles = _.filter(challengeRoles, { memberId: userId }) + + const { body: resourceRoles, status: allRolesStatus } = await topcoderApi.getAllRoles() + if (allRolesStatus !== 200) { + throw new Error('Couldn\'t load Topcoder Resource Roles', resourceRoles) + } + + if (_.isEmpty(resourceRoles)) { + throw new Error(`No Resource Roles for ${challengeId}`) + } + + _.map(roles, function (obj) { + // add the properties from second array matching the roleId + return _.assign(obj, _.find(resourceRoles, { id: obj.roleId })) + }) + + // Get all role names + return _.map(roles, 'name') +} + +async function getProjectRoleForUser (challengeId, userId) { + const { body: challenge, status: challengeStatus } = await topcoderApi.getChallenge(challengeId) + if (challengeStatus !== 200) { + throw new Error(`Couldn't load Topcoder Challenge by challengeId ${challengeId}`) + } + const { body: project, status: projectStatus } = await topcoderApi.getProject(challenge.projectId) + + if (projectStatus !== 200) { + throw new Error(`Couldn't load Topcoder Project by projectId ${challenge.projectId}`) + } + + // userId - string , x.userId - int + /* eslint-disable eqeqeq */ + const member = _.filter(project.members, x => x.userId == userId) + // User doesn't have project roles + if (_.isEmpty(member)) { + return null + } else { + return member[0].role + } +} + module.exports = { getTopcoderUserHandle, + getAllChallengeRolesForUser, + getProjectRoleForUser, processPayload } diff --git a/src/services/vanilla.js b/src/services/vanilla.js index 6deaba2..c4f8d2f 100644 --- a/src/services/vanilla.js +++ b/src/services/vanilla.js @@ -11,20 +11,18 @@ const utils = require('../utils/common.util') const template = require(config.TEMPLATES.TEMPLATE_FILE_PATH) /** - * Add/Remove a user to/from a category. - * + * Add/Remove a user to/from a group. * @param {Object} data the data from message payload * @returns {undefined} */ async function manageVanillaUser (data) { - const { challengeId, action, handle: username, role: projectRole } = data - logger.info(`Managing users for challengeID=${challengeId} ...`) + const { challengeId, action, handle: username, projectRole, challengeRoles } = data + logger.info(`Managing user for challengeID=${challengeId} [action=${action}, handle=${username}, projectRole=${JSON.stringify(projectRole)}, challengeRoles=${JSON.stringify(challengeRoles)}]...`) const { body: groups } = await vanillaClient.searchGroups(challengeId) const group = groups.length > 0 ? groups[0] : null - // Only members and copilots will receive notifications - const watch = (!projectRole || projectRole === constants.TOPCODER.PROJECT_ROLES.COPILOT) ? 1 : 0 - const follow = 1 + const watch = shouldWatchCategories(projectRole, challengeRoles) + const follow = shouldFollowCategories(projectRole, challengeRoles) if (!group) { throw new Error('The group wasn\'t not found by challengeID') @@ -77,7 +75,7 @@ async function manageVanillaUser (data) { const { body: user } = await vanillaClient.addUser(userData) vanillaUser = user - logger.info(`New user with UserID=${vanillaUser.userID} was added.`) + logger.info(`New user [UserID=${vanillaUser.userID}, Name=${vanillaUser.name}] was added.`) } else { // Get a full user profile with roles const { body: user } = await vanillaClient.getUser(vanillaUser.userID) @@ -90,6 +88,7 @@ async function manageVanillaUser (data) { roleID: [...currentVanillaRoleIDs, ...userTopcoderRoleIDs] } await vanillaClient.updateUser(vanillaUser.userID, userData) + // logger.info(`Roles were synchronized for User [UserID=${vanillaUser.userID}, Name=${vanillaUser.name}].`) } // Choose action to perform @@ -100,12 +99,12 @@ async function manageVanillaUser (data) { watch: watch, follow: follow }) - logger.info(`The user '${vanillaUser.name}' was added to the group '${group.name}'`) + logger.info(`User [UserID=${vanillaUser.userID}, Name=${vanillaUser.name} was added to Group [GroupID=${group.groupID}, Name=${group.name}, Watch=${watch}, Follow=${follow}]`) break } case constants.USER_ACTIONS.KICK: { await vanillaClient.removeUserFromGroup(group.groupID, vanillaUser.userID) - logger.info(`The user '${vanillaUser.name}' was removed from the group '${group.name}'`) + logger.info(`User [UserID=${vanillaUser.userID}, Name =${vanillaUser.name} was removed from Group [GroupID=${group.groupID}, Name=${group.name}]`) break } default: @@ -256,7 +255,12 @@ async function createVanillaGroup (challenge) { } for (const member of members) { - await manageVanillaUser({ challengeId: challenge.id, action: constants.USER_ACTIONS.INVITE, handle: member.handle, role: member.role }) + await manageVanillaUser({ + challengeId: challenge.id, + action: constants.USER_ACTIONS.INVITE, + handle: member.handle, + projectRole: member.role + }) } challengeDetailsDiscussion.url = `${challengeCategory.url}` @@ -319,6 +323,44 @@ async function createDiscussions (group, challenge, templateDiscussions, vanilla } } +/** + * Auto-watch categories + * @param projectRole string + * @param challengeRoles array + * @returns {boolean} + */ +function shouldWatchCategories (projectRole, challengeRoles) { + // New user + if (!projectRole && _.isEmpty(challengeRoles)) { + return true + } + + // Project Copilots / Challenge Copilots + return (projectRole === constants.TOPCODER.PROJECT_ROLES.COPILOT || + (_.isArray(challengeRoles) && _.includes(challengeRoles, constants.TOPCODER.CHALLENGE_ROLES.COPILOT)) + ) +} + +/** + * Auto-follow categories + * @param projectRole string + * @param challengeRoles array + * @returns {boolean} + */ +function shouldFollowCategories (projectRole, challengeRoles) { + // New user + if (!projectRole && _.isEmpty(challengeRoles)) { + return true + } + + // Project Copilots or Managers / Challenge Copilots and Managers + return projectRole === constants.TOPCODER.PROJECT_ROLES.COPILOT || + projectRole === constants.TOPCODER.PROJECT_ROLES.MANAGER || + (_.isArray(challengeRoles) && (_.includes(challengeRoles, constants.TOPCODER.CHALLENGE_ROLES.COPILOT) || + _.includes(challengeRoles, constants.TOPCODER.CHALLENGE_ROLES.MANAGER)) + ) +} + module.exports = { manageVanillaUser, createVanillaGroup, diff --git a/src/utils/topcoder-api.util.js b/src/utils/topcoder-api.util.js index 58e436d..641a292 100644 --- a/src/utils/topcoder-api.util.js +++ b/src/utils/topcoder-api.util.js @@ -110,11 +110,29 @@ async function getProject (projectId) { return reqToAPI('GET', path) } +/** + * Gets the list of Resource Roles by challengeId + */ +async function getAllChallengeRoles (challengeId) { + const path = `${config.TOPCODER.API_URL}/v5/resources?challengeId=${challengeId}` + return reqToAPI('GET', path) +} + +/** + * Gets the list of All Resource Roles + */ +async function getAllRoles () { + const path = `${config.TOPCODER.API_URL}/v5/resource-roles` + return reqToAPI('GET', path) +} + module.exports = { getUserDetailsById, getUserDetailsByHandle, getChallenge, updateChallenge, getRoles, + getAllRoles, + getAllChallengeRoles, getProject }