Skip to content

Commit 9b78ba7

Browse files
authored
Merge pull request #216 from eisbilir/feature/universal-notifications
update: improve universal notifications payload
2 parents 9fee095 + 1cb6e25 commit 9b78ba7

File tree

3 files changed

+261
-40
lines changed

3 files changed

+261
-40
lines changed

config/default.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ module.exports = {
2828
KAFKA_CLIENT_CERT_KEY: process.env.KAFKA_CLIENT_CERT_KEY ?
2929
process.env.KAFKA_CLIENT_CERT_KEY.replace('\\n', '\n') : null,
3030

31-
TC_API_V3_BASE_URL: process.env.TC_API_V3_BASE_URL || '',
31+
TC_API_V3_BASE_URL: process.env.TC_API_V3_BASE_URL || 'http://api.topcoder-dev.com/v3',
3232
TC_API_V4_BASE_URL: process.env.TC_API_V4_BASE_URL || '',
33-
TC_API_V5_BASE_URL: process.env.TC_API_V5_BASE_URL || '',
33+
TC_API_V5_BASE_URL: process.env.TC_API_V5_BASE_URL || 'https://api.topcoder-dev.com/v5',
3434
API_CONTEXT_PATH: process.env.API_CONTEXT_PATH || '/v5/notifications',
3535
TC_API_BASE_URL: process.env.TC_API_BASE_URL || '',
3636

@@ -135,7 +135,7 @@ module.exports = {
135135
// email notification service related variables
136136
ENV: process.env.ENV,
137137
ENABLE_EMAILS: process.env.ENABLE_EMAILS ? Boolean(process.env.ENABLE_EMAILS) : false,
138-
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE ? Boolean(process.env.ENABLE_DEV_MODE) : true,
138+
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE === 'true',
139139
DEV_MODE_EMAIL: process.env.DEV_MODE_EMAIL,
140140
DEFAULT_REPLY_EMAIL: process.env.DEFAULT_REPLY_EMAIL,
141141
ENABLE_HOOK_BULK_NOTIFICATION: process.env.ENABLE_HOOK_BULK_NOTIFICATION || false,

src/common/tcApiHelper.js

+131-18
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,103 @@ function* getUsersByHandles(handles) {
8888
return yield searchUsersByQuery(query);
8989
}
9090

91+
/**
92+
* Get users by handles or userIds.
93+
* @param {Array<Object>} handles the objects that has user handles.
94+
* @param {Array<Object>} userIds the objects that has userIds.
95+
* @returns {Array<Object>} the matched users
96+
*/
97+
function* getUsersByHandlesAndUserIds(handles, userIds) {
98+
if ((!handles || handles.length === 0) && (!userIds || userIds.length === 0)) {
99+
return [];
100+
}
101+
const handlesQuery = _.map(handles, h => `handleLower:${h.handle.toLowerCase()}`);
102+
const userIdsQuery = _.map(userIds, u => `userId:${u.userId}`);
103+
const query = _.concat(handlesQuery, userIdsQuery).join(URI.encodeQuery(' OR ', 'utf8'));
104+
try {
105+
return yield searchUsersByQuery(query);
106+
} catch (err) {
107+
const error = new Error(err.response.text);
108+
error.status = err.status;
109+
throw error;
110+
}
111+
}
112+
113+
/**
114+
* Search users by query string.
115+
* @param {String} query the query string
116+
* @returns {Array} the matched users
117+
*/
118+
function* searchUsersByEmailQuery(query) {
119+
const token = yield getM2MToken();
120+
const res = yield request
121+
.get(`${
122+
config.TC_API_V3_BASE_URL
123+
}/users?filter=${
124+
query
125+
}&fields=id,email,handle`)
126+
.set('Authorization', `Bearer ${token}`);
127+
if (!_.get(res, 'body.result.success')) {
128+
throw new Error(`Failed to search users by query: ${query}`);
129+
}
130+
const records = _.get(res, 'body.result.content') || [];
131+
132+
logger.verbose(`Searched users: ${JSON.stringify(records, null, 4)}`);
133+
return records;
134+
}
135+
136+
/**
137+
* Get users by emails.
138+
* @param {Array<Object>} emails the objects that has user emails.
139+
* @returns {Array<Object>} the matched users
140+
*/
141+
function* getUsersByEmails(emails) {
142+
if (!emails || emails.length === 0) {
143+
return [];
144+
}
145+
const users = [];
146+
try {
147+
for (const email of emails) {
148+
const query = `email%3D${email.email}`;
149+
const result = yield searchUsersByEmailQuery(query);
150+
users.push(...result);
151+
}
152+
return users;
153+
} catch (err) {
154+
const error = new Error(err.response.text);
155+
error.status = err.status;
156+
throw error;
157+
}
158+
}
159+
160+
/**
161+
* Get users by uuid.
162+
* @param {Array<Object>} ids the objects that has user uuids.
163+
* @returns {Array<Object>} the matched users
164+
*/
165+
function* getUsersByUserUUIDs(ids, enrich) {
166+
if (!ids || ids.length === 0) {
167+
return [];
168+
}
169+
const users = [];
170+
const token = yield getM2MToken();
171+
try {
172+
for (const id of ids) {
173+
const res = yield request
174+
.get(`${config.TC_API_V5_BASE_URL}/users/${id.userUUID}${enrich ? '?enrich=true' : ''}`)
175+
.set('Authorization', `Bearer ${token}`);
176+
const user = res.body;
177+
logger.verbose(`Searched users: ${JSON.stringify(user, null, 4)}`);
178+
users.push(user);
179+
}
180+
return users;
181+
} catch (err) {
182+
const error = new Error(err.response.text);
183+
error.status = err.status;
184+
throw error;
185+
}
186+
}
187+
91188
/**
92189
* Send message to bus.
93190
* @param {Object} data the data to send
@@ -158,21 +255,30 @@ function* checkNotificationSetting(userId, notificationType, serviceId) {
158255
}
159256

160257
/**
161-
* Notify user via email.
258+
* Notify user via web.
162259
* @param {Object} message the Kafka message payload
163-
* @return {Object} notification details.
260+
* @return {Array<Object>} notification details.
164261
*/
165262
function* notifyUserViaWeb(message) {
166263
const notificationType = message.type;
167-
const userId = message.details.userId;
168-
// if web notification is explicitly disabled for current notification type do nothing
169-
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_WEB_SERVICE_ID);
170-
if (!allowed) {
171-
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_WEB_SERVICE_ID}'`
264+
const notifications = [];
265+
for (const recipient of message.details.recipients) {
266+
const userId = recipient.userId;
267+
if (_.isUndefined(userId)) {
268+
logger.error(`userId not received for user: ${JSON.stringify(recipient, null, 4)}`);
269+
continue;
270+
}
271+
// if web notification is explicitly disabled for current notification type do nothing
272+
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_WEB_SERVICE_ID);
273+
if (!allowed) {
274+
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_WEB_SERVICE_ID}'`
172275
+ ` service to the userId '${userId}' due to his notification settings.`);
173-
return;
276+
continue;
277+
}
278+
notifications.push(_.assign({}, _.pick(message.details, ['contents', 'version']), { userId }));
174279
}
175-
return message.details;
280+
281+
return notifications;
176282
}
177283

