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

Commit 3c2f6dc

Browse files
authored
Merge pull request #27 from topcoder-platform/refactor-proc
Update timelines and member payments directly in ifx
2 parents 719a4de + 2880010 commit 3c2f6dc

File tree

5 files changed

+401
-5
lines changed

5 files changed

+401
-5
lines changed

src/common/idGenerator.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* The ID generator service
3+
*/
4+
const util = require('util')
5+
const Mutex = require('async-mutex').Mutex
6+
const logger = require('./logger')
7+
const helper = require('./helper')
8+
9+
/**
10+
* Main class of IDGenerator
11+
*/
12+
class IDGenerator {
13+
/**
14+
* Constructor
15+
* @param {Informix} db database
16+
* @param {String} seqName sequence name
17+
*/
18+
constructor (seqName) {
19+
this.seqName = seqName
20+
this._availableId = 0
21+
this.mutex = new Mutex()
22+
}
23+
24+
/**
25+
* Get next id
26+
* @returns {Number} next id
27+
*/
28+
async getNextId () {
29+
const release = await this.mutex.acquire()
30+
try {
31+
logger.debug('Getting nextId')
32+
--this._availableId
33+
logger.debug(`this._availableId = ${this._availableId}`)
34+
35+
if (this._availableId <= 0) {
36+
const connection = await helper.getInformixConnection()
37+
try {
38+
// begin transaction
39+
await connection.beginTransactionAsync()
40+
41+
const [nextId, availableId] = await this.getNextBlock(connection)
42+
await this.updateNextBlock(connection, nextId + availableId + 1)
43+
44+
// commit the transaction
45+
await connection.commitTransactionAsync()
46+
47+
// Only set to this's properties after successful commit
48+
this._nextId = nextId
49+
this._availableId = availableId
50+
} catch (e) {
51+
await connection.rollbackTransactionAsync()
52+
throw e
53+
} finally {
54+
await connection.closeAsync()
55+
}
56+
}
57+
58+
logger.debug(`this._availableId = ${this._availableId}`)
59+
return ++this._nextId
60+
} finally {
61+
release()
62+
}
63+
}
64+
65+
/**
66+
* Fetch next block from id_sequence
67+
* @param {Object} connection the Informix connection
68+
* @returns {Array} [nextId, availableId]
69+
* @private
70+
*/
71+
async getNextBlock (connection) {
72+
try {
73+
const result = await connection.queryAsync(`select next_block_start, block_size from id_sequences where name = '${this.seqName}'`)
74+
if (result.length > 0) {
75+
return [Number(result[0].next_block_start) - 1, Number(result[0].block_size)]
76+
} else {
77+
throw new Error(`null or empty result for ${this.seqName}`)
78+
}
79+
} catch (e) {
80+
logger.error(util.inspect(e))
81+
throw e
82+
}
83+
}
84+
85+
/**
86+
* Update id_sequence
87+
* @param {Object} connection the Informix connection
88+
* @param {Number} nextStart next start id
89+
* @private
90+
*/
91+
async updateNextBlock (connection, nextStart) {
92+
try {
93+
await connection.queryAsync(`update id_sequences set next_block_start = ${nextStart} where name = '${this.seqName}'`)
94+
} catch (e) {
95+
logger.error('Failed to update id sequence: ' + this.seqName)
96+
logger.error(util.inspect(e))
97+
throw e
98+
}
99+
}
100+
}
101+
102+
module.exports = IDGenerator

src/constants.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ const challengeStatuses = {
4545
CancelledZeroRegistrations: 'Cancelled - Zero Registrations'
4646
}
4747

