Skip to content

Commit b359127

Browse files
committed
Initial commit
0 parents  commit b359127

28 files changed

+5094
-0
lines changed

.eslintrc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"extends": "airbnb-base",
3+
"rules": {
4+
"no-use-before-define": 1,
5+
"strict": 0,
6+
"comma-dangle": 1,
7+
"func-names": 0,
8+
"no-underscore-dangle": 0,
9+
"prefer-rest-params": 0,
10+
"no-param-reassign": 0,
11+
"prefer-template": 0,
12+
"new-cap": 0,
13+
"global-require": 0,
14+
"consistent-return": 0,
15+
"max-len": [2, 120]
16+
},
17+
"parserOptions": {
18+
"ecmaVersion": 6
19+
}
20+
}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
node_modules
3+
*.log
4+
.DS_Store

README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# TOPCODER NOTIFICATIONS SERIES - NOTIFICATIONS SERVER
2+
3+
4+
## Dependencies
5+
- nodejs https://nodejs.org/en/ (v6+)
6+
- Heroku Toolbelt https://toolbelt.heroku.com
7+
- git
8+
- PostgreSQL 9.5
9+
10+
11+
## Configuration
12+
Configuration for the notification server is at `config/default.js`.
13+
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+
- JWT_SECRET: JWT secret
17+
- DATABASE_URL: URI to PostgreSQL database
18+
- DATABASE_OPTIONS: database connection options
19+
- KAFKA_URL: comma separated Kafka hosts
20+
- KAFKA_TOPIC_IGNORE_PREFIX: ignore this prefix for topics in the Kafka
21+
- KAFKA_GROUP_ID: Kafka consumer group id
22+
- KAFKA_CLIENT_CERT: Kafka connection certificate, optional;
23+
if not provided, then SSL connection is not used, direct insecure connection is used;
24+
if provided, it can be either path to certificate file or certificate content
25+
- KAFKA_CLIENT_CERT_KEY: Kafka connection private key, optional;
26+
if not provided, then SSL connection is not used, direct insecure connection is used;
27+
if provided, it can be either path to private key file or private key content
28+
29+
30+
Configuration for the connect notification server is at `connect/config.js`.
31+
The following parameters can be set in config files or in env variables:
32+
- TC_API_BASE_URL: the TopCoder API base URL
33+
- TC_ADMIN_TOKEN: the admin token to access TopCoder API
34+
35+
36+
Note that the above two configuration are separate because the common notification server config
37+
will be deployed to a NPM package, the connect notification server will use that NPM package,
38+
the connection notification server should only use API exposed by the index.js.
39+
40+
41+
## JWT Token Generation
42+
43+
JWT token can be generated using the script test/token.js, its usage: `node test/token {user-id}`.
44+
Then use the generated token to manage the user's notifications.
45+
46+
In the Postman bus API, the `Post Connect event` will create a Kafka event of project id 1936;
47+
In the Postman notification server API, the `TC API - get project` will get details of project id 1936,
48+
we can see the project has one member of user id 305384;
49+
so we can run `node test/token 305384` to generate a token to manage notifications of the user of id 305384.
50+
51+
The generated token is already configured in the Postman notification server API environment TOKEN variable.
52+
You may reuse it during review.
53+
54+
55+
## TC API Admin Token
56+
57+
An admin token is needed to access TC API. This is already configured in connect/config.js and Postman notification
58+
server API environment TC_ADMIN_TOKEN variable.
59+
In case it expires, you may get a new token in this way:
60+
61+
- use Chrome to browse connect.topcoder-dev.com
62+
- open developer tools, click the Network tab
63+
- log in with suser1 / Topcoder123
64+
- once logged in, open some project, for example https://connect.topcoder-dev.com/projects/1936 and in the network inspector
65+
look for the call to the project api and get the token from the auth header, see
66+
http://pokit.org/get/img/68cdd34f3d205d6d9bd8bddb07bdc216.jpg
67+
68+
69+
## Local deployment
70+
- start local PostgreSQL db, create an empty database, update the config/default.js DATABASE_URL param to point to the db
71+
- install dependencies `npm i`
72+
- run code lint check `npm run lint`
73+
- start connect notification server `npm start`
74+
- the app is running at `http://localhost:3000`, it also starts Kafka consumer to listen for events and save unroll-ed notifications to db
75+
76+
77+
## Heroku deployment
78+
79+
- git init
80+
- git add .
81+
- git commit -m 'message'
82+
- heroku login
83+
- heroku create [application-name] // choose a name, or leave it empty to use generated one
84+
- heroku addons:create heroku-postgresql:hobby-dev
85+
- note that you may need to wait for several minutes before the PostgreSQL database is ready
86+
- optionally, to set some environment variables in heroku, run command like:
87+
`heroku config:set KAFKA_CLIENT_CERT=path/to/certificate/file`
88+
`heroku config:set KAFKA_CLIENT_CERT_KEY=path/to/private/key/file`
89+
`heroku config:set KAFKA_GROUP_ID=some-group`
90+
etc.
91+
- git push heroku master // push code to Heroku
92+
93+
94+
## Verification
95+
96+
- start the app following above sections
97+
- in Postman, using the bus API collection and environment, run the `POST /events` / `Post Connect event` test,
98+
you may run it multiple times to create multiple events in Kafka,
99+
then you may watch the console output in the app, it should show info about handling the events
100+
- in Postman, using the notification server API collection and environment, run the tests
101+
102+
103+
## Swagger
104+
105+
Swagger API definition is provided at `docs/swagger_api.yaml`,
106+
you may check it at `http://editor.swagger.io`.
107+

