Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

Commit 84d63ad

Browse files
committed
Merge branch 'develop'
2 parents 034d329 + 49f2052 commit 84d63ad

File tree

5 files changed

+173
-19
lines changed

5 files changed

+173
-19
lines changed

ReadMe.md

+3
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,19 @@ The following parameters can be set in config files or in env variables:
3535
- KAFKA_GROUP_ID: the Kafka group id, default value is 'legacy-challenge-processor'
3636
- KAFKA_ERROR_TOPIC: The kafka error topic.
3737
- BUSAPI_URL: Bus API URL
38+
- MAX_RETRIES: the number of max retries; default value: 3
3839
- RETRY_TIMEOUT: The timeout to retry processing the same message
3940
- CREATE_CHALLENGE_TOPIC: the create challenge Kafka message topic, default value is 'challenge.notification.create'
4041
- UPDATE_CHALLENGE_TOPIC: the update challenge Kafka message topic, default value is 'challenge.notification.update'
42+
- RESOURCE_CREATE_TOPIC: the resource create Kafka message topic, default value is 'challenge.action.resource.create'
4143
- AUTH0_URL: Auth0 URL, used to get TC M2M token
4244
- AUTH0_AUDIENCE: Auth0 audience, used to get TC M2M token
4345
- TOKEN_CACHE_TIME: Auth0 token cache time, used to get TC M2M token
4446
- AUTH0_CLIENT_ID: Auth0 client id, used to get TC M2M token
4547
- AUTH0_CLIENT_SECRET: Auth0 client secret, used to get TC M2M token
4648
- AUTH0_PROXY_SERVER_URL: Proxy Auth0 URL, used to get TC M2M token
4749
- V5_CHALLENGE_API_URL: v5 challenge api url, default value is 'http://localhost:4000/v5/challenges'
50+
- V5_RESOURCES_API_URL: v5 resources api url, default value is 'http://localhost:4000/v5/resources'
4851
- V5_CHALLENGE_TYPE_API_URL: v5 challenge type api url, default value is 'http://localhost:4000/v5/challenge-types'
4952
- V4_CHALLENGE_API_URL: v4 challenge api url, default value is 'http://localhost:4000/v4/challenges'
5053
- V4_TECHNOLOGIES_API_URL: v4 technologies api url, default value is 'http://localhost:4000/v4/technologies'

config/default.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ module.exports = {
1515
KAFKA_GROUP_ID: process.env.KAFKA_GROUP_ID || 'legacy-challenge-processor',
1616
KAFKA_ERROR_TOPIC: process.env.KAFKA_ERROR_TOPIC || 'common.error.reporting',
1717
BUSAPI_URL: process.env.BUSAPI_URL || 'https://api.topcoder-dev.com/v5',
18+
MAX_RETRIES: process.env.MAX_RETRIES || 3,
1819
RETRY_TIMEOUT: process.env.RETRY_TIMEOUT || 10 * 1000,
1920

2021
// Topics to listen
2122
CREATE_CHALLENGE_TOPIC: process.env.CREATE_CHALLENGE_TOPIC || 'challenge.notification.create',
2223
UPDATE_CHALLENGE_TOPIC: process.env.UPDATE_CHALLENGE_TOPIC || 'challenge.notification.update',
24+
RESOURCE_CREATE_TOPIC: process.env.RESOURCE_CREATE_TOPIC || 'challenge.action.resource.create',
2325

2426
// Auth0 parameters
2527
AUTH0_URL: process.env.AUTH0_URL || 'https://topcoder-dev.auth0.com/oauth/token',
@@ -46,18 +48,22 @@ module.exports = {
4648
},
4749
// Topcoder APIs
4850
V5_CHALLENGE_API_URL: process.env.V5_CHALLENGE_API_URL || 'http://localhost:4000/v5/challenges',
51+
V5_RESOURCES_API_URL: process.env.V5_RESOURCES_API_URL || 'http://localhost:4000/v5/resources',
4952
V5_CHALLENGE_TYPE_API_URL: process.env.V5_CHALLENGE_TYPE_API_URL || 'http://localhost:4000/v5/challenge-types',
5053
V4_CHALLENGE_TYPE_API_URL: process.env.V4_CHALLENGE_TYPE_API_URL || 'http://localhost:4000/v4/challenge-types',
5154
V4_CHALLENGE_API_URL: process.env.V4_CHALLENGE_API_URL || 'http://localhost:4000/v4/challenges',
5255
V4_TECHNOLOGIES_API_URL: process.env.V4_TECHNOLOGIES_API_URL || 'http://localhost:4000/v4/technologies',
5356
V4_PLATFORMS_API_URL: process.env.V4_PLATFORMS_API_URL || 'http://localhost:4000/v4/platforms',
5457
V5_PROJECTS_API_URL: process.env.V5_PROJECTS_API_URL || 'https://api.topcoder-dev.com/v5/projects',
5558
V5_CHALLENGE_MIGRATION_API_URL: process.env.V5_CHALLENGE_MIGRATION_API_URL || 'https://api.topcoder-dev.com/v5/challenge-migration',
59+
V4_ES_FEEDER_API_URL: process.env.V4_ES_FEEDER_API_URL || 'https://api.topcoder-dev.com/v4/esfeeder/challenges',
5660

5761
V5_GROUPS_API_URL: process.env.V5_GROUPS_API_URL || 'https://api.topcoder-dev.com/v5/groups',
5862

5963
// PHASE IDs
6064
REGISTRATION_PHASE_ID: process.env.REGISTRATION_PHASE_ID || 'a93544bc-c165-4af4-b55e-18f3593b457a',
6165
SUBMISSION_PHASE_ID: process.env.SUBMISSION_PHASE_ID || '6950164f-3c5e-4bdc-abc8-22aaf5a1bd49',
62-
CHECKPOINT_SUBMISSION_PHASE_ID: process.env.CHECKPOINT_SUBMISSION_PHASE_ID || 'd8a2cdbe-84d1-4687-ab75-78a6a7efdcc8'
66+
CHECKPOINT_SUBMISSION_PHASE_ID: process.env.CHECKPOINT_SUBMISSION_PHASE_ID || 'd8a2cdbe-84d1-4687-ab75-78a6a7efdcc8',
67+
68+
COPILOT_PAYMENT_TYPE: process.env.COPILOT_PAYMENT_TYPE || 'copilot'
6369
}

