Skip to content
Merged
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ workflows:
context : org-global
filters:
branches:
only: [dev, 'hotfix/V5-API-Standards', 'v5-upgrade', 'feature/bulk-notification']
only: [dev, 'bug/rate-limit']
- "build-prod":
context : org-global
filters:
Expand Down
43 changes: 29 additions & 14 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,35 +70,50 @@ module.exports = {
{
phaseTypeName: 'Checkpoint Screening',
state: 'START',
roles: ['Copilot', 'Reviewer'],
roles: ['Primary Screener'],
notification:
{
id: 0, /** challengeid or projectid */
name: '', /** challenge name */
group: 'challenge',
title: 'Challenge checkpoint review.',
title: 'Checkpoint Screening phase is now open. You may now begin screening checkpoint submissions.',
},
},
},
],
'submission.notification.create': [
{
handleSubmission:
}, {
handleAutoPilot:
{
resource: 'submission',
roles: ['Copilot', 'Reviewer'],
selfOnly: true /** Submitter only */,
phaseTypeName: 'Checkpoint Review',
state: 'START',
roles: ['Copilot'],
notification:
{
id: 0, /** challengeid or projectid */
name: '', /** challenge name */
group: 'submission',
title: 'A new submission is uploaded.',
group: 'challenge',
title: 'Checkpoint Review is now open. You may now begin reviewing checkpoint submissions.',
},
},
},
],
'admin.notification.broadcast' : [{
// /** 'submission.notification.create': [
// {
// handleSubmission:
// {
// resource: 'submission',
// roles: ['Copilot', 'Reviewer'],
// selfOnly: true /** Submitter only */,
// notification:
// {
// id: 0, /** challengeid or projectid */
// name: '', /** challenge name */
// group: 'submission',
// title: 'A new submission is uploaded.',
// },
// },
// },
// ],
// */ // issue - https://github.com/topcoder-platform/community-app/issues/4108
'admin.notification.broadcast': [{
handleBulkNotification: {}
}
]
Expand All @@ -112,5 +127,5 @@ module.exports = {
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE ? Boolean(process.env.ENABLE_DEV_MODE) : true,
DEV_MODE_EMAIL: process.env.DEV_MODE_EMAIL,
DEFAULT_REPLY_EMAIL: process.env.DEFAULT_REPLY_EMAIL,
ENABLE_HOOK_BULK_NOTIFICATION : process.env.ENABLE_HOOK_BULK_NOTIFICATION || false,
ENABLE_HOOK_BULK_NOTIFICATION: process.env.ENABLE_HOOK_BULK_NOTIFICATION || false,
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6.2",
"topcoder-healthcheck-dropin": "^1.0.3",
"urijs": "^1.19.1",
"winston": "^2.2.0"
"winston": "^2.2.0",
"node-cache": "^5.1.0"
},
"engines": {
"node": "6.x"
Expand Down
66 changes: 52 additions & 14 deletions src/common/broadcastAPIHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ const _ = require('lodash')
const config = require('config')
const request = require('superagent')
const logger = require('./logger')
const m2mAuth = require('tc-core-library-js').auth.m2m;
const m2m = m2mAuth(config);
const m2mAuth = require('tc-core-library-js').auth.m2m
const NodeCache = require('node-cache')

const m2m = m2mAuth(config)
const cache = new NodeCache()

const logPrefix = "BroadcastAPI: "
const cachedTimeInSeconds = 300 //300 seconds

/**
* Helper Function - get m2m token
Expand All @@ -27,6 +31,11 @@ async function getMemberInfo(userId) {
"/members/_search/?" +
`query=userId%3A${userId}` +
`&limit=1`
if (cachedMemberInfo = cache.get(url)) {
return new Promise((resolve, reject) => {
resolve(cachedMemberInfo)
})
}
return new Promise(async function (resolve, reject) {
let memberInfo = []
logger.info(`calling member api ${url} `)
Expand All @@ -37,6 +46,7 @@ async function getMemberInfo(userId) {
}
memberInfo = _.get(res, 'body.result.content')
logger.info(`BCA Memeber API: Feteched ${memberInfo.length} record(s) from member api`)
cache.set(url, memberInfo, cachedTimeInSeconds)
resolve(memberInfo)
} catch (err) {
reject(new Error(`BCA Memeber API: Failed to get member ` +
Expand Down Expand Up @@ -102,12 +112,16 @@ async function callApi(url, machineToken) {

/**
* Helper function - check Skills and Tracks condition
*
* @param {Integer} userId
* @param {Object} bulkMessage
* @param {Object} m memberInfo
*
*/
async function checkUserSkillsAndTracks(userId, bulkMessage) {
async function checkUserSkillsAndTracks(userId, bulkMessage, m) {
try {
const skills = _.get(bulkMessage, 'recipients.skills')
const tracks = _.get(bulkMessage, 'recipients.tracks')
const m = await getMemberInfo(userId)
let skillMatch, trackMatch = false // default
if (skills && skills.length > 0) {
const ms = _.get(m[0], "skills") // get member skills
Expand Down Expand Up @@ -159,25 +173,43 @@ async function checkUserSkillsAndTracks(userId, bulkMessage) {
/**
* Helper function - check group condition
*/
async function checkUserGroup(userId, bulkMessage) {
async function checkUserGroup(userId, bulkMessage, userGroupInfo) {
try {
const excludeGroupSign = '!'
const groups = _.get(bulkMessage, 'recipients.groups')

let flag = false // default
const userGroupInfo = await getUserGroup(userId)
if (groups.length > 0) {
let excludeGroups = []
let includeGroups = []

_.map(groups, (g) => {
if (_.startsWith(g, excludeGroupSign)) {
excludeGroups.push(g)
} else {
includeGroups.push(g)
}
})

if (includeGroups.length > 0) {
_.map(userGroupInfo, (o) => {
// particular group only condition
flag = (_.indexOf(groups, _.get(o, "name")) >= 0) ? true : flag
flag = (_.indexOf(includeGroups, _.get(o, "name")) >= 0) ? true : flag
})
} else { // no group condition means its for `public` no private group
}
if (excludeGroups.length > 0) {
flag = true // default allow for all
_.map(userGroupInfo, (o) => {
// not allow if user is part of any private group
flag = (_.get(o, "privateGroup")) ? false : flag
// not allow if user is part of any private group i.e. excludeGroups
flag = (_.indexOf(excludeGroups, (excludeGroupSign + _.get(o, "name"))) >= 0) ? false : flag
})
logger.info(`public group condition for userId ${userId}` +
` and BC messageId ${bulkMessage.id}, the result is: ${flag}`)
}

if (groups.length === 0) {
flag = true // no restriction
}

return flag
} catch (e) {
throw new Error(`checkUserGroup(): ${e}`)
Expand All @@ -189,12 +221,16 @@ async function checkUserGroup(userId, bulkMessage) {
*
* @param {Integer} userId
* @param {Object} bulkMessage
* @param {Object} memberInfo
* @param {Object} userGroupInfo
*
* @return Promise
*/
async function checkBroadcastMessageForUser(userId, bulkMessage) {
async function checkBroadcastMessageForUser(userId, bulkMessage, memberInfo, userGroupInfo) {
return new Promise(function (resolve, reject) {
Promise.all([
checkUserSkillsAndTracks(userId, bulkMessage),
checkUserGroup(userId, bulkMessage),
checkUserSkillsAndTracks(userId, bulkMessage, memberInfo),
checkUserGroup(userId, bulkMessage, userGroupInfo),
]).then((results) => {
let flag = true // TODO need to be sure about default value
_.map(results, (r) => {
Expand All @@ -214,4 +250,6 @@ async function checkBroadcastMessageForUser(userId, bulkMessage) {

module.exports = {
checkBroadcastMessageForUser,
getMemberInfo,
getUserGroup,
}
2 changes: 1 addition & 1 deletion src/common/tcApiHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ function filterChallengeUsers(usersInfo, filterOnRoles = [], filterOnUsers = [])
}
});
logger.info(`Total roles available in this challenge are: ${rolesAvailable.join(',')}`);
return users;
return _.uniqBy(users, 'userId');
}

/**
Expand Down
20 changes: 16 additions & 4 deletions src/hooks/hookBulkMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,17 @@ async function syncBulkMessageForUser(userId) {
" LEFT OUTER JOIN (SELECT id as refid, bulk_message_id " +
" FROM bulk_message_user_refs AS bmur WHERE bmur.user_id=$1)" +
" AS b ON a.id=b.bulk_message_id WHERE b.refid IS NULL"
let memberInfo = []
let userGroupInfo = []
models.sequelize.query(q, { bind: [userId] })
.then(function (res) {
Promise.all(res[0].map((r) => isBroadCastMessageForUser(userId, r)))
.then(async function (res) {
try {
memberInfo = await api.getMemberInfo(userId)
userGroupInfo = await api.getUserGroup(userId)
} catch (e) {
reject(`${logPrefix} Failed to get member/group info: ${e}`)
}
Promise.all(res[0].map((r) => isBroadCastMessageForUser(userId, r, memberInfo, userGroupInfo)))
.then((results) => {
Promise.all(results.map((o) => {
if (o.result) {
Expand All @@ -94,9 +102,13 @@ async function syncBulkMessageForUser(userId) {
* Check if current user in broadcast recipent group
* @param {Integer} userId
* @param {Object} bulkMessage
* @param {Object} memberInfo
* @param {Object} userGroupInfo
*
* @retun promise
*/
async function isBroadCastMessageForUser(userId, bulkMessage) {
return api.checkBroadcastMessageForUser(userId, bulkMessage)
async function isBroadCastMessageForUser(userId, bulkMessage, memberInfo, userGroupInfo) {
return api.checkBroadcastMessageForUser(userId, bulkMessage, memberInfo, userGroupInfo)
}

/**
Expand Down