config/default.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* The configuration file.
3+
*/
4+
5+
module.exports = {
6+
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
7+
PORT: process.env.PORT || 3000,
8+
9+
JWT_SECRET: process.env.JWT_SECRET || 'hjijfvbw859',
10+
11+
DATABASE_URL: process.env.DATABASE_URL || 'postgres://postgres:123456@localhost:5432/notification',
12+
DATABASE_OPTIONS: {
13+
dialect: 'postgres',
14+
pool: {
15+
max: 5,
16+
min: 0,
17+
idle: 10000,
18+
},
19+
},
20+
21+
// comma separated Kafka hosts
22+
KAFKA_URL: process.env.KAFKA_URL || 'ec2-34-205-227-216.compute-1.amazonaws.com:9096,ec2-34-233-75-247.compute-1.amazonaws.com:9096,ec2-34-198-118-170.compute-1.amazonaws.com:9096,ec2-34-231-150-104.compute-1.amazonaws.com:9096,ec2-34-233-209-20.compute-1.amazonaws.com:9096,ec2-34-233-131-252.compute-1.amazonaws.com:9096,ec2-52-205-198-73.compute-1.amazonaws.com:9096,ec2-52-4-109-80.compute-1.amazonaws.com:9096', // eslint-disable-line max-len
23+
24+
// ignore prefix for topics in the Kafka, e.g.
25+
// 'joan-26673.notifications.connect.project.updated' is considered as 'notifications.connect.project.updated'
26+
KAFKA_TOPIC_IGNORE_PREFIX: process.env.KAFKA_TOPIC_IGNORE_PREFIX || 'joan-26673.',
27+
28+
// when notification server is deployed to multiple instances, the instances should use same group id so that
29+
// Kafka event is not handled duplicately in the group, an event is handled by only one instance in the group
30+
KAFKA_GROUP_ID: process.env.KAFKA_GROUP_ID || 'tc-notification-server',
31+
32+
// Kafka connection certificate, 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 certificate file or certificate content
35+
KAFKA_CLIENT_CERT: process.env.KAFKA_CLIENT_CERT ||
36+
`-----BEGIN CERTIFICATE-----
37+
MIIDQzCCAiugAwIBAgIBADANBgkqhkiG9w0BAQsFADAyMTAwLgYDVQQDDCdjYS1j
38+
YmJiNGVkZi1mNDFhLTRjNzMtYTg5OC01NDYyMjhkNmQyNDIwHhcNMTcwOTI3MDUw
39+
MTIyWhcNMjcwOTI3MDUwMTIyWjAZMRcwFQYDVQQDDA51ODFvcjFsdTl2dTB1bzCC
40+
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKjkcV/BXD13Q09bRKohN/sK
41+
iDMGFA2QZ57TGd7svX7iZQIk9HmXEPd5zCTHZ6nZBbcGDQ9P+zGlJIGZQuRVKLOn
42+
ZPilaeUHRGrvCWGJZ6lVPNqInC1STHTfFJhcUNMG6qaD2ayBAw16f13vD1wGEWCm
43+
/DRfvrjIp3JIetGdKctmdiGLYH7CQecRW88Czx9e3Vpl1nxGcNAPDDCj0nRwuPse
44+
fVrg5onPX355om+Ct/teSeSONhio4dFL3fwl47CXFCReZFYOrBoLexfHXHumJ/Kp
45+
TQW+k056hhoagykcpTf8uNjxNRalDPkw7ngt+1IzatbhOacea1/WUE1444eEak8C
46+
AwEAAaN9MHswHQYDVR0OBBYEFJJzd6OZVLthBkhVZEW42CR8NJbtMFoGA1UdIwRT
47+
MFGAFORcypfrQ0tfS5tyBdv89jfpZEMaoTakNDAyMTAwLgYDVQQDDCdjYS1jYmJi
48+
NGVkZi1mNDFhLTRjNzMtYTg5OC01NDYyMjhkNmQyNDKCAQAwDQYJKoZIhvcNAQEL
49+
BQADggEBALVAdOWXdnSqhucXHjpIf0lxlH6WhzhhlctLCreSf+/7y6pPVpWSVIEl
50+
seVxOE5tsO2OzdtGgN2t7rr6bHuakL5rk9rH06r1jYAVQBR+T6SSLFbSVzl4Q5TO
51+
b+T9/sHx5QtXSYgMh4FhZcBjrmDmWvpd42Y4MPfjLTqTP8RWHHib8E4/FYS9txk6
52+
WoxrKcgnm/RmOcFWrjNjgm6JJprO1BSnbc2i/Rs5rxG2tRTTmXp6d7QCRa0bdhKz
53+
yETgcStnaVvyh64zhls3xXBm06rvpu2wwo6QHcPeekvQwxQvb63oovD8b+pFJri+
54+
6MBSQE4TtQenlVx7Ksy9UdNmU21xCYU=
55+
-----END CERTIFICATE-----
56+
`,
57+
58+
// Kafka connection private key, optional;
59+
// if not provided, then SSL connection is not used, direct insecure connection is used;
60+
// if provided, it can be either path to private key file or private key content
61+
KAFKA_CLIENT_CERT_KEY: process.env.KAFKA_CLIENT_CERT_KEY ||
62+
`-----BEGIN RSA PRIVATE KEY-----
63+
MIIEogIBAAKCAQEAqORxX8FcPXdDT1tEqiE3+wqIMwYUDZBnntMZ3uy9fuJlAiT0
64+
eZcQ93nMJMdnqdkFtwYND0/7MaUkgZlC5FUos6dk+KVp5QdEau8JYYlnqVU82oic
65+
LVJMdN8UmFxQ0wbqpoPZrIEDDXp/Xe8PXAYRYKb8NF++uMinckh60Z0py2Z2IYtg
66+
fsJB5xFbzwLPH17dWmXWfEZw0A8MMKPSdHC4+x59WuDmic9ffnmib4K3+15J5I42
67+
GKjh0Uvd/CXjsJcUJF5kVg6sGgt7F8dce6Yn8qlNBb6TTnqGGhqDKRylN/y42PE1
68+
FqUM+TDueC37UjNq1uE5px5rX9ZQTXjjh4RqTwIDAQABAoIBAEGCOid2DJ0awVTq
69+
hbunntsUvrdryCNqu4ZzQzmgge/RSHSIePsgiUg0SeaKIb9Tmk/fXPlvgHNFJt/N
70+
3pBKJ7tnVlbLckOPig4gIXdfoIGhujTZgBpkLZu3W3mtdPwlVqa3xZqPf+uedACv
71+
VTnQcLUYkAKQkJ2D1s8RJfJgD3IA7nbZkzjVdUFdpl5m2Rijs3oLvVYVsAJBSsJK
72+
AjGWobf9pgvXhUnBxmtWKEYsnrAwNF8j+uXo8uTXZj6KMWSmKMI5urykKw/LiSk3
73+
u0IsweCE2cqtTgP3Os5b+au/SVNfFlNOLlic/XX3Z28AvupfuoNWx30VpUsqFBE8
74+
LQEG9EECgYEA3Z6QJKcdMmETN6C0+nMAqdibqMv4su3dmfW3M3Hw4IH/pdsQ7aOa
75+
tn2w01BxYaYfaPjN4cksmJnYLyAHp3D8nxopKtYnS+ky162Wya1ETowjd5+0X0Lq
76+
tMGATPzqcysVt+OO+stRuTkLKXy0OANH1OCEhzlPtFEbYDmKt6srRAkCgYEAwxfe
77+
Ky5eJB63sEkUg0QbXsDr5to1RMrvxjmWVF51LXHBSJl/UFde6l8fOHVtDbG08XGR
78+
lIsQ4f4vsbNOiR7bim0opYPxcxWCD13GBP1u0eUbBPpU4ac0JT12uMYRg9bB7RMl
79+
3eWJU3qmddeAOq0oCsC7aimEFih6QCr4TNcxQZcCgYBddrzFqHDIyWXoZO9OXGfg
80+
OYjUNEmLdIOrpZQAr0Ht/QVK9kt6XTAnXHTRebCHhR7kD2IMoeIb7W3d2f1AYYc4
81+
tji8ZxqlihC2IvBf16HiGnnuvjy8nCUN3Dl2vodF0NrU9bRcEplBq0wI0B3VLZUC
82+
szlRKhtyKW6JM1tMQHT7uQKBgEOP+Hirzh5kJOj/5gKvi2r9FLUVzGzOessDFnSR
83+
YbMjOfSSc+y21UAFQSKkR+f+KtOSqP/wSSB6jrnThtclwJHny7PGRc+9GxWHPBRu
84+
T/qQhRLsPokHBp/+8SZ8MYSe0vnvL6Xw3+XxC8SzpMytOri+lijlx8CEtBGUz/iM
85+
bZpxAoGAEnJFUEGCB1ta3RQpI5L4nH2Rex0Avv8rkXGK2T/t5z2h8Ujg4WW3J7DD
86+
Jp8xItVz3sqz5aCg+EvcewSGZ18AC+9cbxrbI2I83jQDHw+DQmVUyR6rl5+r+S6O
87+
69wdZ08Y/jYkltb5PbhPqs0Kfr86cUqBuKEptRtto6Wto3k/Za4=
88+
-----END RSA PRIVATE KEY-----
89+
`,
90+
};

