Skip to content

Commit baefcd0

Browse files
author
Vikas Agarwal
committed
Added logic for creating/deleting a topic for a phase created/deleted for a project.
1 parent c2d1e76 commit baefcd0

File tree

5 files changed

+246
-6
lines changed

5 files changed

+246
-6
lines changed

config/custom-environment-variables.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"VALID_ISSUERS": "VALID_ISSUERS",
3232
"jwksUri": "JWKS_URI",
3333
"busApiUrl": "BUS_API_URL",
34+
"messageApiUrl": "MESSAGE_SERVICE_URL",
3435
"AUTH0_URL" : "AUTH0_URL",
3536
"AUTH0_CLIENT_ID": "AUTH0_CLIENT_ID",
3637
"AUTH0_CLIENT_SECRET": "AUTH0_CLIENT_SECRET",

config/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"validIssuers": "[\"https:\/\/topcoder-newauth.auth0.com\/\",\"https:\/\/api.topcoder-dev.com\"]",
3737
"jwksUri": "",
3838
"busApiUrl": "http://api.topcoder-dev.com/v5",
39+
"messageApiUrl": "http://api.topcoder-dev.com/v5",
3940
"busApiToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicHJvamVjdC1zZXJ2aWNlIiwiaWF0IjoxNTEyNzQ3MDgyLCJleHAiOjE1MjEzODcwODJ9.PHuNcFDaotGAL8RhQXQMdpL8yOKXxjB5DbBIodmt7RE",
4041
"HEALTH_CHECK_URL": "_health",
4142
"maxPhaseProductCount": 1,

src/events/busApi.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ module.exports = (app, logger) => {
257257
projectName: project.name,
258258
userId: req.authUser.userId,
259259
initiatorUserId: req.authUser.userId,
260+
phase: created,
260261
}, logger);
261262
}).catch(err => null); // eslint-disable-line no-unused-vars
262263
});

src/events/projectPhases/index.js

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@ import config from 'config';
77
import _ from 'lodash';
88
import Promise from 'bluebird';
99
import util from '../../util';
10+
import messageService from '../../services/messageService';
1011

1112
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
1213
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
1314

1415
const eClient = util.getElasticSearchClient();
1516