src/common/helper.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ async function postBusEvent (topic, payload) {
150150
})
151151
}
152152

153+
async function forceV4ESFeeder (legacyId) {
154+
const token = await getM2MToken()
155+
const body = {
156+
param: {
157+
challengeIds: [legacyId]
158+
}
159+
}
160+
await request.put(`${config.V4_ES_FEEDER_API_URL}`).send(body).set({ Authorization: `Bearer ${token}` })
161+
}
162+
153163
module.exports = {
154164
getInformixConnection,
155165
getKafkaOptions,
@@ -158,5 +168,6 @@ module.exports = {
158168
getRequest,
159169
putRequest,
160170
postRequest,
161-
postBusEvent
171+
postBusEvent,
172+
forceV4ESFeeder
162173
}

src/services/ProcessorService.js

+41-17
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const logger = require('../common/logger')
1111
const helper = require('../common/helper')
1212
const constants = require('../constants')
1313
const groupService = require('./groupsService')
14+
const copilotPaymentService = require('./copilotPaymentService')
1415
// TODO: Remove this
1516
// const showdown = require('showdown')
1617
// const converter = new showdown.Converter()
@@ -40,6 +41,21 @@ async function associateChallengeGroups (toBeAdded = [], toBeDeleted = [], chall
4041
}
4142
}
4243