48+
const PhaseStatusTypes = {
49+
Open: 2,
50+
Closed: 3
51+
}
52+
53+
const prizeTypesIds = {
54+
Contest: 15,
55+
Checkpoint: 14
56+
}
57+
4858
const supportedMetadata = {
4959
allowStockArt: 52,
5060
drPoints: 30,
@@ -60,5 +70,7 @@ module.exports = {
6070
EVENT_MIME_TYPE,
6171
createChallengeStatusesMap,
6272
challengeStatuses,
73+
PhaseStatusTypes,
74+
prizeTypesIds,
6375
supportedMetadata
6476
}

src/services/ProcessorService.js

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,81 @@ const termsService = require('./termsService')
1515
const copilotPaymentService = require('./copilotPaymentService')
1616
const timelineService = require('./timelineService')
1717
const metadataService = require('./metadataService')
18+
const paymentService = require('./paymentService')
19+
20+
/**
21+
* Sync the information from the v5 phases into legacy
22+
* @param {Number} legacyId the legacy challenge ID
23+
* @param {Array} v4Phases the v4 phases
24+
* @param {Array} v5Phases the v5 phases
25+
*/
26+
async function syncChallengePhases (legacyId, v5Phases) {
27+
const phaseTypes = await timelineService.getPhaseTypes()
28+
const phasesFromIFx = await timelineService.getChallengePhases(legacyId)
29+
for (const phase of phasesFromIFx) {
30+
const phaseName = _.get(_.find(phaseTypes, pt => pt.phase_type_id === phase.phase_type_id), 'name')
31+
const v5Equivalent = _.find(v5Phases, p => p.name === phaseName)
32+
if (v5Equivalent) {
33+
// Compare duration and status
34+
if (v5Equivalent.duration * 1000 !== phase.duration ||
35+
(v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Closed) ||
36+
(!v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Open)) {
37+
// update phase
38+
await timelineService.updatePhase(
39+
phase.project_phase_id,
40+
legacyId,
41+
v5Equivalent.scheduledStartDate,
42+
v5Equivalent.scheduledEndDate,
43+
v5Equivalent.duration * 1000,
44+
v5Equivalent.isOpen ? constants.PhaseStatusTypes.Open : constants.PhaseStatusTypes.Closed)
45+
}
46+
}
47+
}
48+
// TODO: What about iterative reviews? There can be many for the same challenge.
49+
// TODO: handle timeline template updates
50+
}
51+
52+
/**
53+
* Update the payments from v5 prize sets into legacy
54+
* @param {Number} legacyId the legacy challenge ID
55+
* @param {Array} v5PrizeSets the v5 prize sets
56+
* @param {String} createdBy the created by
57+
*/
58+
async function updateMemberPayments (legacyId, v5PrizeSets, createdBy) {
59+
const prizesFromIfx = await paymentService.getChallengePrizes(legacyId, constants.prizeTypesIds.Contest)
60+
const [checkpointPrizesFromIfx] = await paymentService.getChallengePrizes(legacyId, constants.prizeTypesIds.Checkpoint)
61+
const v5Prizes = _.map(_.get(_.find(v5PrizeSets, p => p.type === constants.prizeSetTypes.ChallengePrizes), 'prizes', []), prize => prize.value)
62+
const v5CheckPointPrizes = _.map(_.get(_.find(v5PrizeSets, p => p.type === constants.prizeSetTypes.CheckPoint), 'prizes', []), prize => prize.value)
63+
// compare prizes
64+
if (v5Prizes && v5Prizes.length > 0) {
65+
v5Prizes.sort((a, b) => b - a)
66+
for (let i = 0; i < v5Prizes.length; i += 1) {
67+
const ifxPrize = _.find(prizesFromIfx, p => p.place === i + 1)
68+
if (ifxPrize) {
69+
if (_.toInteger(ifxPrize.prize_amount) !== v5Prizes[i]) {
70+
await paymentService.updatePrize(ifxPrize.prize_id, legacyId, v5Prizes[i], 1)
71+
}
72+
} else {
73+
await paymentService.createPrize(legacyId, i + 1, v5Prizes[i], constants.prizeTypesIds.Contest, 1, createdBy)
74+
}
75+
}
76+
if (prizesFromIfx.length > v5Prizes.length) {
77+
const prizesToDelete = _.filter(prizesFromIfx, p => p.place > v5Prizes.length)
78+
for (const prizeToDelete of prizesToDelete) {
79+
await paymentService.deletePrize(legacyId, prizeToDelete.prize_id)
80+
}
81+
}
82+
}
83+
// compare checkpoint prizes
84+
if (v5CheckPointPrizes && v5CheckPointPrizes.length > 0) {
85+
// we assume that all checkpoint prizes will be the same
86+
if (v5CheckPointPrizes.length !== checkpointPrizesFromIfx.number_of_submissions || v5CheckPointPrizes[0] !== _.toInteger(checkpointPrizesFromIfx.prize_amount)) {
87+
await paymentService.updatePrize(checkpointPrizesFromIfx.prize_id, legacyId, v5CheckPointPrizes[0], v5CheckPointPrizes.length)
88+
}
89+
} else if (checkpointPrizesFromIfx) {
90+
await paymentService.deletePrize(legacyId, checkpointPrizesFromIfx.prize_id)
91+
}
92+
}
1893

1994
/**
2095
* Get group information by V5 UUID
@@ -544,9 +619,6 @@ async function processUpdate (message) {
544619
logger.warn('Failed to update the challenge via the V4 API')
545620
logger.error(e)
546621
}
547-
await associateChallengeGroups(saveDraftContestDTO.groupsToBeAdded, saveDraftContestDTO.groupsToBeDeleted, legacyId)
548-
await associateChallengeTerms(message.payload.terms, legacyId, _.get(message, 'payload.createdBy'), _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy'))
549-
await setCopilotPayment(message.payload.id, legacyId, _.get(message, 'payload.prizeSets'), _.get(message, 'payload.createdBy'), _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy'), m2mToken)
550622

551623
// Update metadata in IFX
552624
if (message.payload.metadata && message.payload.metadata.length > 0) {
@@ -595,6 +667,14 @@ async function processUpdate (message) {
595667
}
596668
}
597669
}
670+
671+
// Direct IFX modifications
672+
await syncChallengePhases(message.payload.legacyId, message.payload.phases)
673+
await updateMemberPayments(message.payload.legacyId, message.payload.prizeSets, _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy'))
674+
await associateChallengeGroups(saveDraftContestDTO.groupsToBeAdded, saveDraftContestDTO.groupsToBeDeleted, legacyId)
675+
await associateChallengeTerms(message.payload.terms, legacyId, _.get(message, 'payload.createdBy'), _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy'))
676+
await setCopilotPayment(message.payload.id, legacyId, _.get(message, 'payload.prizeSets'), _.get(message, 'payload.createdBy'), _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy'), m2mToken)
677+
598678
try {
599679
await helper.forceV4ESFeeder(legacyId)
600680
} catch (e) {

src/services/paymentService.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* Payment Service
3+
* Interacts with InformixDB
4+
*/
5+
const logger = require('../common/logger')
6+
const util = require('util')
7+
const helper = require('../common/helper')
8+
const IDGenerator = require('../common/idGenerator')
9+
10+
const prizeIdGen = new IDGenerator('prize_id_seq')
11+
12+
const QUERY_GET_CHALLENGE_PRIZES = 'SELECT prize_id, place, prize_amount, number_of_submissions FROM prize WHERE prize_type_id = %d and project_id = %d'
13+
const QUERY_UPDATE_CHALLENGE_PRIZE = 'UPDATE prize SET prize_amount = ?, number_of_submissions = ? WHERE prize_id = %d and project_id = %d'
14+
const QUERY_CREATE_CHALLENGE_PRIZE = 'INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
15+
const QUERY_DELETE_CHALLENGE_PRIZE = 'DELETE FROM prize WHERE prize_id = %d and project_id = %d'
16+
17+
/**
18+
* Prepare Informix statement
19+
* @param {Object} connection the Informix connection
20+
* @param {String} sql the sql
21+
* @return {Object} Informix statement
22+
*/
23+
async function prepare (connection, sql) {
24+
// logger.debug(`Preparing SQL ${sql}`)
25+
const stmt = await connection.prepareAsync(sql)
26+
return Promise.promisifyAll(stmt)
27+
}
28+
29+
/**
30+
* Gets the challenge phases from ifx
31+
* @param {Number} challengeLegacyId the legacy challenge ID
32+
* @param {Number} prizeTypeId the legacy challenge ID
33+
*/
34+
async function getChallengePrizes (challengeLegacyId, prizeTypeId) {
35+
const connection = await helper.getInformixConnection()
36+
let result = null
37+
try {
38+
result = await connection.queryAsync(util.format(QUERY_GET_CHALLENGE_PRIZES, prizeTypeId, challengeLegacyId))
39+
} catch (e) {
40+
logger.error(`Error in 'getChallengePrizes' ${e}`)
41+
throw e
42+
} finally {
43+
await connection.closeAsync()
44+
}
45+
return result
46+
}
47+
48+
/**
49+
* Update a prize in IFX
50+
* @param {Number} phaseId the phase ID
51+
* @param {Number} challengeLegacyId the legacy challenge ID
52+
* @param {Number} prizeAmount the prize amount
53+
* @param {Number} numberOfSubmissions the number of submissions to receive the prize
54+
*/
55+
async function updatePrize (prizeId, challengeLegacyId, prizeAmount, numberOfSubmissions) {
56+
const connection = await helper.getInformixConnection()
57+
let result = null
58+
try {
59+
// await connection.beginTransactionAsync()
60+
const query = await prepare(connection, util.format(QUERY_UPDATE_CHALLENGE_PRIZE, prizeId, challengeLegacyId))
61+
result = await query.executeAsync([prizeAmount, numberOfSubmissions])
62+
// await connection.commitTransactionAsync()
63+
} catch (e) {
64+
logger.error(`Error in 'updatePrize' ${e}, rolling back transaction`)
65+
await connection.rollbackTransactionAsync()
66+
throw e
67+
} finally {
68+
logger.info(`Prize ${prizeId} has been updated`)
69+
await connection.closeAsync()
70+
}
71+
return result
72+
}
73+
74+
/**
75+
* Creates a new prize in IFX
76+
* @param {Number} challengeLegacyId the legacy challenge ID
77+
* @param {Number} place the placement
78+
* @param {Number} prizeAmount the prize amount
79+
* @param {Number} prizeTypeId the prize type ID
80+
* @param {Number} numberOfSubmissions the number of submissions that will receive the prize
81+
* @param {String} createdBy the creator user
82+
*/
83+
async function createPrize (challengeLegacyId, place, prizeAmount, prizeTypeId, numberOfSubmissions, createdBy) {
84+
const connection = await helper.getInformixConnection()
85+
let result = null
86+
let prizeId
87+
try {
88+
// await connection.beginTransactionAsync()
89+
prizeId = await prizeIdGen.getNextId()
90+
const currentDateIso = new Date().toISOString().replace('T', ' ').replace('Z', '').split('.')[0]
91+
const query = await prepare(connection, QUERY_CREATE_CHALLENGE_PRIZE)
92+
result = await query.executeAsync([prizeId, challengeLegacyId, place, prizeAmount, prizeTypeId, numberOfSubmissions, createdBy, currentDateIso, createdBy, currentDateIso])
93+
// await connection.commitTransactionAsync()
94+
} catch (e) {
95+
logger.error(`Error in 'createPrize' ${e}, rolling back transaction`)
96+
await connection.rollbackTransactionAsync()
97+
throw e
98+
} finally {
99+
logger.info(`Prize ${prizeId} has been updated`)
100+
await connection.closeAsync()
101+
}
102+
return result
103+
}
104+
105+
/**
106+
* Deletes a prize from IFX
107+
* @param {Number} challengeLegacyId the legacy challenge ID
108+
* @param {Number} prizeId the prize ID
109+
*/
110+
async function deletePrize (challengeLegacyId, prizeId) {
111+
const connection = await helper.getInformixConnection()
112+
let result = null
113+
try {
114+
// await connection.beginTransactionAsync()
115+
const query = await prepare(connection, QUERY_DELETE_CHALLENGE_PRIZE)
116+
result = await query.executeAsync([prizeId, challengeLegacyId])
117+
// await connection.commitTransactionAsync()
118+
} catch (e) {
119+
logger.error(`Error in 'deletePrize' ${e}, rolling back transaction`)
120+
await connection.rollbackTransactionAsync()
121+
throw e
122+
} finally {
123+
logger.info(`Prize ${prizeId} has been deleted`)
124+
await connection.closeAsync()
125+
}
126+
return result
127+
}
128+
129+
module.exports = {
130+
getChallengePrizes,
131+
updatePrize,
132+
createPrize,
133+
deletePrize
134+
}

0 commit comments

Comments
 (0)