Skip to content

Commit 4472a96

Browse files
Merge pull request #169 from topcoder-platform/issue-168
Validate winners
2 parents 92c055e + 8741e1a commit 4472a96

File tree

4 files changed

+93
-9
lines changed

4 files changed

+93
-9
lines changed

config/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ module.exports = {
5454
// copilot resource role ids allowed to upload attachment
5555
COPILOT_RESOURCE_ROLE_IDS: process.env.COPILOT_RESOURCE_ROLE_IDS
5656
? process.env.COPILOT_RESOURCE_ROLE_IDS.split(',') : ['10ba038e-48da-487b-96e8-8d3b99b6d18b'],
57+
SUBMITTER_ROLE_ID: process.env.SUBMITTER_ROLE_ID || '732339e7-8e30-49d7-9198-cccf9451e221',
5758

5859
// health check timeout in milliseconds
5960
HEALTH_CHECK_TIMEOUT: process.env.HEALTH_CHECK_TIMEOUT || 3000,

docs/test-legacy-processors/Challenge V5 Test processor.postman_collection.json

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
],
8181
"body": {
8282
"mode": "raw",
83-
"raw": "{\n \"typeId\": \"e885273d-aeda-42c0-917d-bfbf979afbba\",\n \"name\": \"Thomas test challenge 11 June\",\n \"projectId\": 16531,\n \"status\": \"Draft\",\n \"terms\": [],\n \"description\": \"This is the description\",\n \"legacy\": {\n \"track\": \"DEVELOP\",\n \"reviewType\": \"COMMUNITY\"\n },\n \"timelineTemplateId\": \"7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c\",\n \"phases\": [\n {\n \"phaseId\": \"a93544bc-c165-4af4-b55e-18f3593b457a\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"6950164f-3c5e-4bdc-abc8-22aaf5a1bd49\",\n \"duration\": 432000\n },\n {\n \"phaseId\": \"aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6\",\n \"duration\": 43200\n },\n {\n \"phaseId\": \"797a6af7-cd3f-4436-9fca-9679f773bee9\",\n \"duration\": 57600\n }\n ],\n \"startDate\": \"2020-06-09T14:00:00.000Z\",\n \"prizeSets\": [\n {\n \"type\": \"placement\",\n \"description\": \"Challenge Prizes\",\n \"prizes\": [\n {\n \"value\": 500,\n \"type\": \"USD\"\n },\n {\n \"value\": 250,\n \"type\": \"USD\"\n }\n ]\n }\n ],\n \"tags\": [\n \"JavaScript\",\n \"Apex\",\n \"Visualforce\",\n \"Force.com\",\n \"Salesforce.com\"\n ]\n}",
83+
"raw": "{\n \"typeId\": \"e885273d-aeda-42c0-917d-bfbf979afbba\",\n \"name\": \"Thomas test challenge 11 June\",\n \"projectId\": 16531,\n \"status\": \"Draft\",\n \"terms\": [],\n \"description\": \"This is the description\",\n \"legacy\": {\n \"track\": \"DEVELOP\",\n \"reviewType\": \"COMMUNITY\"\n },\n \"timelineTemplateId\": \"7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c\",\n \"phases\": [\n {\n \"phaseId\": \"a93544bc-c165-4af4-b55e-18f3593b457a\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"6950164f-3c5e-4bdc-abc8-22aaf5a1bd49\",\n \"duration\": 432000\n },\n {\n \"phaseId\": \"aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6\",\n \"duration\": 43200\n },\n {\n \"phaseId\": \"797a6af7-cd3f-4436-9fca-9679f773bee9\",\n \"duration\": 57600\n }\n ],\n \"startDate\": \"2020-06-12T14:00:00.000Z\",\n \"prizeSets\": [\n {\n \"type\": \"placement\",\n \"description\": \"Challenge Prizes\",\n \"prizes\": [\n {\n \"value\": 500,\n \"type\": \"USD\"\n },\n {\n \"value\": 250,\n \"type\": \"USD\"\n }\n ]\n }\n ],\n \"tags\": [\n \"JavaScript\",\n \"Apex\",\n \"Visualforce\",\n \"Force.com\",\n \"Salesforce.com\"\n ]\n}",
8484
"options": {
8585
"raw": {
8686
"language": "json"
@@ -113,7 +113,7 @@
113113
],
114114
"body": {
115115
"mode": "raw",
116-
"raw": "{\n \"typeId\": \"e885273d-aeda-42c0-917d-bfbf979afbba\",\n \"name\": \"Thomas test challenge 9 June\",\n \"projectId\": 16531,\n \"status\": \"New\",\n \"terms\": [],\n \"description\": \"This is the description\",\n \"legacy\": {\n \"track\": \"DEVELOP\",\n \"reviewType\": \"COMMUNITY\"\n },\n \"timelineTemplateId\": \"7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c\",\n \"phases\": [\n {\n \"phaseId\": \"a93544bc-c165-4af4-b55e-18f3593b457a\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"6950164f-3c5e-4bdc-abc8-22aaf5a1bd49\",\n \"duration\": 432000\n },\n {\n \"phaseId\": \"aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6\",\n \"duration\": 43200\n },\n {\n \"phaseId\": \"797a6af7-cd3f-4436-9fca-9679f773bee9\",\n \"duration\": 57600\n }\n ],\n \"startDate\": \"2020-06-09T14:00:00.000Z\",\n \"prizeSets\": [\n {\n \"type\": \"placement\",\n \"description\": \"Challenge Prizes\",\n \"prizes\": [\n {\n \"value\": 500,\n \"type\": \"USD\"\n },\n {\n \"value\": 250,\n \"type\": \"USD\"\n }\n ]\n }\n ],\n \"tags\": [\n \"JavaScript\",\n \"Apex\",\n \"Visualforce\",\n \"Force.com\",\n \"Salesforce.com\"\n ]\n}",
116+
"raw": "{\n \"typeId\": \"e885273d-aeda-42c0-917d-bfbf979afbba\",\n \"name\": \"Thomas test challenge 9 June\",\n \"projectId\": 16531,\n \"status\": \"New\",\n \"terms\": [],\n \"description\": \"This is the description\",\n \"legacy\": {\n \"track\": \"DEVELOP\",\n \"reviewType\": \"COMMUNITY\"\n },\n \"timelineTemplateId\": \"7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c\",\n \"phases\": [\n {\n \"phaseId\": \"a93544bc-c165-4af4-b55e-18f3593b457a\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"6950164f-3c5e-4bdc-abc8-22aaf5a1bd49\",\n \"duration\": 432000\n },\n {\n \"phaseId\": \"aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6\",\n \"duration\": 43200\n },\n {\n \"phaseId\": \"797a6af7-cd3f-4436-9fca-9679f773bee9\",\n \"duration\": 57600\n }\n ],\n \"startDate\": \"2020-06-12T14:00:00.000Z\",\n \"prizeSets\": [\n {\n \"type\": \"placement\",\n \"description\": \"Challenge Prizes\",\n \"prizes\": [\n {\n \"value\": 500,\n \"type\": \"USD\"\n },\n {\n \"value\": 250,\n \"type\": \"USD\"\n }\n ]\n }\n ],\n \"tags\": [\n \"JavaScript\",\n \"Apex\",\n \"Visualforce\",\n \"Force.com\",\n \"Salesforce.com\"\n ]\n}",
117117
"options": {
118118
"raw": {
119119
"language": "json"
@@ -226,6 +226,76 @@
226226
},
227227
"response": []
228228
},
229+
{
230+
"name": "Open submission/registration phases",
231+
"request": {
232+
"method": "PATCH",
233+
"header": [
234+
{
235+
"key": "Authorization",
236+
"type": "text",
237+
"value": "Bearer {{TOKEN}}"
238+
}
239+
],
240+
"body": {
241+
"mode": "raw",
242+
"raw": "{\n \"phases\": [\n {\n \"phaseId\": \"a93544bc-c165-4af4-b55e-18f3593b457a\",\n \"duration\": 172800,\n \"isOpen\": true\n },\n {\n \"phaseId\": \"6950164f-3c5e-4bdc-abc8-22aaf5a1bd49\",\n \"duration\": 432000,\n \"isOpen\": true\n },\n {\n \"phaseId\": \"aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b\",\n \"duration\": 172800\n },\n {\n \"phaseId\": \"1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6\",\n \"duration\": 43200\n },\n {\n \"phaseId\": \"797a6af7-cd3f-4436-9fca-9679f773bee9\",\n \"duration\": 57600\n }\n ]\n}",
243+
"options": {
244+
"raw": {
245+
"language": "json"
246+
}
247+
}
248+
},
249+
"url": {
250+
"raw": "{{URL}}/challenges/{{CHALLENGE_ID}}",
251+
"host": [
252+
"{{URL}}"
253+
],
254+
"path": [
255+
"challenges",
256+
"{{CHALLENGE_ID}}"
257+
]
258+
},
259+
"description": "The legacy processor should ingore the event if the `challenge.status` is `New`"
260+
},
261+
"response": []
262+
},
263+
{
264+
"name": "Register member",
265+
"request": {
266+
"method": "POST",
267+
"header": [
268+
{
269+
"key": "Authorization",
270+
"value": "Bearer {{TOKEN}}",
271+
"type": "text"
272+
}
273+
],
274+
"body": {
275+
"mode": "raw",
276+
"raw": "{\n \"challengeId\": \"{{CHALLENGE_ID}}\",\n \"memberHandle\": \"TonyJ\",\n \"roleId\": \"{{SUBMITTER_ROLE_ID}}\"\n}",
277+
"options": {
278+
"raw": {
279+
"language": "json"
280+
}
281+
}
282+
},
283+
"url": {
284+
"raw": "http://api.topcoder-dev.com/v5/resources",
285+
"protocol": "http",
286+
"host": [
287+
"api",
288+
"topcoder-dev",
289+
"com"
290+
],
291+
"path": [
292+
"v5",
293+
"resources"
294+
]
295+
}
296+
},
297+
"response": []
298+
},
229299
{
230300
"name": "Update challenge status to Completed and set winner",
231301
"request": {

docs/test-legacy-processors/Challenge V5 Test.postman_environment.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616
"key": "CHALLENGE_ID",
1717
"value": "",
1818
"enabled": true
19+
},
20+
{
21+
"key": "SUBMITTER_ROLE_ID",
22+
"value": "732339e7-8e30-49d7-9198-cccf9451e221",
23+
"enabled": true
1924
}
2025
],
2126
"_postman_variable_scope": "environment",
22-
"_postman_exported_at": "2020-06-11T18:43:49.451Z",
27+
"_postman_exported_at": "2020-06-12T19:13:09.693Z",
2328
"_postman_exported_using": "Postman/7.26.0"
2429
}

src/services/ChallengeService.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ async function populatePhases (phases, startDate, timelineTemplateId) {
371371
const templatePhase = _.find(template.phases, (p) => p.phaseId === phase.phaseId)
372372
const phaseDefinition = _.find(phaseDefinitions, (p) => p.id === phase.phaseId)
373373
phase.name = _.get(phaseDefinition, 'name')
374-
phase.isOpen = false
374+
phase.isOpen = _.get(phase, 'isOpen', false)
375375
if (templatePhase) {
376376
// use default duration if not provided
377377
if (!phase.duration) {
@@ -716,9 +716,15 @@ function isDifferentPrizeSets (prizeSets = [], otherPrizeSets) {
716716
/**
717717
* Validate the winners array.
718718
* @param {Array} winners the Winner Array
719+
* @param {String} winchallengeIdners the challenge ID
719720
*/
720-
function validateWinners (winners) {
721+
async function validateWinners (winners, challengeId) {
722+
const challengeResources = await helper.getChallengeResources(challengeId)
723+
const registrants = _.filter(challengeResources, r => r.roleId === config.SUBMITTER_ROLE_ID)
721724
for (const winner of winners) {
725+
if (!_.find(registrants, r => _.toString(r.memberId) === _.toString(winner.userId))) {
726+
throw new errors.BadRequestError(`Member with userId: ${winner.userId} is not registered on the challenge`)
727+
}
722728
const diffWinners = _.differenceWith(winners, [winner], _.isEqual)
723729
if (diffWinners.length + 1 !== winners.length) {
724730
throw new errors.BadRequestError(`Duplicate member with placement: ${helper.toString(winner)}`)
@@ -857,7 +863,7 @@ async function update (currentUser, challengeId, data, userToken, isFull) {
857863
}
858864

859865
if (data.winners && data.winners.length) {
860-
await validateWinners(data.winners)
866+
await validateWinners(data.winners, challengeId)
861867
}
862868

863869
data.updated = moment().utc()
@@ -1169,7 +1175,7 @@ function sanitizeChallenge (challenge) {
11691175
sanitized.metadata = _.map(challenge.metadata, meta => _.pick(meta, ['name', 'value']))
11701176
}
11711177
if (challenge.phases) {
1172-
sanitized.phases = _.map(challenge.phases, phase => _.pick(phase, ['phaseId', 'duration']))
1178+
sanitized.phases = _.map(challenge.phases, phase => _.pick(phase, ['phaseId', 'duration', 'isOpen']))
11731179
}
11741180
if (challenge.prizeSets) {
11751181
sanitized.prizeSets = _.map(challenge.prizeSets, prizeSet => ({
@@ -1227,7 +1233,8 @@ fullyUpdateChallenge.schema = {
12271233
timelineTemplateId: Joi.string(), // Joi.optionalId(),
12281234
phases: Joi.array().items(Joi.object().keys({
12291235
phaseId: Joi.id(),
1230-
duration: Joi.number().positive()
1236+
duration: Joi.number().positive(),
1237+
isOpen: Joi.boolean()
12311238
}).unknown(true)),
12321239
prizeSets: Joi.array().items(Joi.object().keys({
12331240
type: Joi.string().valid(_.values(constants.prizeSetTypes)).required(),
@@ -1300,7 +1307,8 @@ partiallyUpdateChallenge.schema = {
13001307
timelineTemplateId: Joi.string(), // changing this to update migrated challenges
13011308
phases: Joi.array().items(Joi.object().keys({
13021309
phaseId: Joi.id(),
1303-
duration: Joi.number().positive()
1310+
duration: Joi.number().positive(),
1311+
isOpen: Joi.boolean()
13041312
}).unknown(true)).min(1),
13051313
events: Joi.array().items(Joi.object().keys({
13061314
id: Joi.number().required(),

0 commit comments

Comments
 (0)