44+
/**
45+
* Set the copilot payment on legacy
46+
* @param {Number|String} legacyChallengeId the legacy challenge ID
47+
* @param {Array} prizeSets the prizeSets array
48+
*/
49+
async function setCopilotPayment (legacyChallengeId, prizeSets = []) {
50+
try {
51+
const copilotPayment = _.get(_.find(prizeSets, p => p.type === config.COPILOT_PAYMENT_TYPE), 'prizes[0].value', null)
52+
await copilotPaymentService.setCopilotPayment(legacyChallengeId, copilotPayment)
53+
} catch (e) {
54+
logger.error('Failed to set the copilot payment!')
55+
logger.debug(e)
56+
}
57+
}
58+
4359
/**
4460
* Get technologies from V4 API
4561
* @param {String} m2mToken token for accessing the API
@@ -291,7 +307,9 @@ async function processCreate (message) {
291307
logger.debug('processCreate :: beforeTry')
292308
try {
293309
const newChallenge = await helper.postRequest(`${config.V4_CHALLENGE_API_URL}`, { param: _.omit(saveDraftContestDTO, ['groupsToBeAdded', 'groupsToBeDeleted']) }, m2mToken)
310+
await helper.forceV4ESFeeder(newChallenge.body.result.content.id)
294311
await associateChallengeGroups(saveDraftContestDTO.groupsToBeAdded, saveDraftContestDTO.groupsToBeDeleted, newChallenge.body.result.content.id)
312+
await setCopilotPayment(newChallenge.body.result.content.id, _.get(message, 'payload.prizeSets'))
295313
await helper.patchRequest(`${config.V5_CHALLENGE_API_URL}/${challengeUuid}`, {
296314
legacy: {
297315
...message.payload.legacy,
@@ -303,6 +321,11 @@ async function processCreate (message) {
303321
},
304322
legacyId: newChallenge.body.result.content.id
305323
}, m2mToken)
324+
// Repost all challenge resource on Kafka so they will get created on legacy by the legacy-challenge-resource-processor
325+
const challengeResourcesResponse = await helper.getRequest(`${config.V5_RESOURCES_API_URL}?challengeId=${challengeUuid}&perPage=100`, m2mToken)
326+
for (const resource of (challengeResourcesResponse.body || [])) {
327+
await helper.postBusEvent(config.RESOURCE_CREATE_TOPIC, _.pick(resource, ['id', 'challengeId', 'memberId', 'memberHandle', 'roleId', 'created', 'createdBy', 'updated', 'updatedBy', 'legacyId']))
328+
}
306329
logger.debug('End of processCreate')
307330
} catch (e) {
308331
logger.error('processCreate Catch', e)
@@ -380,12 +403,21 @@ async function processUpdate (message) {
380403
} catch (e) {
381404
// postponne kafka event
382405
logger.info('Challenge does not exist yet. Will post the same message back to the bus API')
383-
await new Promise((resolve) => {
384-
setTimeout(async () => {
385-
await helper.postBusEvent(config.UPDATE_CHALLENGE_TOPIC, message.payload)
386-
resolve()
387-
}, config.RETRY_TIMEOUT)
388-
})
406+
logger.error(`Error: ${JSON.stringify(e)}`)
407+
408+
const retryCountIdentifier = `${config.KAFKA_GROUP_ID.split(' ').join('_')}_retry_count`
409+
let currentRetryCount = parseInt(_.get(message.payload, retryCountIdentifier, 1), 10)
410+
if (currentRetryCount <= config.MAX_RETRIES) {
411+
await new Promise((resolve) => {
412+
setTimeout(async () => {
413+
currentRetryCount += 1
414+
await helper.postBusEvent(config.UPDATE_CHALLENGE_TOPIC, { ...message.payload, [retryCountIdentifier]: currentRetryCount })
415+
resolve()
416+
}, config.RETRY_TIMEOUT * currentRetryCount)
417+
})
418+
} else {
419+
logger.error(`Failed to process message after ${config.MAX_RETRIES} retries. Aborting...`)
420+
}
389421
return
390422
}
391423

@@ -397,9 +429,10 @@ async function processUpdate (message) {
397429
try {
398430
await helper.putRequest(`${config.V4_CHALLENGE_API_URL}/${message.payload.legacyId}`, { param: _.omit(saveDraftContestDTO, ['groupsToBeAdded', 'groupsToBeDeleted']) }, m2mToken)
399431
await associateChallengeGroups(saveDraftContestDTO.groupsToBeAdded, saveDraftContestDTO.groupsToBeDeleted, message.payload.legacyId)
432+
await setCopilotPayment(message.payload.legacyId, _.get(message, 'payload.prizeSets'))
400433

401434
if (message.payload.status) {
402-
logger.info(`The status has changed from ${challenge.currentStatus} to ${message.payload.status}`)
435+
// logger.info(`The status has changed from ${challenge.currentStatus} to ${message.payload.status}`)
403436
if (message.payload.status === constants.challengeStatuses.Active && challenge.currentStatus !== constants.challengeStatuses.Active) {
404437
logger.info('Activating challenge...')
405438
await activateChallenge(message.payload.legacyId)
@@ -421,16 +454,7 @@ async function processUpdate (message) {
421454
}
422455
}
423456
}
424-
// we can't switch the challenge type
425-
// TODO: track is missing from the response.
426-
// if (message.payload.legacy.track) {
427-
// const newTrack = message.payload.legacy.track
428-
// // track information is stored in subTrack of V4 API
429-
// if (challenge.track !== newTrack) {
430-
// // refer ContestDirectManager.prepare in ap-challenge-microservice
431-
// throw new Error('You can\'t change challenge track')
432-
// }
433-
// }
457+
await helper.forceV4ESFeeder(message.payload.legacyId)
434458
} catch (e) {
435459
logger.error('processUpdate Catch', e)
436460
throw e

src/services/copilotPaymentService.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const logger = require('../common/logger')
2+
const _ = require('lodash')
3+
const util = require('util')
4+
const helper = require('../common/helper')
5+
6+
const QUERY_GET_COPILOT_PAYMENT = 'SELECT limit 1 * FROM project_info WHERE project_info_type_id = 49 AND project_id = %d'
7+
const QUERY_INSERT_COPILOT_PAYMENT = `
8+
INSERT INTO project_info
9+
(
10+
project_id,
11+
project_info_type_id,
12+
value,
13+
create_user,
14+
create_date,
15+
modify_user,
16+
modify_date
17+
)
18+
VALUES
19+
(?, 49, ?, ?, CURRENT, ?, CURRENT)`
20+
const QUERY_UPDATE_COPILOT_PAYMENT = 'UPDATE project_info SET value = ?, modify_user = ?, modify_date = CURRENT WHERE project_info_type_id = 49 AND project_id = ?'
21+
const QUERY_DELETE_COPILOT_PAYMENT = 'DELETE FROM project_info WHERE project_info_type_id = 49 AND project_id = ?'
22+
23+
/**
24+
* Prepare Informix statement
25+
* @param {Object} connection the Informix connection
26+
* @param {String} sql the sql
27+
* @return {Object} Informix statement
28+
*/
29+
async function prepare (connection, sql) {
30+
// logger.debug(`Preparing SQL ${sql}`)
31+
const stmt = await connection.prepareAsync(sql)
32+
return Promise.promisifyAll(stmt)
33+
}
34+
35+
/**
36+
* Set the copilot payment
37+
* @param {Number} challengeLegacyId the legacy challenge ID
38+
* @param {Number} amount the $ amount of the copilot payment
39+
* @param {String} createdBy the create user handle
40+
* @param {String} updatedBy the update user handle
41+
*/
42+
async function setCopilotPayment (challengeLegacyId, amount, createdBy, updatedBy) {
43+
const connection = await helper.getInformixConnection()
44+
try {
45+
// await connection.beginTransactionAsync()
46+
const copilotPayment = await getCopilotPayment(connection, challengeLegacyId)
47+
if (copilotPayment) {
48+
if (!amount) {
49+
await deleteCopilotPayment(connection, challengeLegacyId)
50+
} else if (_.toString(copilotPayment.value) !== _.toString(amount)) {
51+
await updateCopilotPayment(connection, challengeLegacyId, amount, updatedBy)
52+
}
53+
} else {
54+
await createCopilotPayment(connection, challengeLegacyId, amount, createdBy)
55+
}
56+
} catch (e) {
57+
logger.error(`Error in 'setCopilotPayment' ${e}`)
58+
// await connection.rollbackTransactionAsync()
59+
throw e
60+
} finally {
61+
await connection.closeAsync()
62+
}
63+
}
64+
65+
/**
66+
* Gets the copilot payment for a legacyId
67+
* @param {Object} connection
68+
* @param {Number} challengeLegacyId
69+
*/
70+
async function getCopilotPayment (connection, challengeLegacyId) {
71+
const result = await connection.queryAsync(util.format(QUERY_GET_COPILOT_PAYMENT, challengeLegacyId))
72+
return _.get(result, '[0]', null)
73+
}
74+
75+
/**
76+
* Create the copilot payment record
77+
* @param {Object} connection the connection
78+
* @param {Number} challengeLegacyId the legacy challenge id
79+
* @param {Number} amount the $ amount of the copilot payment
80+
* @param {String} createdBy the create user handle
81+
*/
82+
async function createCopilotPayment (connection, challengeLegacyId, amount, createdBy) {
83+
const query = await prepare(connection, QUERY_INSERT_COPILOT_PAYMENT)
84+
return query.executeAsync([challengeLegacyId, amount, createdBy, createdBy])
85+
}
86+
87+
/**
88+
* Update the existing copilot payment for a legacyId
89+
* @param {Object} connection
90+
* @param {Number} challengeLegacyId
91+
* @param {*} updatedBy the update user handle
92+
*/
93+
async function updateCopilotPayment (connection, challengeLegacyId, newValue, updatedBy) {
94+
const query = await prepare(connection, QUERY_UPDATE_COPILOT_PAYMENT)
95+
return query.executeAsync([newValue, updatedBy, challengeLegacyId])
96+
}
97+
98+
/**
99+
* Delete the existing copilot payment for a legacyId
100+
* @param {Object} connection
101+
* @param {Number} challengeLegacyId
102+
*/
103+
async function deleteCopilotPayment (connection, challengeLegacyId) {
104+
const query = await prepare(connection, QUERY_DELETE_COPILOT_PAYMENT)
105+
return query.executeAsync([challengeLegacyId])
106+
}
107+
108+
module.exports = {
109+
setCopilotPayment
110+
}

0 commit comments

Comments
 (0)