diff --git a/config/default.js b/config/default.js index e7d1cf02..f3c88b4b 100644 --- a/config/default.js +++ b/config/default.js @@ -54,6 +54,7 @@ module.exports = { // copilot resource role ids allowed to upload attachment COPILOT_RESOURCE_ROLE_IDS: process.env.COPILOT_RESOURCE_ROLE_IDS ? process.env.COPILOT_RESOURCE_ROLE_IDS.split(',') : ['10ba038e-48da-487b-96e8-8d3b99b6d18b'], + SUBMITTER_ROLE_ID: process.env.SUBMITTER_ROLE_ID || '732339e7-8e30-49d7-9198-cccf9451e221', // health check timeout in milliseconds HEALTH_CHECK_TIMEOUT: process.env.HEALTH_CHECK_TIMEOUT || 3000, diff --git a/docs/test-legacy-processors/Challenge V5 Test processor.postman_collection.json b/docs/test-legacy-processors/Challenge V5 Test processor.postman_collection.json index 5d2d5f35..d5cf9321 100644 --- a/docs/test-legacy-processors/Challenge V5 Test processor.postman_collection.json +++ b/docs/test-legacy-processors/Challenge V5 Test processor.postman_collection.json @@ -80,7 +80,7 @@ ], "body": { "mode": "raw", - "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}", + "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}", "options": { "raw": { "language": "json" @@ -113,7 +113,7 @@ ], "body": { "mode": "raw", - "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}", + "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}", "options": { "raw": { "language": "json" @@ -226,6 +226,76 @@ }, "response": [] }, + { + "name": "Open submission/registration phases", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{TOKEN}}" + } + ], + "body": { + "mode": "raw", + "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}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/challenges/{{CHALLENGE_ID}}", + "host": [ + "{{URL}}" + ], + "path": [ + "challenges", + "{{CHALLENGE_ID}}" + ] + }, + "description": "The legacy processor should ingore the event if the `challenge.status` is `New`" + }, + "response": [] + }, + { + "name": "Register member", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"challengeId\": \"{{CHALLENGE_ID}}\",\n \"memberHandle\": \"TonyJ\",\n \"roleId\": \"{{SUBMITTER_ROLE_ID}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://api.topcoder-dev.com/v5/resources", + "protocol": "http", + "host": [ + "api", + "topcoder-dev", + "com" + ], + "path": [ + "v5", + "resources" + ] + } + }, + "response": [] + }, { "name": "Update challenge status to Completed and set winner", "request": { diff --git a/docs/test-legacy-processors/Challenge V5 Test.postman_environment.json b/docs/test-legacy-processors/Challenge V5 Test.postman_environment.json index ec741f5b..1fe8d44d 100644 --- a/docs/test-legacy-processors/Challenge V5 Test.postman_environment.json +++ b/docs/test-legacy-processors/Challenge V5 Test.postman_environment.json @@ -16,9 +16,14 @@ "key": "CHALLENGE_ID", "value": "", "enabled": true + }, + { + "key": "SUBMITTER_ROLE_ID", + "value": "732339e7-8e30-49d7-9198-cccf9451e221", + "enabled": true } ], "_postman_variable_scope": "environment", - "_postman_exported_at": "2020-06-11T18:43:49.451Z", + "_postman_exported_at": "2020-06-12T19:13:09.693Z", "_postman_exported_using": "Postman/7.26.0" } \ No newline at end of file diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 1fa5448a..fb3c9619 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -371,7 +371,7 @@ async function populatePhases (phases, startDate, timelineTemplateId) { const templatePhase = _.find(template.phases, (p) => p.phaseId === phase.phaseId) const phaseDefinition = _.find(phaseDefinitions, (p) => p.id === phase.phaseId) phase.name = _.get(phaseDefinition, 'name') - phase.isOpen = false + phase.isOpen = _.get(phase, 'isOpen', false) if (templatePhase) { // use default duration if not provided if (!phase.duration) { @@ -716,9 +716,15 @@ function isDifferentPrizeSets (prizeSets = [], otherPrizeSets) { /** * Validate the winners array. * @param {Array} winners the Winner Array + * @param {String} winchallengeIdners the challenge ID */ -function validateWinners (winners) { +async function validateWinners (winners, challengeId) { + const challengeResources = await helper.getChallengeResources(challengeId) + const registrants = _.filter(challengeResources, r => r.roleId === config.SUBMITTER_ROLE_ID) for (const winner of winners) { + if (!_.find(registrants, r => _.toString(r.memberId) === _.toString(winner.userId))) { + throw new errors.BadRequestError(`Member with userId: ${winner.userId} is not registered on the challenge`) + } const diffWinners = _.differenceWith(winners, [winner], _.isEqual) if (diffWinners.length + 1 !== winners.length) { throw new errors.BadRequestError(`Duplicate member with placement: ${helper.toString(winner)}`) @@ -857,7 +863,7 @@ async function update (currentUser, challengeId, data, userToken, isFull) { } if (data.winners && data.winners.length) { - await validateWinners(data.winners) + await validateWinners(data.winners, challengeId) } data.updated = moment().utc() @@ -1169,7 +1175,7 @@ function sanitizeChallenge (challenge) { sanitized.metadata = _.map(challenge.metadata, meta => _.pick(meta, ['name', 'value'])) } if (challenge.phases) { - sanitized.phases = _.map(challenge.phases, phase => _.pick(phase, ['phaseId', 'duration'])) + sanitized.phases = _.map(challenge.phases, phase => _.pick(phase, ['phaseId', 'duration', 'isOpen'])) } if (challenge.prizeSets) { sanitized.prizeSets = _.map(challenge.prizeSets, prizeSet => ({ @@ -1227,7 +1233,8 @@ fullyUpdateChallenge.schema = { timelineTemplateId: Joi.string(), // Joi.optionalId(), phases: Joi.array().items(Joi.object().keys({ phaseId: Joi.id(), - duration: Joi.number().positive() + duration: Joi.number().positive(), + isOpen: Joi.boolean() }).unknown(true)), prizeSets: Joi.array().items(Joi.object().keys({ type: Joi.string().valid(_.values(constants.prizeSetTypes)).required(), @@ -1300,7 +1307,8 @@ partiallyUpdateChallenge.schema = { timelineTemplateId: Joi.string(), // changing this to update migrated challenges phases: Joi.array().items(Joi.object().keys({ phaseId: Joi.id(), - duration: Joi.number().positive() + duration: Joi.number().positive(), + isOpen: Joi.boolean() }).unknown(true)).min(1), events: Joi.array().items(Joi.object().keys({ id: Joi.number().required(),