1617
/**
17-
* Handler for project phase creation event
18+
* Indexes the project phase in the elastic search.
19+
*
1820
* @param {Object} logger logger to log along with trace id
1921
* @param {Object} msg event payload
20-
* @param {Object} channel channel to ack, nack
2122
* @returns {undefined}
2223
*/
23-
const projectPhaseAddedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
24+
const indexProjectPhase = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names
2425
try {
2526
const data = JSON.parse(msg.content.toString());
2627
const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: data.projectId });
@@ -29,6 +30,50 @@ const projectPhaseAddedHandler = Promise.coroutine(function* (logger, msg, chann
2930
const merged = _.assign(doc._source, { phases }); // eslint-disable-line no-underscore-dangle
3031
yield eClient.update({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: data.projectId, body: { doc: merged } });
3132
logger.debug('project phase added to project document successfully');
33+
} catch (error) {
34+
logger.error('Error handling indexing the project phase', error);
35+
// throw the error back to nack the bus
36+
throw error;
37+
}
38+
});
39+
40+
/**
41+
* Creates a new phase topic in message api.
42+
*
43+
* @param {Object} logger logger to log along with trace id
44+
* @param {Object} msg event payload
45+
* @returns {undefined}
46+
*/
47+
const createPhaseTopic = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names
48+
try {
49+
const phase = JSON.parse(msg.content.toString());
50+
const topic = yield messageService.createTopic({
51+
reference: 'project',
52+
referenceId: `${phase.projectId}`,
53+
tag: `phase#${phase.id}`,
54+
title: phase.name,
55+
body: 'Welcome!!! Please use this channel for communication around the phase.',
56+
}, logger);
57+
logger.debug('topic for the phase created successfully');
58+
logger.debug(topic);
59+
} catch (error) {
60+
logger.error('Error in creating topic for the project phase', error);
61+
// don't throw the error back to nack the bus, because we don't want to get multiple topics per phase
62+
// we can create topic for a phase manually, if somehow it fails
63+
}
64+
});
65+
66+
/**
67+
* Handler for project phase creation event
68+
* @param {Object} logger logger to log along with trace id
69+
* @param {Object} msg event payload
70+
* @param {Object} channel channel to ack, nack
71+
* @returns {undefined}
72+
*/
73+
const projectPhaseAddedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
74+
try {
75+
yield indexProjectPhase(logger, msg, channel);
76+
yield createPhaseTopic(logger, msg);
3277
channel.ack(msg);
3378
} catch (error) {
3479
logger.error('Error handling project.phase.added event', error);
@@ -73,13 +118,13 @@ const projectPhaseUpdatedHandler = Promise.coroutine(function* (logger, msg, cha
73118
});
74119

75120
/**
76-
* Handler for project phase deleted event
121+
* Removes the project phase from the elastic search.
122+
*
77123
* @param {Object} logger logger to log along with trace id
78124
* @param {Object} msg event payload
79-
* @param {Object} channel channel to ack, nack
80125
* @returns {undefined}
81126
*/
82-
const projectPhaseRemovedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
127+
const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names
83128
try {
84129
const data = JSON.parse(msg.content.toString());
85130
const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: data.projectId });
@@ -94,6 +139,45 @@ const projectPhaseRemovedHandler = Promise.coroutine(function* (logger, msg, cha
94139
},
95140
});
96141
logger.debug('project phase removed from project document successfully');
142+
} catch (error) {
143+
logger.error('Error in removing project phase from index', error);
144+
// throw the error back to nack the bus
145+
throw error;
146+
}
147+
});
148+
149+
/**
150+
* Removes the phase topic from the message api.
151+
*
152+
* @param {Object} logger logger to log along with trace id
153+
* @param {Object} msg event payload
154+
* @returns {undefined}
155+
*/
156+
const removePhaseTopic = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names
157+
try {
158+
const phase = JSON.parse(msg.content.toString());
159+
const phaseTopic = yield messageService.getPhaseTopic(phase.projectId, phase.id, logger);
160+
yield messageService.deletePosts(phaseTopic.id, phaseTopic.postIds, logger);
161+
yield messageService.deleteTopic(phaseTopic.id, logger);
162+
logger.debug('topic for the phase removed successfully');
163+
} catch (error) {
164+
logger.error('Error in removing topic for the project phase', error);
165+
// don't throw the error back to nack the bus
166+
// we can delete topic for a phase manually, if somehow it fails
167+
}
168+
});
169+
170+
/**
171+
* Handler for project phase deleted event
172+
* @param {Object} logger logger to log along with trace id
173+
* @param {Object} msg event payload
174+
* @param {Object} channel channel to ack, nack
175+
* @returns {undefined}
176+
*/
177+
const projectPhaseRemovedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
178+
try {
179+
yield removePhaseFromIndex(logger, msg, channel);
180+
yield removePhaseTopic(logger, msg);
97181
channel.ack(msg);
98182
} catch (error) {
99183
logger.error('Error fetching project document from elasticsearch', error);

src/services/messageService.js

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import config from 'config';
2+
import _ from 'lodash';
3+
4+
const Promise = require('bluebird');
5+
const axios = require('axios');
6+
const tcCoreLibAuth = require('tc-core-library-js').auth;
7+
8+
const m2m = tcCoreLibAuth.m2m(config);
9+
10+
let client = null;
11+
12+
/**
13+
* Get Http client to bus api
14+
* @param {Object} logger object
15+
* @return {Object} Http Client to bus api
16+
*/
17+
async function getClient(logger) {
18+
if (client) return client;
19+
const msgApiUrl = config.get('messageApiUrl');
20+
try {
21+
const token = await m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET);
22+
client = axios.create({ baseURL: msgApiUrl });
23+
24+
// Alter defaults after instance has been created
25+
client.defaults.headers.common.Authorization = `Bearer ${token}`;
26+
27+
// Add a response interceptor
28+
client.interceptors.response.use(function (res) { // eslint-disable-line
29+
return res;
30+
}, function (error) { // eslint-disable-line
31+
if (error.response) {
32+
// The request was made and the server responded with a status code
33+
// that falls out of the range of 2xx
34+
logger.debug(error.response.data);
35+
logger.debug(error.response.status);
36+
logger.debug(error.response.headers);
37+
} else if (error.request) {
38+
// The request was made but no response was received
39+
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
40+
// http.ClientRequest in node.js
41+
logger.debug(error.request);
42+
} else {
43+
// Something happened in setting up the request that triggered an Error
44+
logger.debug(error.message);
45+
}
46+
logger.debug(error.config);
47+
// Ingore response errors
48+
return Promise.reject(error);
49+
});
50+
51+
return client;
52+
} catch (err) {
53+
return Promise.reject(`Bus api calling - Error in genearting m2m token : ${err.message}`);
54+
}
55+
}
56+
57+
/**
58+
* Creates a new topic in message api
59+
*
60+
* @param {Object} topic the topic, should be a JSON object
61+
* @param {Object} logger object
62+
* @return {Promise} new topic promise
63+
*/
64+
function createTopic(topic, logger) {
65+
logger.debug(`createTopic for topic: ${JSON.stringify(topic)}`);
66+
return getClient(logger).then((msgClient) => {
67+
logger.debug('calling message service');
68+
return msgClient.post('/topics/create', topic)
69+
.then((resp) => {
70+
logger.debug('Topic created successfully');
71+
logger.debug(`Topic created successfully [status]: ${resp.status}`);
72+
logger.debug(`Topic created successfully [data]: ${resp.data}`);
73+
})
74+
.catch((error) => {
75+
logger.debug('Error creating topic');
76+
logger.error(error);
77+
// eslint-disable-line
78+
});
79+
}).catch((errMessage) => {
80+
logger.debug(errMessage);
81+
});
82+
}
83+
84+
/**
85+
* Deletes the given posts for the given topic.
86+
*
87+
* @param {Integer} topicId id of the topic
88+
* @param {Array} postIds array of post ids to be deleted, array of integers
89+
* @param {Object} logger object
90+
* @return {Promise} delete posts promise
91+
*/
92+
function deletePosts(topicId, postIds, logger) {
93+
logger.debug(`deletePosts for topicId: ${topicId} and postIds: ${postIds}`);
94+
const promises = [];
95+
if (postIds && postIds.length > 0) {
96+
postIds.forEach((postId) => {
97+
promises.push(getClient(logger).then((msgClient) => {
98+
logger.debug(`calling message service for deleting post#${postId}`);
99+
return msgClient.delete(`/topics/${topicId}/posts/${postId}/remove`);
100+
}));
101+
});
102+
}
103+
if (promises.length > 0) {
104+
return Promise.all(promises).then(() => logger.debug(`All posts deleted for topic ${topicId}`));
105+
}
106+
return Promise.resolve();
107+
}
108+
109+
/**
110+
* Fetches the topic of given phase of the project.
111+
*
112+
* @param {Integer} projectId id of the project
113+
* @param {Integer} phaseId id of the phase of the project
114+
* @param {Object} logger object
115+
* @return {Promise} topic promise
116+
*/
117+
function getPhaseTopic(projectId, phaseId, logger) {
118+
logger.debug(`getPhaseTopic for phaseId: ${phaseId}`);
119+
return getClient(logger).then((msgClient) => {
120+
logger.debug(`calling message service for fetching phaseId#${phaseId}`);
121+
return msgClient.get('/topics/list', {
122+
params: { filter: `reference=project&referenceId=${projectId}&tag=phase#${phaseId}` },
123+
}).then((resp) => {
124+
const topics = _.get(resp.data, 'result.content', []);
125+
if (topics && topics.length > 0) {
126+
return topics[0];
127+
}
128+
return null;
129+
});
130+
});
131+
}
132+
133+
/**
134+
* Deletes the given topic.
135+
*
136+
* @param {Integer} topicId id of the topic
137+
* @param {Object} logger object
138+
* @return {Promise} delete topic promise
139+
*/
140+
function deleteTopic(topicId, logger) {
141+
logger.debug(`deleteTopic for topicId: ${topicId}`);
142+
return getClient(logger).then((msgClient) => {
143+
logger.debug(`calling message service for deleting topic#${topicId}`);
144+
return msgClient.delete(`/topics/${topicId}/remove`);
145+
});
146+
}
147+
148+
module.exports = {
149+
createTopic,
150+
deletePosts,
151+
getPhaseTopic,
152+
deleteTopic,
153+
};

0 commit comments

Comments
 (0)