Skip to content

Commit b2a2f19

Browse files
authored
Merge pull request #47 from maxceem/email-bundle-and-refactoring
Email bundle and refactoring
2 parents 0163d88 + 5e1700e commit b2a2f19

28 files changed

+1539
-1178
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.idea
22
node_modules
33
*.log
4+
log.txt
45
.DS_Store
56
dist

README.md

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,75 @@
66
- Heroku Toolbelt https://toolbelt.heroku.com
77
- git
88
- PostgreSQL 9.5
9-
9+
1010

1111
## Configuration
12+
13+
### Notification server
1214
Configuration for the notification server is at `config/default.js`.
1315
The following parameters can be set in config files or in env variables:
14-
- LOG_LEVEL: the log level
15-
- PORT: the notification server port
16-
- AUTH_SECRET: TC auth secret
17-
- VALID_ISSUERS: TC auth valid issuers
18-
- jwksUri: TC auth JWKS URI
19-
- DATABASE_URL: URI to PostgreSQL database
20-
- DATABASE_OPTIONS: database connection options
21-
- KAFKA_URL: comma separated Kafka hosts
22-
- KAFKA_TOPIC_IGNORE_PREFIX: ignore this prefix for topics in the Kafka
23-
- KAFKA_GROUP_ID: Kafka consumer group id
24-
- KAFKA_CLIENT_CERT: Kafka connection certificate, optional;
25-
if not provided, then SSL connection is not used, direct insecure connection is used;
26-
if provided, it can be either path to certificate file or certificate content
27-
- KAFKA_CLIENT_CERT_KEY: Kafka connection private key, optional;
28-
if not provided, then SSL connection is not used, direct insecure connection is used;
29-
if provided, it can be either path to private key file or private key content
30-
- BUS_API_BASE_URL: Bus API url
31-
- REPLY_EMAIL_PREFIX: prefix of the genereated reply email address
32-
- REPLY_EMAIL_DOMAIN: email domain
33-
- DEFAULT_REPLY_EMAIL: default reply to email address, for example no-reply@topcoder.com
34-
- MENTION_EMAIL: recipient email used for email.project.post.mention event
35-
16+
- **General**
17+
- `LOG_LEVEL`: the log level
18+
- `PORT`: the notification server port
19+
- `DATABASE_URL`: URI to PostgreSQL database
20+
- `DATABASE_OPTIONS`: database connection options
21+
- **JWT authentication**
22+
- `AUTH_SECRET`: TC auth secret
23+
- `VALID_ISSUERS`: TC auth valid issuers
24+
- `JWKS_URI`: TC auth JWKS URI (need only for local deployment)
25+
- **KAFKA**
26+
- `KAFKA_URL`: comma separated Kafka hosts
27+
- `KAFKA_TOPIC_IGNORE_PREFIX`: ignore this prefix for topics in the Kafka
28+
- `KAFKA_GROUP_ID`: Kafka consumer group id
29+
- `KAFKA_CLIENT_CERT`: Kafka connection certificate, optional;
30+
if not provided, then SSL connection is not used, direct insecure connection is used;
31+
if provided, it can be either path to certificate file or certificate content
32+
- `KAFKA_CLIENT_CERT_KEY`: Kafka connection private key, optional;
33+
if not provided, then SSL connection is not used, direct insecure connection is used;
34+
if provided, it can be either path to private key file or private key content
35+
- **Topcoder API**
36+
- `TC_API_V5_BASE_URL`: the TopCoder API V5 base URL
37+
- **Notifications API**
38+
- `API_CONTEXT_PATH`: path to serve API on
39+
- **Machine to machine auth0 token**
40+
- `AUTH0_URL`: auth0 URL
41+
- `AUTH0_AUDIENCE`: auth0 audience
42+
- `TOKEN_CACHE_TIME`: time period of the cached token
43+
- `AUTH0_CLIENT_ID`: auth0 client id
44+
- `AUTH0_CLIENT_SECRET`: auth0 client secret
45+
46+
### Connect notification server
3647
Configuration for the connect notification server is at `connect/config.js`.
3748
The following parameters can be set in config files or in env variables:
38-
- TC_API_V3_BASE_URL: the TopCoder API V3 base URL
39-
- TC_API_V4_BASE_URL: the TopCoder API V4 base URL
40-
- TC_ADMIN_TOKEN: the admin token to access TopCoder API - same for V3 and V4<br>
41-
Also it has probably temporary variables of TopCoder role ids for 'Connect Manager', 'Connect Copilot' and 'administrator':
42-
- CONNECT_MANAGER_ROLE_ID: 8,
43-
- CONNECT_COPILOT_ROLE_ID: 4,
44-
- ADMINISTRATOR_ROLE_ID: 1<br>
45-
Provided values are for development backend. For production backend they may be different.
46-
These variables are currently being used to retrieve above role members using API V3 `/roles` endpoint. As soon as this endpoint is replaced with more suitable one, these variables has to be removed if no need anymore.
47-
- TCWEBSERVICE_ID - id of the BOT user which creates post with various events in discussions
48-
49+
- **Topcoder API**
50+
- `TC_API_V3_BASE_URL`: the TopCoder API V3 base URL
51+
- `TC_API_V4_BASE_URL`: the TopCoder API V4 base URL
52+
- `MESSAGE_API_BASE_URL`: the TopCoder message service API base URL
53+
- `TC_ADMIN_TOKEN`: the admin token to access TopCoder API - same for V3 and V4
54+
- **Topcder specific**<br>
55+
Also it has probably temporary variables of TopCoder role ids for 'Connect Manager', 'Connect Copilot' and 'administrator':
56+
- `CONNECT_MANAGER_ROLE_ID`: 8,
57+
- `CONNECT_COPILOT_ROLE_ID`: 4,
58+
- `ADMINISTRATOR_ROLE_ID`: 1<br>
59+
Provided values are for development backend. For production backend they may be different.
60+
These variables are currently being used to retrieve above role members using API V3 `/roles` endpoint. As soon as this endpoint is replaced with more suitable one, these variables has to be removed if no need anymore.
61+
- `TCWEBSERVICE_ID` - id of the BOT user which creates post with various events in discussions
62+
- **Machine to machine auth0 token**
63+
- `AUTH0_URL`: auth0 URL
64+
- `AUTH0_AUDIENCE`: auth0 audience
65+
- `TOKEN_CACHE_TIME`: time period of the cached token
66+
- `AUTH0_CLIENT_ID`: auth0 client id
67+
- `AUTH0_CLIENT_SECRET`: auth0 client secret
68+
- **Email notification service**
69+
- `ENV`: environment variable (used to generate reply emails)
70+
- `AUTH_SECRET`: auth secret (used to sign reply emails)
71+
- `ENABLE_EMAILS`: if email service has to be enabled
72+
- `ENABLE_DEV_MODE`: send all emails to the `DEV_MODE_EMAIL` email address
73+
- `DEV_MODE_EMAIL`: address to send all email when `ENABLE_DEV_MODE` is enabled
74+
- `MENTION_EMAIL`: recipient email used for `notifications.action.email.connect.project.post.mention` event
75+
- `REPLY_EMAIL_PREFIX`: prefix of the genereated reply email address
76+
- `REPLY_EMAIL_DOMAIN`: email domain
77+
- `DEFAULT_REPLY_EMAIL`: default reply to email address, for example no-reply@topcoder.com
4978

