Skip to content

update: improve universal notifications payload #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ module.exports = {
KAFKA_CLIENT_CERT_KEY: process.env.KAFKA_CLIENT_CERT_KEY ?
process.env.KAFKA_CLIENT_CERT_KEY.replace('\\n', '\n') : null,

TC_API_V3_BASE_URL: process.env.TC_API_V3_BASE_URL || '',
TC_API_V3_BASE_URL: process.env.TC_API_V3_BASE_URL || 'http://api.topcoder-dev.com/v3',
TC_API_V4_BASE_URL: process.env.TC_API_V4_BASE_URL || '',
TC_API_V5_BASE_URL: process.env.TC_API_V5_BASE_URL || '',
TC_API_V5_BASE_URL: process.env.TC_API_V5_BASE_URL || 'https://api.topcoder-dev.com/v5',
API_CONTEXT_PATH: process.env.API_CONTEXT_PATH || '/v5/notifications',
TC_API_BASE_URL: process.env.TC_API_BASE_URL || '',

Expand Down Expand Up @@ -135,7 +135,7 @@ module.exports = {
// email notification service related variables
ENV: process.env.ENV,
ENABLE_EMAILS: process.env.ENABLE_EMAILS ? Boolean(process.env.ENABLE_EMAILS) : false,
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE ? Boolean(process.env.ENABLE_DEV_MODE) : true,
ENABLE_DEV_MODE: 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,
Expand Down
149 changes: 131 additions & 18 deletions src/common/tcApiHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,103 @@ function* getUsersByHandles(handles) {
return yield searchUsersByQuery(query);
}

/**
* Get users by handles or userIds.
* @param {Array<Object>} handles the objects that has user handles.
* @param {Array<Object>} userIds the objects that has userIds.
* @returns {Array<Object>} the matched users
*/
function* getUsersByHandlesAndUserIds(handles, userIds) {
if ((!handles || handles.length === 0) && (!userIds || userIds.length === 0)) {
return [];
}
const handlesQuery = _.map(handles, h => `handleLower:${h.handle.toLowerCase()}`);
const userIdsQuery = _.map(userIds, u => `userId:${u.userId}`);
const query = _.concat(handlesQuery, userIdsQuery).join(URI.encodeQuery(' OR ', 'utf8'));
try {
return yield searchUsersByQuery(query);
} catch (err) {
const error = new Error(err.response.text);
error.status = err.status;
throw error;
}
}

/**
* Search users by query string.
* @param {String} query the query string
* @returns {Array} the matched users
*/
function* searchUsersByEmailQuery(query) {
const token = yield getM2MToken();
const res = yield request
.get(`${
config.TC_API_V3_BASE_URL
}/users?filter=${
query
}&fields=id,email,handle`)
.set('Authorization', `Bearer ${token}`);
if (!_.get(res, 'body.result.success')) {
throw new Error(`Failed to search users by query: ${query}`);
}
const records = _.get(res, 'body.result.content') || [];

logger.verbose(`Searched users: ${JSON.stringify(records, null, 4)}`);
return records;
}

/**
* Get users by emails.
* @param {Array<Object>} emails the objects that has user emails.
* @returns {Array<Object>} the matched users
*/
function* getUsersByEmails(emails) {
if (!emails || emails.length === 0) {
return [];
}
const users = [];
try {
for (const email of emails) {
const query = `email%3D${email.email}`;
const result = yield searchUsersByEmailQuery(query);
users.push(...result);
}
return users;
} catch (err) {
const error = new Error(err.response.text);
error.status = err.status;
throw error;
}
}

/**
* Get users by uuid.
* @param {Array<Object>} ids the objects that has user uuids.
* @returns {Array<Object>} the matched users
*/
function* getUsersByUserUUIDs(ids, enrich) {
if (!ids || ids.length === 0) {
return [];
}
const users = [];
const token = yield getM2MToken();
try {
for (const id of ids) {
const res = yield request
.get(`${config.TC_API_V5_BASE_URL}/users/${id.userUUID}${enrich ? '?enrich=true' : ''}`)
.set('Authorization', `Bearer ${token}`);
const user = res.body;
logger.verbose(`Searched users: ${JSON.stringify(user, null, 4)}`);
users.push(user);
}
return users;
} catch (err) {
const error = new Error(err.response.text);
error.status = err.status;
throw error;
}
}

/**
* Send message to bus.
* @param {Object} data the data to send
Expand Down Expand Up @@ -158,21 +255,30 @@ function* checkNotificationSetting(userId, notificationType, serviceId) {
}

/**
* Notify user via email.
* Notify user via web.
* @param {Object} message the Kafka message payload
* @return {Object} notification details.
* @return {Array<Object>} notification details.
*/
function* notifyUserViaWeb(message) {
const notificationType = message.type;
const userId = message.details.userId;
// if web notification is explicitly disabled for current notification type do nothing
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_WEB_SERVICE_ID);
if (!allowed) {
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_WEB_SERVICE_ID}'`
const notifications = [];
for (const recipient of message.details.recipients) {
const userId = recipient.userId;
if (_.isUndefined(userId)) {
logger.error(`userId not received for user: ${JSON.stringify(recipient, null, 4)}`);
continue;
}
// if web notification is explicitly disabled for current notification type do nothing
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_WEB_SERVICE_ID);
if (!allowed) {
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_WEB_SERVICE_ID}'`
+ ` service to the userId '${userId}' due to his notification settings.`);
return;
continue;
}
notifications.push(_.assign({}, _.pick(message.details, ['contents', 'version']), { userId }));
}
return message.details;

return notifications;
}

/**
Expand All @@ -182,31 +288,35 @@ function* notifyUserViaWeb(message) {
function* notifyUserViaEmail(message) {
const notificationType = message.type;
const topic = constants.BUS_API_EVENT.EMAIL.UNIVERSAL;
const cc = _.map(_.filter(message.details.cc, c => !_.isUndefined(c.email)), 'email');
for (const recipient of message.details.recipients) {
const userId = recipient.userId;
// if email notification is explicitly disabled for current notification type do nothing
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_EMAIL_SERVICE_ID);
if (!allowed) {
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_EMAIL_SERVICE_ID}'`
+ ` service to the userId '${userId}' due to his notification settings.`);
continue;
}
let userEmail;
// if dev mode for email is enabled then replace recipient email
if (config.ENABLE_DEV_MODE) {
userEmail = config.DEV_MODE_EMAIL;
} else {
userEmail = recipient.email;
if (!userEmail) {
logger.error(`Email not received for user: ${userId}`);
logger.error(`Email not received for user: ${JSON.stringify(recipient, null, 4)}`);
continue;
}
}
// skip checking notification setting if userId is not found.
if (!_.isUndefined(userId)) {
// if email notification is explicitly disabled for current notification type do nothing
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_EMAIL_SERVICE_ID);
if (!allowed) {
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_EMAIL_SERVICE_ID}'`
+ ` service to the userId '${userId}' due to his notification settings.`);
continue;
}
}
const recipients = [userEmail];
const payload = {
from: message.details.from,
recipients,
cc: message.details.cc || [],
cc,
data: message.details.data || {},
sendgrid_template_id: message.details.sendgridTemplateId,
version: message.details.version,
Expand Down Expand Up @@ -496,6 +606,9 @@ module.exports = {
getM2MToken,
getUsersBySkills,
getUsersByHandles,
getUsersByHandlesAndUserIds,
getUsersByEmails,
getUsersByUserUUIDs,
sendMessageToBus,
notifySlackChannel,
checkNotificationSetting,
Expand Down
Loading