diff --git a/.circleci/config.yml b/.circleci/config.yml index 990cc8d7..f01686dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,7 +90,7 @@ workflows: branches: only: - dev - - feature/phase-advance + - PLAT-3368 - "build-qa": context: org-global diff --git a/package.json b/package.json index 7c028194..b992d707 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "dependencies": { "@grpc/grpc-js": "^1.8.12", "@opensearch-project/opensearch": "^2.2.0", - "@topcoder-framework/domain-challenge": "^0.18.0", - "@topcoder-framework/lib-common": "^0.18.0", + "@topcoder-framework/domain-challenge": "^0.22.0", + "@topcoder-framework/lib-common": "^0.22.0", "aws-sdk": "^2.1145.0", "axios": "^0.19.0", "axios-retry": "^3.4.0", diff --git a/src/common/challenge-helper.js b/src/common/challenge-helper.js index a7372149..5fc956fc 100644 --- a/src/common/challenge-helper.js +++ b/src/common/challenge-helper.js @@ -99,6 +99,10 @@ class ChallengeHelper { // check groups authorization await helper.ensureAccessibleByGroupsAccess(currentUser, challenge); + + if (challenge.constraints) { + await ChallengeHelper.validateChallengeConstraints(challenge.constraints); + } } async validateChallengeUpdateRequest(currentUser, challenge, data) { @@ -196,6 +200,32 @@ class ChallengeHelper { `Cannot set winners for challenge with non-completed ${challenge.status} status` ); } + + if (data.constraints) { + await ChallengeHelper.validateChallengeConstraints(data.constraints); + } + } + + static async validateChallengeConstraints(constraints) { + if (!_.isEmpty(constraints.allowedRegistrants)) { + await ChallengeHelper.validateAllowedRegistrants(constraints.allowedRegistrants); + } + } + + static async validateAllowedRegistrants(allowedRegistrants) { + const members = await helper.getMembersByHandles(allowedRegistrants); + const incorrectHandles = _.difference( + allowedRegistrants, + _.map(members, (m) => _.lowerCase(m.handle)) + ); + if (incorrectHandles.length > 0) { + throw new errors.BadRequestError( + `Cannot create challenge with invalid handle in constraints. [${_.join( + incorrectHandles, + "," + )}]` + ); + } } sanitizeRepeatedFieldsInUpdateRequest(data) { diff --git a/src/common/helper.js b/src/common/helper.js index 724aebde..67a37435 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -358,7 +358,7 @@ async function capturePayment(paymentId) { const url = `${config.CUSTOMER_PAYMENTS_URL}/${paymentId}/charge`; logger.info(`Calling: ${url} to capture payment`); const res = await axios.patch(url, {}, { headers: { Authorization: `Bearer ${token}` } }); - logger.debug(`Payment API Response: ${JSON.stringify(res.data, null, 2)}`); + logger.debug(`Payment API Response: ${JSON.stringify(res.data)}`); if (res.data.status !== "succeeded") { throw new Error(`Failed to charge payment. Current status: ${res.data.status}`); } @@ -1080,6 +1080,22 @@ async function getMemberByHandle(handle) { return res.data || {}; } +/** + * Get members by handles + * @param {Array} handles the user handle + * @returns {Object} + */ +async function getMembersByHandles(handles) { + const token = await m2mHelper.getM2MToken(); + const res = await axios.get( + `${config.MEMBERS_API_URL}/?fields=handle&handlesLower=["${_.join(handles, '","')}"]`, + { + headers: { Authorization: `Bearer ${token}` }, + } + ); + return res.data; +} + /** * Send self service notification * @param {String} type the notification type @@ -1199,6 +1215,7 @@ module.exports = { cancelPayment, sendSelfServiceNotification, getMemberByHandle, + getMembersByHandles, submitZendeskRequest, updateSelfServiceProjectInfo, getFromInternalCache, diff --git a/src/phase-management/PhaseAdvancer.js b/src/phase-management/PhaseAdvancer.js index 530d6304..f6304e3c 100644 --- a/src/phase-management/PhaseAdvancer.js +++ b/src/phase-management/PhaseAdvancer.js @@ -212,7 +212,7 @@ class PhaseAdvancer { this.#updateSubsequentPhases(phases, phase, -delta); } - console.log(`Updated phases: ${JSON.stringify(phases, null, 2)}`); + console.log(`Updated phases: ${JSON.stringify(phases)}`); } async #close(challengeId, phases, phase) { @@ -229,7 +229,7 @@ class PhaseAdvancer { this.#updateSubsequentPhases(phases, phase, -delta); } - console.log(`Updated phases: ${JSON.stringify(phases, null, 2)}`); + console.log(`Updated phases: ${JSON.stringify(phases)}`); } #insertPhaseIfRequired(phases, phase, facts) { @@ -326,9 +326,7 @@ class PhaseAdvancer { async #hasActiveUnreviewedSubmissions(challengeId, phaseSpecificFacts, phases) { console.log( `Checking if there are active unreviewed submissions for challenge ${challengeId} using phaseSpecificFacts: ${JSON.stringify( - phaseSpecificFacts, - null, - 2 + phaseSpecificFacts )}` ); diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 67fb1372..336156d6 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1165,6 +1165,11 @@ createChallenge.schema = { tags: Joi.array().items(Joi.string()), // tag names projectId: Joi.number().integer().positive(), legacyId: Joi.number().integer().positive(), + constraints: Joi.object() + .keys({ + allowedRegistrants: Joi.array().items(Joi.string().trim().lowercase()).optional(), + }) + .optional(), startDate: Joi.date().iso(), status: Joi.string().valid([ constants.challengeStatuses.Active, @@ -1991,6 +1996,11 @@ updateChallenge.schema = { tags: Joi.array().items(Joi.string().required()).min(1), // tag names projectId: Joi.number().integer().positive(), legacyId: Joi.number().integer().positive(), + constraints: Joi.object() + .keys({ + allowedRegistrants: Joi.array().items(Joi.string().trim().lowercase()).optional(), + }) + .optional(), status: Joi.string().valid(_.values(constants.challengeStatuses)), attachments: Joi.array().items( Joi.object().keys({ @@ -2078,6 +2088,7 @@ function sanitizeChallenge(challenge) { "task", "groups", "cancelReason", + "constraints", ]); if (!_.isUndefined(sanitized.name)) { sanitized.name = xss(sanitized.name); diff --git a/yarn.lock b/yarn.lock index b4b8e39a..35cef421 100644 --- a/yarn.lock +++ b/yarn.lock @@ -255,35 +255,35 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@topcoder-framework/client-relational@^0.18.0": - version "0.18.0" - resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/client-relational/-/client-relational-0.18.0.tgz#20175617c3ac281797d7717a157159076b2578fb" - integrity sha512-JwcKcuT6w/3ydd27+doi9dVWHtuJClXOwajVOfXfHPGFhsgswfoFZ+k4kfc2kC/fioDJ1IPKdFRTeVCxycljnA== +"@topcoder-framework/client-relational@^0.22.0": + version "0.22.0" + resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/client-relational/-/client-relational-0.22.0.tgz#0e096758ffd8c9d0eb986b2f9328ed247930abfe" + integrity sha512-We0sb8pdxOZfzX8WzKxczhXl16jmZ6cN/eBgDv5jR8qpVoXhLTa2iaTLqiRYUWi9ZvHCN6vmNQ607w0IU/iRFQ== dependencies: "@grpc/grpc-js" "^1.8.0" - "@topcoder-framework/lib-common" "^0.18.0" - topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.56-beta-1" + "@topcoder-framework/lib-common" "^0.22.0" + topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1" tslib "^2.4.1" -"@topcoder-framework/domain-challenge@^0.18.0": - version "0.18.0" - resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/domain-challenge/-/domain-challenge-0.18.0.tgz#505d24e14a0354c900c2d55f92335bf9c5aa8d8c" - integrity sha512-+jPNhU+ZqcTjuPBCYc2mLLTUiKVg1WTUZbaySL09iYoEQpnfhpCf/t3Z/5cnC2WuLlaHp5lR7xQP5+Iz/Hl+6g== +"@topcoder-framework/domain-challenge@^0.22.0": + version "0.22.0" + resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/domain-challenge/-/domain-challenge-0.22.0.tgz#bcb7f7a602e424d9932fd0693935aa5f1f2439a4" + integrity sha512-PT2Zts56QKtntSJQxjH8slRjrYISuUGCZdYmyQcy+ak0nQL0COhQ0puqJ6mfIA9Ml3Ggi8Vmk/G9Ti12h1YNDg== dependencies: "@grpc/grpc-js" "^1.8.0" - "@topcoder-framework/client-relational" "^0.18.0" - "@topcoder-framework/lib-common" "^0.18.0" - topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.56-beta-1" + "@topcoder-framework/client-relational" "^0.22.0" + "@topcoder-framework/lib-common" "^0.22.0" + topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1" tslib "^2.4.1" -"@topcoder-framework/lib-common@^0.18.0": - version "0.18.0" - resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/lib-common/-/lib-common-0.18.0.tgz#ad06f5effbebcb67472cd0c51434ae4415fd9ba1" - integrity sha512-961jxIjgQcSlMfhQCM9bi8A4yjQV88hzAhOM/SO5k40WhnsmiazWwb4+P9asoBie86Z2TpYn1cnP0hNt1IQ3DA== +"@topcoder-framework/lib-common@^0.22.0": + version "0.22.0" + resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/lib-common/-/lib-common-0.22.0.tgz#bd3428b0199410a5151326d1d9731c404c255fb5" + integrity sha512-sHdOAyCGcNaDT9esc9Q3sNaqvVAwHPv6NCTlTAt5O9dcSpdz2AyEur8mS5WccFclKhF5ZB9BM1bbWxO8i9WXGQ== dependencies: "@grpc/grpc-js" "^1.8.0" rimraf "^3.0.2" - topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.56-beta-1" + topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1" tslib "^2.4.1" "@types/body-parser@*": @@ -3999,9 +3999,9 @@ topcoder-bus-api-wrapper@topcoder-platform/tc-bus-api-wrapper.git: superagent "^3.8.3" tc-core-library-js appirio-tech/tc-core-library-js.git#v2.6.4 -"topcoder-interface@github:topcoder-platform/plat-interface-definition#v0.0.56-beta-1": +"topcoder-interface@github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1": version "1.0.0" - resolved "https://codeload.github.com/topcoder-platform/plat-interface-definition/tar.gz/7d743db08b113964d5cd3d52644963af7dde5ba4" + resolved "https://codeload.github.com/topcoder-platform/plat-interface-definition/tar.gz/474bcfa1d01f0f2d0a2658de21aa835f4c824c44" topo@3.x.x: version "3.0.3"