Skip to content

Commit a320602

Browse files
Update Challenge API to store challenge winners
1 parent 47b443e commit a320602

9 files changed

+1657
-72
lines changed

docs/swagger.yaml

+42
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,20 @@ definitions:
17641764
type: array
17651765
items:
17661766
type: string
1767+
winners:
1768+
type: array
1769+
items:
1770+
properties:
1771+
userId:
1772+
type: integer
1773+
description: The user id
1774+
handle:
1775+
type: string
1776+
description: the user handle
1777+
placement:
1778+
type: integer
1779+
description: the winner placement
1780+
example: 1
17671781
created:
17681782
type: string
17691783
format: date-time
@@ -1973,6 +1987,20 @@ definitions:
19731987
type: array
19741988
items:
19751989
type: string
1990+
winners:
1991+
type: array
1992+
items:
1993+
properties:
1994+
userId:
1995+
type: integer
1996+
description: The user id
1997+
handle:
1998+
type: string
1999+
description: the user handle
2000+
placement:
2001+
type: integer
2002+
description: the winner placement
2003+
example: 1
19762004
required:
19772005
- typeId
19782006
- track
@@ -2073,6 +2101,20 @@ definitions:
20732101
type: array
20742102
items:
20752103
type: string
2104+
winners:
2105+
type: array
2106+
items:
2107+
properties:
2108+
userId:
2109+
type: integer
2110+
description: The user id
2111+
handle:
2112+
type: string
2113+
description: the user handle
2114+
placement:
2115+
type: integer
2116+
description: the winner placement
2117+
example: 1
20762118
created:
20772119
type: string
20782120
format: date-time

docs/topcoder-challenge-api.postman_collection.json

+741-65
Large diffs are not rendered by default.

package-lock.json

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/helper.js

-1
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,6 @@ function getESClient () {
501501
}
502502