5079
Note that the above two configuration are separate because the common notification server config
5180
will be deployed to a NPM package, the connect notification server will use that NPM package,
@@ -89,6 +118,11 @@ In case it expires, you may get a new token in this way:
89118
- `TC_API_V3_BASE_URL=https://api.topcoder-dev.com/v3`
90119
- `TC_ADMIN_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoic3VzZXIxIiwiZXhwIjoxNTEzNDAxMjU4LCJ1c2VySWQiOiI0MDE1MzkzOCIsImlhdCI6MTUwOTYzNzYzOSwiZW1haWwiOiJtdHdvbWV5QGJlYWtzdGFyLmNvbSIsImp0aSI6IjIzZTE2YjA2LWM1NGItNDNkNS1iY2E2LTg0ZGJiN2JiNDA0NyJ9.REds35fdBvY7CMDGGFyT_tOD7DxGimFfVzIyEy9YA0Y` or follow section **TC API Admin Token** to obtain a new one if expired
91120
- `KAFKA_URL`, `KAFKA_CLIENT_CERT` and `KAFKA_CLIENT_CERT_KEY` get from [tc-bus-api readme](https://github.com/topcoder-platform/tc-bus-api/tree/dev)
121+
- if you are willing to use notifications API which is hosted by the notifications server locally, you will need to use some patched `tc-core-library-js` module, which skips verification of user token. Because we don't know Topcoder `AUTH_SECRET` locally. So you can install this fork:
122+
```
123+
npm i https://github.com/maxceem/tc-core-library-js/tree/skip-validation
124+
```
125+
**WARNING** do not push package.json with this dependency as it skips users token validation.
92126
- start local PostgreSQL db, create an empty database, update the config/default.js DATABASE_URL param to point to the db
93127
- install dependencies `npm i`
94128
- run code lint check `npm run lint`
@@ -127,5 +161,5 @@ In case it expires, you may get a new token in this way:
127161
## Swagger
128162

129163
Swagger API definition is provided at `docs/swagger_api.yaml`,
130-
you may check it at `http://editor.swagger.io`.
164+
you may check it at `http://editor.swagger.io`.
131165

config/default.js

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
* The configuration file.
33
*/
44
module.exports = {
5-
ENV: process.env.ENV,
65
LOG_LEVEL: process.env.LOG_LEVEL,
76
PORT: process.env.PORT,
8-
AUTH_SECRET: process.env.authSecret,
97
DATABASE_URL: process.env.DATABASE_URL,
108
DATABASE_OPTIONS: {
119
dialect: 'postgres',
@@ -19,27 +17,20 @@ module.exports = {
1917
},
2018
},
2119

20+
AUTH_SECRET: process.env.authSecret,
2221
VALID_ISSUERS: process.env.validIssuers ? process.env.validIssuers.replace(/\\"/g, '') : null,
22+
// keep it here for dev purposes, it's only needed by modified version of tc-core-library-js
23+
// which skips token validation when locally deployed
24+
JWKS_URI: process.env.jwksUri,
25+
2326
KAFKA_URL: process.env.KAFKA_URL,
2427
KAFKA_TOPIC_IGNORE_PREFIX: process.env.KAFKA_TOPIC_IGNORE_PREFIX,
2528
KAFKA_GROUP_ID: process.env.KAFKA_GROUP_ID,
2629
KAFKA_CLIENT_CERT: process.env.KAFKA_CLIENT_CERT ? process.env.KAFKA_CLIENT_CERT.replace('\\n', '\n') : null,
2730
KAFKA_CLIENT_CERT_KEY: process.env.KAFKA_CLIENT_CERT_KEY ?
2831
process.env.KAFKA_CLIENT_CERT_KEY.replace('\\n', '\n') : null,
2932

30-
MENTION_EMAIL: process.env.MENTION_EMAIL,
31-
REPLY_EMAIL_PREFIX: process.env.REPLY_EMAIL_PREFIX,
32-
REPLY_EMAIL_DOMAIN: process.env.REPLY_EMAIL_DOMAIN,
33-
34-
TC_ADMIN_TOKEN: process.env.TC_ADMIN_TOKEN,
35-
TC_API_BASE_URL: process.env.TC_API_BASE_URL || 'https://api.topcoder-dev.com',
36-
TC_API_V3_BASE_URL: process.env.TC_API_V3_BASE_URL || 'https://api.topcoder-dev.com/v3',
37-
TC_API_V4_BASE_URL: process.env.TC_API_V4_BASE_URL || 'https://api.topcoder-dev.com/v4',
3833
TC_API_V5_BASE_URL: process.env.TC_API_V5_BASE_URL || 'https://api.topcoder-dev.com/v5',
39-
MESSAGE_API_BASE_URL: process.env.MESSAGE_API_BASE_URL || 'https://api.topcoder-dev.com/v5',
40-
ENABLE_EMAILS: process.env.ENABLE_EMAILS || true,
41-
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE || true,
42-
DEV_MODE_EMAIL: process.env.DEV_MODE_EMAIL,
4334
API_CONTEXT_PATH: process.env.API_CONTEXT_PATH || '/v5/notifications',
4435

4536
// Configuration for generating machine to machine auth0 token.

connect/config.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
*/
44

55
module.exports = {
6+
// TC API related variables
67
TC_API_V3_BASE_URL: process.env.TC_API_V3_BASE_URL || 'https://api.topcoder-dev.com/v3',
78
TC_API_V4_BASE_URL: process.env.TC_API_V4_BASE_URL || 'https://api.topcoder-dev.com/v4',
8-
MESSAGE_API_BASE_URL: process.env.MESSAGE_API_BASE_URL || 'https://api.topcoder-dev.com/v4',
9-
// eslint-disable-next-line max-len
9+
MESSAGE_API_BASE_URL: process.env.MESSAGE_API_BASE_URL || 'https://api.topcoder-dev.com/v5',
1010
TC_ADMIN_TOKEN: process.env.TC_ADMIN_TOKEN,
1111

1212
// Probably temporary variables for TopCoder role ids for 'Connect Manager', 'Connect Copilot' and 'administrator'
@@ -16,8 +16,28 @@ module.exports = {
1616
CONNECT_MANAGER_ROLE_ID: 8,
1717
CONNECT_COPILOT_ROLE_ID: 4,
1818
ADMINISTRATOR_ROLE_ID: 1,
19-
2019
// id of the BOT user which creates post with various events in discussions
2120
TCWEBSERVICE_ID: process.env.TCWEBSERVICE_ID || '22838965',
2221

22+
// Configuration for generating machine to machine auth0 token.
23+
// The token will be used for calling another internal API.
24+
AUTH0_URL: process.env.AUTH0_URL,
25+
AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE,
26+
// The token will be cached.
27+
// We define the time period of the cached token.
28+
TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME || 86400000,
29+
AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
30+
AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET,
31+
32+
// email notification service related variables
33+
ENV: process.env.ENV,
34+
AUTH_SECRET: process.env.authSecret,
35+
ENABLE_EMAILS: process.env.ENABLE_EMAILS || true,
36+
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE || true,
37+
DEV_MODE_EMAIL: process.env.DEV_MODE_EMAIL,
38+
MENTION_EMAIL: process.env.MENTION_EMAIL,
39+
REPLY_EMAIL_PREFIX: process.env.REPLY_EMAIL_PREFIX,
40+
REPLY_EMAIL_DOMAIN: process.env.REPLY_EMAIL_DOMAIN,
41+
REPLY_EMAIL_FROM: process.env.REPLY_EMAIL_FROM,
42+
DEFAULT_REPLY_EMAIL: process.env.DEFAULT_REPLY_EMAIL,
2343
};

connect/connectNotificationServer.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ const config = require('./config');
99
const notificationServer = require('../index');
1010
const _ = require('lodash');
1111
const service = require('./service');
12-
const { BUS_API_EVENT } = require('../src/constants')
12+
const { BUS_API_EVENT } = require('./constants');
1313
const EVENTS = require('./events-config').EVENTS;
1414
const TOPCODER_ROLE_RULES = require('./events-config').TOPCODER_ROLE_RULES;
1515
const PROJECT_ROLE_RULES = require('./events-config').PROJECT_ROLE_RULES;
1616
const PROJECT_ROLE_OWNER = require('./events-config').PROJECT_ROLE_OWNER;
17+
const emailNotificationServiceHandler = require('./notificationServices/email').handler;
1718

1819
/**
1920
* Get TopCoder members notifications
@@ -62,14 +63,13 @@ const getTopCoderMembersNotifications = (eventConfig) => {
6263
* @return {Promise} resolves to a list of notifications
6364
*/
6465
const getNotificationsForMentionedUser = (eventConfig, content) => {
65-
if (!eventConfig.toMentionedUsers) {
66+
if (!eventConfig.toMentionedUsers || !content) {
6667
return Promise.resolve([]);
6768
}
6869

6970
let notifications = [];
7071
// eslint-disable-next-line
71-
const regexUserHandle = /title=\"@([a-zA-Z0-9-_.{}\[\]]+)\"|\[.*\]\(.*\"\@(.*)\"\)/g;
72-
const handles = [];
72+
const regexUserHandle = /title=\"@([a-zA-Z0-9-_.{}\[\]]+)\"|\[.*?\]\(.*?\"\@(.*?)\"\)/g;
7373
let matches = regexUserHandle.exec(content);
7474
while (matches) {
7575
const handle = matches[1] ? matches[1].toString() : matches[2].toString();
@@ -81,18 +81,22 @@ const getNotificationsForMentionedUser = (eventConfig, content) => {
8181
},
8282
});
8383
matches = regexUserHandle.exec(content);
84-
handles.push(handle);
8584
}
8685
// only one per userHandle
8786
notifications = _.uniqBy(notifications, 'userHandle');
8887

8988
return new Promise((resolve) => {
90-
service.getUsersByHandle(handles).then((users) => {
91-
_.map(notifications, (notification) => {
92-
notification.userId = _.find(users, { handle: notification.userHandle }).userId.toString();
89+
const handles = _.map(notifications, 'userHandle');
90+
if (handles.length > 0) {
91+
service.getUsersByHandle(handles).then((users) => {
92+
_.forEach(notifications, (notification) => {
93+
notification.userId = _.find(users, { handle: notification.userHandle }).userId.toString();
94+
});
95+
resolve(notifications);
9396
});
94-
resolve(notifications);
95-
});
97+
} else {
98+
resolve([]);
99+
}
96100
});
97101
};
98102

@@ -243,6 +247,7 @@ const excludeNotifications = (notifications, eventConfig, message, data) => {
243247
return Promise.all([
244248
getNotificationsForTopicStarter(excludeEventConfig, message.topicId),
245249
getNotificationsForUserId(excludeEventConfig, message.userId),
250+
getNotificationsForMentionedUser(eventConfig, message.postContent),
246251
getProjectMembersNotifications(excludeEventConfig, project),
247252
getTopCoderMembersNotifications(excludeEventConfig),
248253
]).then((notificationsPerSource) => (
@@ -296,7 +301,7 @@ const handler = (topic, message, callback) => {
296301
// - check that event has everything required or throw error
297302
getNotificationsForTopicStarter(eventConfig, message.topicId),
298303
getNotificationsForUserId(eventConfig, message.userId),
299-
message.postContent ? getNotificationsForMentionedUser(eventConfig, message.postContent) : Promise.resolve([]),
304+
getNotificationsForMentionedUser(eventConfig, message.postContent),
300305
getProjectMembersNotifications(eventConfig, project),
301306
getTopCoderMembersNotifications(eventConfig),
302307
]).then((notificationsPerSource) => (
@@ -344,6 +349,11 @@ EVENTS.forEach(eventConfig => {
344349
notificationServer.addTopicHandler(eventConfig.type, handler);
345350
});
346351

352+
// add notification service handlers
353+
if (config.ENABLE_EMAILS) {
354+
notificationServer.addNotificationServiceHandler(emailNotificationServiceHandler);
355+
}
356+
347357
// init database, it will clear and re-create all tables
348358
notificationServer
349359
.initDatabase()
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
module.exports = {
2+
// periods of time in cron format (node-cron)
3+
SCHEDULED_EVENT_PERIOD: {
4+
every10minutes: '*/10 * * * *',
5+
hourly: '0 * * * *',
6+
daily: '0 7 * * *', // every day at 7am
7+
weekly: '0 7 * * 6', // every Saturday at 7am
8+
},
9+
10+
// email service id for settings
11+
SETTINGS_EMAIL_SERVICE_ID: 'email',
12+
213
BUS_API_EVENT: {
3-
CONNECT : {
14+
CONNECT: {
415
TOPIC_CREATED: 'notifications.connect.project.topic.created',
516
TOPIC_DELETED: 'notifications.connect.project.topic.deleted',
617
POST_CREATED: 'notifications.connect.project.post.created',
718
POST_UPDATED: 'notifications.connect.project.post.edited',
819
POST_DELETED: 'notifications.connect.project.post.deleted',
920
MENTIONED_IN_POST: 'notifications.connect.project.post.mention',
1021
},
11-
EMAIL : {
22+
EMAIL: {
1223
TOPIC_CREATED: 'notifications.action.email.connect.project.topic.created',
1324
POST_CREATED: 'notifications.action.email.connect.project.post.created',
1425
MENTIONED_IN_POST: 'notifications.action.email.connect.project.post.mention',
26+
BUNDLED: 'notifications.action.email.connect.project.bundled',
1527
},
1628
},
1729
};

connect/events-config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ const EVENTS = [
109109
type: 'notifications.connect.project.topic.created',
110110
version: 2,
111111
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
112+
toMentionedUsers: true,
112113
}, {
113114
type: 'notifications.connect.project.post.created',
114115
version: 2,

0 commit comments

Comments
 (0)