Skip to content

Commit f956bd8

Browse files
authored
Merge branch 'dev' into handle_notification
2 parents b9dea0d + 92f76f4 commit f956bd8

File tree

7 files changed

+118
-16
lines changed

7 files changed

+118
-16
lines changed

connect/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module.exports = {
66
TC_API_V3_BASE_URL: process.env.TC_API_V3_BASE_URL || 'https://api.topcoder-dev.com/v3',
77
TC_API_V4_BASE_URL: process.env.TC_API_V4_BASE_URL || 'https://api.topcoder-dev.com/v4',
88
// eslint-disable-next-line max-len
9-
TC_ADMIN_TOKEN: process.env.TC_ADMIN_TOKEN,
9+
TC_ADMIN_TOKEN: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBNYW5hZ2VyIiwiQ29ubmVjdCBBZG1pbiJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoicGF0X21vbmFoYW4iLCJleHAiOjE1MTk4NDIyMzYsInVzZXJJZCI6IjQwMTUyOTMzIiwiaWF0IjoxNTE0MzE4NTA0LCJlbWFpbCI6ImRldm9wcytwYXRfbW9uYWhhbkB0b3Bjb2Rlci5jb20iLCJqdGkiOiIwOThjMGNjOS05OTljLTRlZjktYmM5ZS0yNTExZWJkZmJkMzIifQ.N3rbYMOfniLZ3TV3z08MAD46TwgFUGJ--UYhQVuu1Uw',
1010

1111
// Probably temporary variables for TopCoder role ids for 'Connect Manager', 'Connect Copilot' and 'administrator'
1212
// These are values for development backend. For production backend they may be different.

connect/connectNotificationServer.js

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const PROJECT_ROLE_OWNER = require('./events-config').PROJECT_ROLE_OWNER;
2222
* @return {Promise} resolves to a list of notifications
2323
*/
2424
const getTopCoderMembersNotifications = (eventConfig) => {
25+
// if event doesn't have to be notified to topcoder member, just ignore
2526
if (!eventConfig.topcoderRoles) {
2627
return Promise.resolve([]);
2728
}
@@ -91,8 +92,13 @@ const getNotificationsForMentionedUser = (eventConfig, content) => {
9192
*
9293
* @return {Promise} resolves to a list of notifications
9394
*/
94-
const getProjectMembersNotifications = (eventConfig, project) => (
95-
new Promise((resolve) => {
95+
const getProjectMembersNotifications = (eventConfig, project) => {
96+
// if event doesn't have to be notified to project member, just ignore
97+
if (!eventConfig.projectRoles) {
98+
return Promise.resolve([]);
99+
}
100+
101+
return new Promise((resolve) => {
96102
let notifications = [];
97103
const projectMembers = _.get(project, 'members', []);
98104

@@ -128,8 +134,8 @@ const getProjectMembersNotifications = (eventConfig, project) => (
128134
notifications = _.uniqBy(notifications, 'userId');
129135

130136
resolve(notifications);
131-
})
132-
);
137+
});
138+
};
133139

134140
/**
135141
* Get notifications for users obtained from userId
@@ -179,12 +185,64 @@ const getNotificationsForTopicStarter = (eventConfig, topicId) => {
179185
return Promise.reject(new Error('Missing topicId in the event message.'));
180186
}
181187

182-
return service.getTopic(topicId).then((topic) => ({
183-
userId: topic.userId.toString(),
184-
contents: {
185-
toTopicStarter: true,
186-
},
187-
}));
188+
return service.getTopic(topicId).then((topic) => {
189+
const userId = topic.userId.toString();
190+
191+
// special case: if topic created by CoderBot, don't send notification to him
192+
if (userId === 'CoderBot') {
193+
return [];
194+
}
195+
196+
return [{
197+
userId,
198+
contents: {
199+
toTopicStarter: true,
200+
},
201+
}];
202+
});
203+
};
204+
205+
/**
206+
* Exclude notifications using exclude rules of the event config
207+
*
208+
* @param {Array} notifications notifications list
209+
* @param {Object} eventConfig event configuration
210+
* @param {Object} message message
211+
* @param {Object} data any additional data which is retrieved once
212+
*
213+
* @returns {Promise} resolves to the list of filtered notifications
214+
*/
215+
const excludeNotifications = (notifications, eventConfig, message, data) => {
216+
// if there are no rules to exclude notifications, just return all of them untouched
217+
if (!eventConfig.exclude) {
218+
return Promise.resolve(notifications);
219+
}
220+
221+
const { project } = data;
222+
// create event config using rules to exclude notifications
223+
const excludeEventConfig = Object.assign({
224+
type: eventConfig.type,
225+
}, eventConfig.exclude);
226+
227+
// get notifications using rules for exclude notifications
228+
// and after filter out such notifications from the notifications list
229+
// TODO move this promise all together with `_.uniqBy` to one function
230+
// and reuse it here and in `handler` function
231+
return Promise.all([
232+
getNotificationsForTopicStarter(excludeEventConfig, message.topicId),
233+
getNotificationsForUserId(excludeEventConfig, message.userId),
234+
getProjectMembersNotifications(excludeEventConfig, project),
235+
getTopCoderMembersNotifications(excludeEventConfig),
236+
]).then((notificationsPerSource) => (
237+
_.uniqBy(_.flatten(notificationsPerSource), 'userId')
238+
)).then((excludedNotifications) => {
239+
const excludedUserIds = _.map(excludedNotifications, 'userId');
240+
const filteredNotifications = notifications.filter((notification) => (
241+
!_.includes(excludedUserIds, notification.userId)
242+
));
243+
244+
return filteredNotifications;
245+
});
188246
};
189247

190248
// set configuration for the server, see ../config/default.js for available config parameters
@@ -233,12 +291,16 @@ const handler = (topic, message, callback) => {
233291
// first found notification for one user will be send, the rest ignored
234292
// NOTE all userId has to be string
235293
_.uniqBy(_.flatten(notificationsPerSource), 'userId')
294+
)).then((notifications) => (
295+
excludeNotifications(notifications, eventConfig, message, {
296+
project,
297+
})
236298
)).then((notifications) => {
237299
allNotifications = notifications;
238300

239301
// now let's retrieve some additional data
240302

241-
// if message has userId such messages will likely need userHandle
303+
// if message has userId such messages will likely need userHandle and user full name
242304
// so let's get it
243305
if (message.userId) {
244306
const ids = [message.userId];
@@ -247,10 +309,12 @@ const handler = (topic, message, callback) => {
247309
return [];
248310
}).then((users) => {
249311
_.map(allNotifications, (notification) => {
312+
notification.version = eventConfig.version;
250313
notification.contents.projectName = project.name;
251314
// if found a user then add user handle
252315
if (users.length) {
253316
notification.contents.userHandle = users[0].handle;
317+
notification.contents.userFullName = `${users[0].firstName} ${users[0].lastName}`;
254318
}
255319
});
256320
callback(null, allNotifications);

connect/events-config.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ const TOPCODER_ROLE_RULES = {
3434
*
3535
* Each event configuration object has
3636
* type {String} [mandatory] Event type
37+
* version {Number} [optional] Version of the event.
3738
* projectRoles {Array} [optional] List of project member roles which has to get notification
3839
* topcoderRoles {Array} [optional] List of TopCoder member roles which has to get notification
3940
* toUserHandle {Boolean} [optional] If set to true, user defined in `message.userHandle` will get notification
40-
* toTopicStarter {Boolean} [optional] If set to true, than will find who started topic `message.topicId` and send notification to him
41+
* toTopicStarter {Boolean} [optional] If set to true, than will find who started topic `message.topicId` and
42+
* send notification to him
43+
* exclude {Object} [optional] May contains any rules like `projectRoles`, `toUserHandle` etc
44+
* but these rules will forbid sending notifications to members who satisfy them
4145
*
4246
* @type {Array}
4347
*/
@@ -46,6 +50,9 @@ const EVENTS = [
4650
{
4751
type: 'notifications.connect.project.created',
4852
projectRoles: [PROJECT_ROLE_OWNER],
53+
exclude: {
54+
topcoderRoles: [ROLE_CONNECT_MANAGER, ROLE_ADMINISTRATOR],
55+
},
4956
}, {
5057
type: 'notifications.connect.project.submittedForReview',
5158
projectRoles: [PROJECT_ROLE_OWNER],
@@ -73,13 +80,16 @@ const EVENTS = [
7380
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER],
7481
}, {
7582
type: 'notifications.connect.project.member.left',
83+
version: 2,
7684
projectRoles: [PROJECT_ROLE_MANAGER],
7785
}, {
7886
type: 'notifications.connect.project.member.removed',
87+
version: 2,
7988
projectRoles: [PROJECT_ROLE_MANAGER],
8089
toUserHandle: true,
8190
}, {
8291
type: 'notifications.connect.project.member.assignedAsOwner',
92+
version: 2,
8393
projectRoles: [PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER],
8494
toUserHandle: true,
8595
}, {
@@ -93,9 +103,11 @@ const EVENTS = [
93103
// Project activity
94104
{
95105
type: 'notifications.connect.project.topic.created',
106+
version: 2,
96107
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
97108
}, {
98109
type: 'notifications.connect.project.post.created',
110+
version: 2,
99111
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
100112
toTopicStarter: true,
101113
toMentionedUsers: true,
@@ -104,12 +116,15 @@ const EVENTS = [
104116
},
105117
{
106118
type: 'notifications.connect.project.linkCreated',
119+
version: 2,
107120
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
108121
}, {
109122
type: 'notifications.connect.project.fileUploaded',
123+
version: 2,
110124
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
111125
}, {
112126
type: 'notifications.connect.project.specificationModified',
127+
version: 2,
113128
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
114129
},
115130
];

src/app.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function startKafkaConsumer(handlers) {
5353
.then((notifications) => Promise.all(_.map(notifications, (notification) => models.Notification.create({
5454
userId: notification.userId,
5555
type: notification.newType | topicName,
56+
version: notification.version || null,
5657
contents: _.extend({}, messageJSON, notification.contents),
5758
read: false,
5859
}))))
@@ -110,6 +111,28 @@ function start(handlers) {
110111
});
111112
});
112113

114+
app.use('/notifications/debug', (req, res) => {
115+
const options = {
116+
from: new Date - 1 * 60 * 60 * 1000,
117+
until: new Date,
118+
limit: 100000,
119+
start: 0,
120+
order: 'desc',
121+
};
122+
123+
//
124+
// Find items logged between today and yesterday.
125+
//
126+
logger.query(options, (err, results) => {
127+
if (err) {
128+
res.status(500).json(err);
129+
return;
130+
}
131+
132+
res.status(200).json({ history: results, env: process.env });
133+
});
134+
});
135+
113136
app.use('/', apiRouter);
114137

115138

src/models/Notification.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = (sequelize, DataTypes) => sequelize.define('Notification', {
1616
type: { type: DataTypes.STRING, allowNull: false },
1717
contents: { type: DataTypes.JSONB, allowNull: false },
1818
read: { type: DataTypes.BOOLEAN, allowNull: false },
19+
version: { type: DataTypes.SMALLINT, allowNull: true },
1920
}, {});
2021

2122
// sequelize will generate and manage createdAt, updatedAt fields

src/models/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ const NotificationSetting = require('./NotificationSetting')(sequelize, DataType
1818
module.exports = {
1919
Notification,
2020
NotificationSetting,
21-
init: () => sequelize.sync({ }),
21+
init: () => sequelize.sync({ force: true }),
2222
};

src/services/NotificationService.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,11 @@ function* updateSettings(data, userId) {
8787
*/
8888
function* listNotifications(query, userId) {
8989
const settings = yield getSettings(userId);
90-
9190

9291
const filter = { where: {
9392
userId,
9493
}, offset: query.offset, limit: query.limit, order: [['createdAt', 'DESC']] };
95-
if (_.keys(settings).length>0){
94+
if (_.keys(settings).length > 0) {
9695
// only filter out notifications types which were explicitly set to 'no' - so we return notification by default
9796
const notificationTypes = _.keys(settings).filter((notificationType) => settings[notificationType].web !== 'no');
9897
filter.where.type = { $in: notificationTypes };

0 commit comments

Comments
 (0)