503503
/**
504-
<<<<<<< HEAD
505504
* Ensure project exist
506505
* @param {String} projectId the project id
507506
* @param {String} userToken the user token

src/models/Challenge.js

+5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ const schema = new Schema({
8686
type: Array,
8787
required: false
8888
},
89+
// winners
90+
winners: {
91+
type: [Object],
92+
required: false
93+
},
8994
created: {
9095
type: Date,
9196
required: true

src/services/ChallengeService.js

+72-2
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,31 @@ function isDifferentPrizeSets (prizeSets, otherPrizeSets) {
436436
return false
437437
}
438438

439+
/**
440+
* Validate the winners array.
441+
* @param {Array} winners the Winner Array
442+
*/
443+
function validateWinners (winners) {
444+
for (const winner of winners) {
445+
const diffWinners = _.differenceWith(winners, [winner], _.isEqual)
446+
if (diffWinners.length + 1 !== winners.length) {
447+
throw new errors.BadRequestError(`Duplicate member with placement: ${helper.toString(winner)}`)
448+
}
449+
450+
// find another member with the placement
451+
const placementExists = _.find(diffWinners, function (w) { return w.placement === winner.placement })
452+
if (placementExists && (placementExists.userId !== winner.userId || placementExists.handle !== winner.handle)) {
453+
throw new errors.BadRequestError(`Only one member can have a placement: ${winner.placement}`)
454+
}
455+
456+
// find another placement for a member
457+
const memberExists = _.find(diffWinners, function (w) { return w.userId === winner.userId })
458+
if (memberExists && memberExists.placement !== winner.placement) {
459+
throw new errors.BadRequestError(`The same member ${winner.userId} cannot have multiple placements`)
460+
}
461+
}
462+
}
463+
439464
/**
440465
* Update challenge.
441466
* @param {Object} currentUser the user who perform operation
@@ -480,10 +505,22 @@ async function update (currentUser, challengeId, data, userToken, isFull) {
480505
}
481506

482507
await validateChallengeData(data)
508+
if ((challenge.status === constants.challengeStatuses.Completed || challenge.status === constants.challengeStatuses.Canceled) && data.status && data.status !== challenge.status) {
509+
throw new errors.BadRequestError(`Cannot change ${challenge.status} challenge status to ${data.status} status`)
510+
}
511+
512+
if (data.winners && (challenge.status !== constants.challengeStatuses.Completed && data.status !== constants.challengeStatuses.Completed)) {
513+
throw new errors.BadRequestError(`Cannot set winners for challenge with non-completed ${challenge.status} status`)
514+
}
515+
483516
if (data.phases) {
484517
await helper.validatePhases(data.phases)
485518
}
486519

520+
if (data.winners && data.winners.length) {
521+
await validateWinners(data.winners)
522+
}
523+
487524
data.updated = new Date()
488525
data.updatedBy = currentUser.handle || currentUser.sub
489526
const updateDetails = {}
@@ -526,6 +563,11 @@ async function update (currentUser, challengeId, data, userToken, isFull) {
526563
_.intersection(challenge[key], value).length !== value.length) {
527564
op = '$PUT'
528565
}
566+
} else if (key === 'winners') {
567+
if (_.isUndefined(challenge[key]) || challenge[key].length !== value.length ||
568+
_.intersectionWith(challenge[key], value, _.isEqual).length !== value.length) {
569+
op = '$PUT'
570+
}
529571
} else if (_.isUndefined(challenge[key]) || challenge[key] !== value) {
530572
op = '$PUT'
531573
}
@@ -649,6 +691,24 @@ async function update (currentUser, challengeId, data, userToken, isFull) {
649691
// send null to Elasticsearch to clear the field
650692
data.legacyId = null
651693
}
694+
if (isFull && _.isUndefined(data.winners) && challenge.winners) {
695+
if (!updateDetails['$DELETE']) {
696+
updateDetails['$DELETE'] = {}
697+
}
698+
updateDetails['$DELETE'].winners = null
699+
auditLogs.push({
700+
id: uuid(),
701+
challengeId,
702+
fieldName: 'winners',
703+
oldValue: JSON.stringify(challenge.winners),
704+
newValue: 'NULL',
705+
created: new Date(),
706+
createdBy: currentUser.handle || currentUser.sub
707+
})
708+
delete challenge.winners
709+
// send null to Elasticsearch to clear the field
710+
data.winners = null
711+
}
652712

653713
await models.Challenge.update({ id: challengeId }, updateDetails)
654714
if (auditLogs.length > 0) {
@@ -727,7 +787,12 @@ fullyUpdateChallenge.schema = {
727787
status: Joi.string().valid(_.values(constants.challengeStatuses)).required(),
728788
attachmentIds: Joi.array().items(Joi.optionalId()),
729789
groups: Joi.array().items(Joi.string()), // group names
730-
gitRepoURLs: Joi.array().items(Joi.string().uri())
790+
gitRepoURLs: Joi.array().items(Joi.string().uri()),
791+
winners: Joi.array().items(Joi.object().keys({
792+
userId: Joi.number().integer().positive().required(),
793+
handle: Joi.string().required(),
794+
placement: Joi.number().integer().positive().required()
795+
})).min(1)
731796
}).required(),
732797
userToken: Joi.any()
733798
}
@@ -783,7 +848,12 @@ partiallyUpdateChallenge.schema = {
783848
status: Joi.string().valid(_.values(constants.challengeStatuses)),
784849
attachmentIds: Joi.array().items(Joi.optionalId()),
785850
groups: Joi.array().items(Joi.string()), // group names
786-
gitRepoURLs: Joi.array().items(Joi.string().uri())
851+
gitRepoURLs: Joi.array().items(Joi.string().uri()),
852+
winners: Joi.array().items(Joi.object().keys({
853+
userId: Joi.number().integer().positive().required(),
854+
handle: Joi.string().required(),
855+
placement: Joi.number().integer().positive().required()
856+
})).min(1)
787857
}).required(),
788858
userToken: Joi.any()
789859
}

0 commit comments

Comments
 (0)