connect/config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* The configuration file for connectNotificationServer.js.
3+
*/
4+
5+
module.exports = {
6+
TC_API_BASE_URL: process.env.TC_API_BASE_URL || 'https://api.topcoder-dev.com/v4',
7+
TC_ADMIN_TOKEN: process.env.TC_ADMIN_TOKEN || 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoic3VzZXIxIiwiZXhwIjoxNTEwNTAxNjM5LCJ1c2VySWQiOiI0MDE1MzkzOCIsImlhdCI6MTUwOTYzNzYzOSwiZW1haWwiOiJtdHdvbWV5QGJlYWtzdGFyLmNvbSIsImp0aSI6IjIzZTE2YjA2LWM1NGItNDNkNS1iY2E2LTg0ZGJiN2JiNDA0NyJ9.Pp0GERZZGxFbNvplc-B4rKJ2DB7IABpcVQTWGSuJItY', // eslint-disable-line max-len
8+
};

connect/connectNotificationServer.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* This is TopCoder connect notification server.
3+
*/
4+
'use strict';
5+
6+
const notificationServer = require('../index');
7+
const request = require('superagent');
8+
const _ = require('lodash');
9+
const config = require('./config');
10+
11+
// set configuration for the server, see ../config/default.js for available config parameters
12+
// setConfig should be called before initDatabase and start functions
13+
notificationServer.setConfig({ LOG_LEVEL: 'debug' });
14+
15+
// add topic handlers,
16+
// handler is used to find user ids that should receive notifications for a message of a topic,
17+
// it is defined as: function(topic, message, callback),
18+
// the topic is topic name,
19+
// the message is JSON event message,
20+
// the callback is function(error, userIds), where userIds is an array of user ids to receive notifications
21+
const handler = (topic, message, callback) => {
22+
const projectId = message.projectId;
23+
if (!projectId) {
24+
return callback(new Error('Missing projectId in the event message'));
25+
}
26+
27+
// get project details
28+
request
29+
.get(`${config.TC_API_BASE_URL}/projects/${projectId}`)
30+
.set('accept', 'application/json')
31+
.set('authorization', `Bearer ${config.TC_ADMIN_TOKEN}`)
32+
.end((err, res) => {
33+
if (err) {
34+
return callback(err);
35+
}
36+
if (!_.get(res, 'body.result.success')) {
37+
return callback(new Error(`Failed to get project details of project id: ${projectId}`));
38+
}
39+
// return member user ids
40+
callback(null, _.map(_.get(res, 'body.result.content.members', []), (member) => member.userId));
41+
});
42+
};
43+
44+
notificationServer.addTopicHandler('notifications.connect.project.created', handler);
45+
notificationServer.addTopicHandler('notifications.connect.project.updated', handler);
46+
notificationServer.addTopicHandler('notifications.connect.message.posted', handler);
47+
notificationServer.addTopicHandler('notifications.connect.message.edited', handler);
48+
notificationServer.addTopicHandler('notifications.connect.message.deleted', handler);
49+
notificationServer.addTopicHandler('notifications.connect.project.submittedForReview', handler);
50+
51+
// init database, it will clear and re-create all tables
52+
notificationServer
53+
.initDatabase()
54+
.then(() => notificationServer.start())
55+
.catch((e) => console.log(e)); // eslint-disable-line no-console
56+
57+
// if no need to init database, then directly start the server:
58+
// notificationServer.start();

0 commit comments

Comments
 (0)