178284
/**
@@ -182,31 +288,35 @@ function* notifyUserViaWeb(message) {
182288
function* notifyUserViaEmail(message) {
183289
const notificationType = message.type;
184290
const topic = constants.BUS_API_EVENT.EMAIL.UNIVERSAL;
291+
const cc = _.map(_.filter(message.details.cc, c => !_.isUndefined(c.email)), 'email');
185292
for (const recipient of message.details.recipients) {
186293
const userId = recipient.userId;
187-
// if email notification is explicitly disabled for current notification type do nothing
188-
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_EMAIL_SERVICE_ID);
189-
if (!allowed) {
190-
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_EMAIL_SERVICE_ID}'`
191-
+ ` service to the userId '${userId}' due to his notification settings.`);
192-
continue;
193-
}
194294
let userEmail;
195295
// if dev mode for email is enabled then replace recipient email
196296
if (config.ENABLE_DEV_MODE) {
197297
userEmail = config.DEV_MODE_EMAIL;
198298
} else {
199299
userEmail = recipient.email;
200300
if (!userEmail) {
201-
logger.error(`Email not received for user: ${userId}`);
301+
logger.error(`Email not received for user: ${JSON.stringify(recipient, null, 4)}`);
302+
continue;
303+
}
304+
}
305+
// skip checking notification setting if userId is not found.
306+
if (!_.isUndefined(userId)) {
307+
// if email notification is explicitly disabled for current notification type do nothing
308+
const allowed = yield checkNotificationSetting(userId, notificationType, constants.SETTINGS_EMAIL_SERVICE_ID);
309+
if (!allowed) {
310+
logger.verbose(`Notification '${notificationType}' won't be sent by '${constants.SETTINGS_EMAIL_SERVICE_ID}'`
311+
+ ` service to the userId '${userId}' due to his notification settings.`);
202312
continue;
203313
}
204314
}
205315
const recipients = [userEmail];
206316
const payload = {
207317
from: message.details.from,
208318
recipients,
209-
cc: message.details.cc || [],
319+
cc,
210320
data: message.details.data || {},
211321
sendgrid_template_id: message.details.sendgridTemplateId,
212322
version: message.details.version,
@@ -496,6 +606,9 @@ module.exports = {
496606
getM2MToken,
497607
getUsersBySkills,
498608
getUsersByHandles,
609+
getUsersByHandlesAndUserIds,
610+
getUsersByEmails,
611+
getUsersByUserUUIDs,
499612
sendMessageToBus,
500613
notifySlackChannel,
501614
checkNotificationSetting,

0 commit comments

Comments
 (0)