Skip to content

Commit 0c2b0e9

Browse files
committed
feat: change reviewer payment
1 parent 6f32fe0 commit 0c2b0e9

File tree

11 files changed

+191
-73
lines changed

11 files changed

+191
-73
lines changed

challenge-api-v5-mock/mock-challenge-api.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,28 @@ const _ = require('lodash')
77

88
// Sample challenge
99
const sampleChallenge = {
10-
'id': '96059e8d-4761-4978-9a14-c86ae6b971c3',
11-
'legacyId': 30049360,
12-
'type': 'Code',
13-
'track': 'Develop',
14-
'name': 'Test Challenge 1',
15-
'description': 'Test Challenge 1 - Description',
16-
'challengeSettings': [
10+
id: '96059e8d-4761-4978-9a14-c86ae6b971c3',
11+
legacyId: 30049360,
12+
type: 'Code',
13+
track: 'Develop',
14+
name: 'Test Challenge 1',
15+
description: 'Test Challenge 1 - Description',
16+
challengeSettings: [
1717
{
18-
'type': 'setting1',
19-
'value': 'value1'
18+
type: 'setting1',
19+
value: 'value1'
2020
}
2121
],
22-
'metadata': [
22+
metadata: [
2323
{
24-
'name': 'reviewerPrize',
25-
'value': '5'
24+
name: 'reviewerPrize',
25+
value: '5'
2626
}
2727
],
28-
'created': '2019-03-02T14:35:53.948Z',
29-
'createdBy': 'Copilot1',
30-
'updated': '2019-03-02T14:35:53.948Z',
31-
'updatedBy': 'Copilot1'
28+
created: '2019-03-02T14:35:53.948Z',
29+
createdBy: 'Copilot1',
30+
updated: '2019-03-02T14:35:53.948Z',
31+
updatedBy: 'Copilot1'
3232
}
3333

3434
const responses = {
@@ -40,7 +40,7 @@ const mockChallengeV5Api = http.createServer((req, res) => {
4040
return send(res, 200, responses[req.url])
4141
} else {
4242
// 404 for other routes
43-
return send(res, 404, {message: 'Challenge not found'})
43+
return send(res, 404, { message: 'Challenge not found' })
4444
}
4545
})
4646

config/default.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module.exports = {
3232

3333
IS_CREATE_FORUM: process.env.IS_CREATE_FORUM || true,
3434

35+
UPDATE_CHALLENGE_TOPIC: process.env.UPDATE_CHALLENGE_TOPIC || 'challenge.notification.update',
3536
CREATE_CHALLENGE_RESOURCE_TOPIC: process.env.CREATE_CHALLENGE_RESOURCE_TOPIC || 'challenge.action.resource.create',
3637
DELETE_CHALLENGE_RESOURCE_TOPIC: process.env.DELETE_CHALLENGE_RESOURCE_TOPIC || 'challenge.action.resource.delete',
3738

@@ -41,6 +42,7 @@ module.exports = {
4142
CHALLENGE_API_V4_URL: process.env.CHALLENGE_API_V4_URL || 'https://api.topcoder-dev.com/v4/challenges',
4243
CHALLENGE_API_V5_URL: process.env.CHALLENGE_API_V5_URL || 'http://localhost:3001/v5/challenges',
4344
RESOURCE_ROLE_API_URL: process.env.RESOURCE_ROLE_API_URL || 'http://localhost:3001/v5/resource-roles',
45+
MEMBER_API_V5_URL: process.env.MEMBER_API_V5_URL || 'https://api.topcoder-dev.com/v5/members',
4446

4547
V4_ES_FEEDER_API_URL: process.env.V4_ES_FEEDER_API_URL || 'https://api.topcoder-dev.com/v4/esfeeder/challenges',
4648
INDEX_CHALLENGE_TIMEOUT: process.env.INDEX_CHALLENGE_TIMEOUT || 2,

config/production.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77

88
CHALLENGE_API_V4_URL: process.env.CHALLENGE_API_V4_URL || 'https://api.topcoder.com/v4/challenges',
99
CHALLENGE_API_V5_URL: process.env.CHALLENGE_API_V5_URL || 'https://api.topcoder.com/v5/challenges',
10+
MEMBER_API_V5_URL: process.env.MEMBER_API_V5_URL || 'https://api.topcoder.com/v5/members',
1011

1112
AUTH0_URL: process.env.AUTH0_URL, // Auth0 credentials for M2M token
1213
AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE || 'https://www.topcoder.com',

src/app.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ const dataHandler = async (messageSet, topic, partition) => Promise.each(message
3939
}
4040
try {
4141
switch (topic) {
42-
case config.CREATE_CHALLENGE_RESOURCE_TOPIC :
42+
case config.CREATE_CHALLENGE_RESOURCE_TOPIC:
4343
await ProcessorService.createChallengeResource(messageJSON)
4444
break
4545
case config.DELETE_CHALLENGE_RESOURCE_TOPIC:
4646
await ProcessorService.deleteChallengeResource(messageJSON)
4747
break
48+
case config.UPDATE_CHALLENGE_TOPIC:
49+
await ProcessorService.updateResourcePayment(messageJSON)
50+
break
4851
default:
4952
throw new Error(`Invalid topic: ${topic}`)
5053
}
@@ -69,7 +72,7 @@ function check () {
6972
return connected
7073
}
7174

72-
const topics = [config.CREATE_CHALLENGE_RESOURCE_TOPIC, config.DELETE_CHALLENGE_RESOURCE_TOPIC]
75+
const topics = [config.CREATE_CHALLENGE_RESOURCE_TOPIC, config.DELETE_CHALLENGE_RESOURCE_TOPIC, config.UPDATE_CHALLENGE_TOPIC]
7376
consumer
7477
.init([{
7578
subscriptions: topics,

src/common/helper.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ async function queryDataFromDB (sql, params) {
153153
const connection = await getInformixConnection()
154154
try {
155155
await connection.beginTransactionAsync()
156-
result = await connection.queryAsync(util.format(sql, ...params))
156+
const queryString = util.format(sql, ...params)
157+
logger.debug(`Querying data from database: ${queryString}`)
158+
result = await connection.queryAsync(queryString)
157159
await connection.commitTransactionAsync()
158160
} catch (e) {
159161
logger.error(`Error in 'queryDataFromDB' ${e}, rolling back transaction`)
@@ -198,7 +200,14 @@ async function forceV4ESFeeder (legacyId) {
198200
}
199201

200202
function isReviewerRole (roleId) {
201-
return roleId === config.REVIEWER_ROLE_ID || roleId === config.ITERATIVE_REVIEWER_ROLE_ID || roleId == config.LEGACY_REVIEWER_ROLE_ID || roleId == config.LEGACY_REVIEWER_ITERATIVE_ROLE_ID
203+
return roleId === config.REVIEWER_ROLE_ID || roleId === config.ITERATIVE_REVIEWER_ROLE_ID || roleId === config.LEGACY_REVIEWER_ROLE_ID || roleId === config.LEGACY_REVIEWER_ITERATIVE_ROLE_ID
204+
}
205+
206+
async function getUserId (handle) {
207+
const token = await getM2Mtoken()
208+
const result = await getRequest(`${config.MEMBER_API_V5_URL}/${handle}`, token)
209+
210+
return result.body.userId;
202211
}
203212

204213
module.exports = {
@@ -213,5 +222,6 @@ module.exports = {
213222
queryDataFromDB,
214223
executeSQLonDB,
215224
forceV4ESFeeder,
216-
isReviewerRole
225+
isReviewerRole,
226+
getUserId
217227
}

src/dao/ProjectPaymentDAO.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const _ = require('lodash')
2+
23
const helper = require('../common/helper')
34
const logger = require('../common/logger')
45

@@ -23,8 +24,16 @@ VALUES
2324
(?, ?, ?, ?, ?, CURRENT, CURRENT, ?)`
2425

2526
// reference Direct App: https://github.com/appirio-tech/direct-app/blob/dev/components/project_payment_management/src/java/main/com/topcoder/management/payment/impl/persistence/DatabaseProjectPaymentPersistence.java#L87
26-
const DELETE_PROJECT_PAYMENT = `
27-
DELETE FROM project_payment WHERE resource_id = ?
27+
const DELETE_PROJECT_PAYMENT = 'DELETE FROM project_payment WHERE resource_id = ?'
28+
29+
const QUERY_UPDATE_PROJECT_PAYMENT = 'UPDATE project_payment SET amount = ?, modify_user = ?, modify_date = CURRENT WHERE project_payment_id = ?'
30+
31+
const QUERY_PROJECT_PAYMENT_BY_RESOURCE_ROLES = `
32+
SELECT r.resource_id as resource_id, r.resource_role_id as role_id, pp.project_payment_id as project_payment_id, pp.amount as amount
33+
FROM resource r
34+
LEFT JOIN project_payment pp ON pp.resource_id = r.resource_id
35+
WHERE r.project_id = %d
36+
AND r.resource_role_id in (%s)
2837
`
2938

3039
async function persistReviewerPayment (userId, resourceId, amount, projectPaymentTypeId) {
@@ -57,7 +66,30 @@ async function removeReviewerPayment (resourceId) {
5766
await helper.executeSQLonDB(DELETE_PROJECT_PAYMENT, [resourceId])
5867
}
5968

69+
async function updateProjectPayment(userId, projectPaymentId, amount) {
70+
const connection = await helper.getInformixConnection()
71+
try {
72+
logger.info('Open connection.')
73+
await connection.beginTransactionAsync()
74+
const query = await helper.prepare(connection, QUERY_UPDATE_PROJECT_PAYMENT)
75+
logger.info('Using parameters: ', [amount, userId, projectPaymentId])
76+
await query.executeAsync([amount, userId, projectPaymentId])
77+
await connection.commitTransactionAsync()
78+
} catch (e) {
79+
await connection.rollbackTransactionAsync()
80+
logger.error(`Error in 'updateProjectPayment' ${e}`)
81+
} finally {
82+
await connection.closeAsync()
83+
}
84+
}
85+
86+
async function getChallengePaymentsByRoleIds (projectId, roleIds) {
87+
return helper.queryDataFromDB(QUERY_PROJECT_PAYMENT_BY_RESOURCE_ROLES, [projectId, roleIds.join(',')])
88+
}
89+
6090
module.exports = {
6191
persistReviewerPayment,
62-
removeReviewerPayment
92+
removeReviewerPayment,
93+
getChallengePaymentsByRoleIds,
94+
updateProjectPayment
6395
}

src/services/ProcessorService.js

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ const logger = require('../common/logger')
88
const helper = require('../common/helper')
99
const ResourceDirectManager = require('./ResourceDirectManager')
1010
const ProjectServices = require('./ProjectService')
11+
const ProjectPaymentDAO = require('../dao/ProjectPaymentDAO')
1112
const notificationService = require('./NotificationService')
12-
const {isStudio} = require('../common/utils')
13+
const { isStudio } = require('../common/utils')
1314

1415
/**
1516
* Check if a challenge exists on legacy (v4)
@@ -96,10 +97,10 @@ async function _updateChallengeResource (message, isDelete) {
9697

9798
if (resourceRole.id === config.SUBMITTER_ROLE_ID && !isTask) {
9899
// force sync v4 elasticsearch service
99-
logger.debug(`Start v4 challenge reindexing to the elasticsearch service`)
100+
logger.debug('Start v4 challenge reindexing to the elasticsearch service')
100101
await helper.forceV4ESFeeder(_.get(v5Challenge, 'legacyId'))
101102
await new Promise(resolve => setTimeout(resolve, config.INDEX_CHALLENGE_TIMEOUT * 1000))
102-
logger.debug(`End v4 challenge reindexing to the elasticsearch service`)
103+
logger.debug('End v4 challenge reindexing to the elasticsearch service')
103104
if (isDelete) {
104105
logger.debug(`v4 Unregistering Submitter ${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/unregister?userId=${userId} - ${JSON.stringify(body)}`)
105106
await helper.postRequest(`${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/unregister?userId=${userId}`, {}, m2mToken)
@@ -182,9 +183,78 @@ async function deleteChallengeResource (message) {
182183

183184
deleteChallengeResource.schema = createChallengeResource.schema
184185

186+
187+
/**
188+
* Updates resource payment
189+
*
190+
* @param {Object} message The message containing the information of challenge whose resource payment to update
191+
*/
192+
193+
async function updateResourcePayment (message) {
194+
logger.info(`Received update resource payment message : ${JSON.stringify(message)}`)
195+
const { payload: { metadata, legacyId, updatedBy } } = message
196+
197+
// if legacy challenge has not yet been created, we don't need to modify any payment records
198+
// as they don't exist yet :) challenge.action.resource.create should take care of that
199+
if (legacyId == null) {
200+
logger.info(`LegacyId is null. Skipping update resource payment message : ${JSON.stringify(message)}`)
201+
return;
202+
}
203+
204+
logger.info(`Get reviewer payments for challenge ${legacyId}`)
205+
const reviewerPaymentAmounts = await ProjectPaymentDAO.getChallengePaymentsByRoleIds(legacyId, [config.LEGACY_REVIEWER_ROLE_ID, config.LEGACY_REVIEWER_ITERATIVE_ROLE_ID])
206+
207+
logger.info(`Reviewer payments for legacyId: ${legacyId} are -> ${JSON.stringify(reviewerPaymentAmounts)}`)
208+
209+
const reviewerPrize = _.find(metadata, { name: 'reviewerPrize' })
210+
logger.info(`reviewerPrize: ${JSON.stringify(reviewerPrize)}`)
211+
212+
try {
213+
reviewerPrize.value = parseFloat(reviewerPrize.value)
214+
} catch (err) {
215+
logger.error(`Invalid reviewerPrize: ${JSON.stringify(reviewerPrize)}`)
216+
return;
217+
}
218+
219+
const userId = await helper.getUserId(updatedBy);
220+
221+
for (const reviewerPaymentAmount of reviewerPaymentAmounts) {
222+
logger.info(`Payment Amt: ${reviewerPrize.value || 0}`)
223+
224+
const { resource_id: resourceId, role_id: roleId, project_payment_id: projectPaymentId, amount } = reviewerPaymentAmount
225+
if (projectPaymentId == null && reviewerPrize != null) {
226+
logger.info(`Add new payment for resource ${resourceId} with role ${roleId}.`)
227+
await ProjectPaymentDAO.persistReviewerPayment(userId, resourceId, reviewerPrize.value, config.LEGACY_PROJECT_REVIEW_PAYMENT_TYPE_ID);
228+
} else {
229+
logger.info(`Update reviewer payment ${amount} for resource ${resourceId} with role ${roleId} and payment ${projectPaymentId} with amount ${reviewerPrize.value}`)
230+
await ProjectPaymentDAO.updateProjectPayment(userId, projectPaymentId, reviewerPrize.value)
231+
}
232+
}
233+
}
234+
235+
updateResourcePayment.schema = {
236+
message: Joi.object().keys({
237+
topic: Joi.string().required(),
238+
originator: Joi.string().required(),
239+
timestamp: Joi.date().required(),
240+
'mime-type': Joi.string().required(),
241+
key: Joi.string().allow(null),
242+
payload: Joi.object().keys({
243+
legacyId: Joi.number().integer().positive(),
244+
id: Joi.string().required(),
245+
updatedBy: Joi.string(),
246+
metadata: Joi.array().items(Joi.object().keys({
247+
name: Joi.string().required(),
248+
value: Joi.string().required()
249+
}).unknown(true))
250+
}).unknown(true).required()
251+
}).required()
252+
}
253+
185254
module.exports = {
186255
createChallengeResource,
187-
deleteChallengeResource
256+
deleteChallengeResource,
257+
updateResourcePayment
188258
}
189259

190260
// logger.buildService(module.exports)

src/services/ResourceDirectManager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const { isReviewerRole } = require('../common/helper')
1717
* @param handle
1818
*/
1919
async function assignRole (legacyChallengeId, roleId, userId, handle, copilotPaymentAmount, reviewerPaymentAmount) {
20-
let found = await ProjectServices.resourceExists(legacyChallengeId, roleId, userId)
20+
const found = await ProjectServices.resourceExists(legacyChallengeId, roleId, userId)
2121
const termChecking = true
2222
const eligible = true
2323
if (found) {

test/common/testData.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,45 @@ const submitterMemberId = 23225544 // lazybaer
88
const legacyId = 30049360
99

1010
const submitterPayload = {
11-
'challengeId': challengeUuid,
12-
'roleId': 'bac822d2-725d-4973-9701-360918a09bc0',
13-
'memberId': submitterMemberId
11+
challengeId: challengeUuid,
12+
roleId: 'bac822d2-725d-4973-9701-360918a09bc0',
13+
memberId: submitterMemberId
1414
}
1515

1616
const createResourceMessage = {
17-
'topic': 'challenge.resources.notification.create',
18-
'originator': 'resources-api',
19-
'timestamp': '2019-05-03T15:46:05.575Z',
17+
topic: 'challenge.resources.notification.create',
18+
originator: 'resources-api',
19+
timestamp: '2019-05-03T15:46:05.575Z',
2020
'mime-type': 'application/json',
21-
'payload': submitterPayload
21+
payload: submitterPayload
2222
}
2323

2424
const deleteResourceMessage = {
25-
'topic': 'challenge.resources.notification.delete',
26-
'originator': 'resources-api',
27-
'timestamp': '2019-05-03T15:46:05.575Z',
25+
topic: 'challenge.resources.notification.delete',
26+
originator: 'resources-api',
27+
timestamp: '2019-05-03T15:46:05.575Z',
2828
'mime-type': 'application/json',
29-
'payload': submitterPayload
29+
payload: submitterPayload
3030
}
3131

3232
const existingChallenge = {
33-
'id': challengeUuid,
34-
'legacyId': legacyId,
35-
'type': 'Code'
33+
id: challengeUuid,
34+
legacyId,
35+
type: 'Code'
3636
}
3737

3838
const requiredFields = ['topic', 'originator', 'timestamp', 'mime-type', 'payload', 'payload.challengeId', 'payload.roleId', 'payload.memberId']
3939
const stringFields = ['topic', 'originator', 'mime-type', 'payload.challengeId', 'payload.roleId']
4040
const integerFields = ['payload.memberId']
4141

4242
const testMethods = {
43-
'createChallengeResource': {
43+
createChallengeResource: {
4444
requiredFields,
4545
stringFields,
4646
integerFields,
4747
testMessage: createResourceMessage
4848
},
49-
'deleteChallengeResource': {
49+
deleteChallengeResource: {
5050
requiredFields,
5151
stringFields,
5252
integerFields,

0 commit comments

Comments
 (0)