Skip to content

fix: challenge update #579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Mar 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c6efe9c
fix: version bump
rakibansary Mar 21, 2023
8f276dc
fix: version bump
rakibansary Mar 21, 2023
9cb8b6d
fix: missing codeartifact env
rakibansary Mar 21, 2023
bb99bf1
fix: dynamically choose es client
rakibansary Mar 21, 2023
d7ef555
fix: missing dependency
rakibansary Mar 21, 2023
d6a3f50
fix: es search
rakibansary Mar 21, 2023
fb2805c
fix: allow searching challenge-timeline-templates by date
rakibansary Mar 21, 2023
08d8038
fix: search using elasticsearc
rakibansary Mar 21, 2023
c4b820c
Merge branch 'refactor/domain-challenge' into refactor/domain-challen…
rakibansary Mar 21, 2023
4e11078
fix: date should be in iso-8601 format
rakibansary Mar 21, 2023
577d90b
fix: phases
rakibansary Mar 22, 2023
5b1bc09
fix: ensure metadata value is a string
rakibansary Mar 22, 2023
c1f8bee
fix: assigning metadata
rakibansary Mar 22, 2023
54f18e1
fix: enable indexing
rakibansary Mar 22, 2023
13aa9ad
feat: new phase logic
eisbilir Mar 22, 2023
d0862f9
fix: missing input
eisbilir Mar 22, 2023
017cfa5
Merge pull request #571 from topcoder-platform/feat/phase-logic-update
rakibansary Mar 23, 2023
9b68f91
fix: merge phase-helper updates
rakibansary Mar 23, 2023
e6d7ab4
fix: add undefined check
eisbilir Mar 23, 2023
a8d0301
fix: add m2m userId
eisbilir Mar 23, 2023
e062c36
refactor: challenge update
rakibansary Mar 23, 2023
07c66a0
Merge branch 'refactor/domain-challenge-dev' into refactor/challenge-…
rakibansary Mar 23, 2023
1fd1ea4
refactor: only update whats necessary in challenge:update
rakibansary Mar 23, 2023
396db36
ci: deploy to dev
rakibansary Mar 23, 2023
704f88d
fix: missing variables
rakibansary Mar 23, 2023
c44d4d4
fix: enable indexing and resource creation
rakibansary Mar 23, 2023
c1945c3
wip
rakibansary Mar 23, 2023
5e4b07f
feat: remove attributes that match exactly with saved challenge
rakibansary Mar 24, 2023
e70465e
refactor: challenge update
rakibansary Mar 24, 2023
8782f0e
fix: allow memberId to be a number
rakibansary Mar 24, 2023
e3d6dcd
fix: getM2MToken reference
eisbilir Mar 24, 2023
fa398ce
Merge pull request #572 from topcoder-platform/fix/getm2mtoken
rakibansary Mar 24, 2023
669c1b1
fix: winner can not be set for non tasks
rakibansary Mar 24, 2023
243b6e8
fix: remove hardcoded debug code
rakibansary Mar 24, 2023
06578d3
fix: unsetting winners
rakibansary Mar 24, 2023
68a7dfd
fix: phase update object
eisbilir Mar 24, 2023
80a4689
fix: allow private description to be empty
eisbilir Mar 24, 2023
99c8645
Merge pull request #573 from topcoder-platform/fix/phase-update
rakibansary Mar 24, 2023
f0f4dd7
fix: phases
eisbilir Mar 24, 2023
8375f2f
Merge pull request #574 from topcoder-platform/fix/phase-update
eisbilir Mar 24, 2023
3104444
chore: handle phase update
rakibansary Mar 24, 2023
b60daa8
validation updates
ThomasKranitsas Mar 25, 2023
28d1646
unset legacy.directProjectId as we do not allowe this to change
ThomasKranitsas Mar 25, 2023
33247ca
fix: bump up versions
rakibansary Mar 25, 2023
8ce4e02
fix: prize amount in cents
rakibansary Mar 26, 2023
1c4dd3a
Merge pull request #576 from topcoder-platform/fix/update-validation
rakibansary Mar 26, 2023
ae4c4e7
Merge pull request #578 from topcoder-platform/refactor/challenge-update
rakibansary Mar 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
DEPLOY_ENV: "DEV"
LOGICAL_ENV: "dev"
APPNAME: "challenge-api"
CODEARTIFACT_ENV: "PROD"
steps: *builddeploy_steps

"build-qa":
Expand Down Expand Up @@ -80,7 +81,8 @@ workflows:
filters:
branches:
only:
- dev
- refactor/domain-challenge-dev
- refactor/challenge-update

- "build-qa":
context: org-global
Expand Down
1 change: 1 addition & 0 deletions app-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ module.exports = (app) => {
next(new errors.ForbiddenError("You are not allowed to perform this action!"));
} else {
req.authUser.handle = config.M2M_AUDIT_HANDLE;
req.authUser.userId = config.M2M_AUDIT_USERID;
req.userToken = req.headers.authorization.split(" ")[1];
next();
}
Expand Down
3 changes: 3 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ module.exports = {
// above AWS_REGION is used if we use AWS ES
HOST: process.env.ES_HOST || "localhost:9200",
API_VERSION: process.env.ES_API_VERSION || "6.8",
OPENSEARCH: process.env.OPENSEARCH || "false",
ES_INDEX: process.env.ES_INDEX || "challenge",
ES_TYPE: process.env.ES_TYPE || "_doc",
ES_REFRESH: process.env.ES_REFRESH || "true",
TEMP_REINDEXING: process.env.TEMP_REINDEXING || true, // if true, it won't delete the existing index when reindexing data
},
Expand Down Expand Up @@ -95,6 +97,7 @@ module.exports = {
DEFAULT_CONFIDENTIALITY_TYPE: process.env.DEFAULT_CONFIDENTIALITY_TYPE || "public",

M2M_AUDIT_HANDLE: process.env.M2M_AUDIT_HANDLE || "tcwebservice",
M2M_AUDIT_USERID: process.env.M2M_AUDIT_USERID || 22838965,

FORUM_TITLE_LENGTH_LIMIT: process.env.FORUM_TITLE_LENGTH_LIMIT || 90,

Expand Down
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,31 @@
"chai-http": "^4.2.1",
"mocha": "^6.1.4",
"mocha-prepare": "^0.1.0",
"nodemon": "^2.0.20",
"nyc": "^14.0.0",
"prettier": "^2.8.1",
"nodemon": "^2.0.20"
"prettier": "^2.8.1"
},
"dependencies": {
"@grpc/grpc-js": "^1.8.12",
"@opensearch-project/opensearch": "^2.2.0",
"@topcoder-framework/domain-challenge": "^0.7.0",
"@topcoder-framework/lib-common": "^0.7.0",
"@topcoder-framework/domain-challenge": "^0.10.13",
"@topcoder-framework/lib-common": "^0.10.13",
"aws-sdk": "^2.1145.0",
"axios": "^0.19.0",
"axios-retry": "^3.4.0",
"bluebird": "^3.5.1",
"body-parser": "^1.15.1",
"config": "^3.0.1",
"cors": "^2.7.1",
"deep-equal": "^2.2.0",
"dotenv": "^8.2.0",
"dynamoose": "^1.11.1",
"elasticsearch": "^16.7.3",
"express": "^4.15.4",
"express-fileupload": "^1.1.6",
"express-interceptor": "^1.2.0",
"get-parameter-names": "^0.3.0",
"http-aws-es": "^6.0.0",
"http-status-codes": "^1.3.0",
"joi": "^14.0.0",
"jsonwebtoken": "^8.3.0",
Expand Down
272 changes: 271 additions & 1 deletion src/common/challenge-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ const HttpStatus = require("http-status-codes");
const _ = require("lodash");
const errors = require("./errors");
const config = require("config");
const helper = require("./helper");
const constants = require("../../app-constants");
const axios = require("axios");
const { getM2MToken } = require("./m2m-helper");
const { hasAdminRole } = require("./role-helper");
const { ensureAcessibilityToModifiedGroups } = require("./group-helper");

class ChallengeHelper {
/**
Expand Down Expand Up @@ -43,7 +46,7 @@ class ChallengeHelper {
* @param {String} projectId the project id
* @param {String} currentUser the user
*/
async ensureProjectExist(projectId, currentUser) {
static async ensureProjectExist(projectId, currentUser) {
let token = await getM2MToken();
const url = `${config.PROJECTS_API_URL}/${projectId}`;
try {
Expand Down Expand Up @@ -75,6 +78,273 @@ class ChallengeHelper {
}
}
}

async validateCreateChallengeRequest(currentUser, challenge) {
// projectId is required for non self-service challenges
if (challenge.legacy.selfService == null && challenge.projectId == null) {
throw new errors.BadRequestError("projectId is required for non self-service challenges.");
}

if (challenge.status === constants.challengeStatuses.Active) {
throw new errors.BadRequestError(
"You cannot create an Active challenge. Please create a Draft challenge and then change the status to Active."
);
}

helper.ensureNoDuplicateOrNullElements(challenge.tags, "tags");
helper.ensureNoDuplicateOrNullElements(challenge.groups, "groups");
// helper.ensureNoDuplicateOrNullElements(challenge.terms, 'terms')
// helper.ensureNoDuplicateOrNullElements(challenge.events, 'events')

// check groups authorization
await helper.ensureAccessibleByGroupsAccess(currentUser, challenge);
}

async validateChallengeUpdateRequest(currentUser, challenge, data) {
if (process.env.LOCAL != "true") {
await helper.ensureUserCanModifyChallenge(currentUser, challenge);
}

helper.ensureNoDuplicateOrNullElements(data.tags, "tags");
helper.ensureNoDuplicateOrNullElements(data.groups, "groups");

if (data.projectId) {
await ChallengeHelper.ensureProjectExist(data.projectId, currentUser);
}

// check groups access to be updated group values
if (data.groups) {
await ensureAcessibilityToModifiedGroups(currentUser, data, challenge);
}

// Ensure descriptionFormat is either 'markdown' or 'html'
if (data.descriptionFormat && !_.includes(["markdown", "html"], data.descriptionFormat)) {
throw new errors.BadRequestError("The property 'descriptionFormat' must be either 'markdown' or 'html'");
}

// Ensure unchangeable fields are not changed
if (
_.get(challenge, "legacy.track") &&
_.get(data, "legacy.track") &&
_.get(challenge, "legacy.track") !== _.get(data, "legacy.track")
) {
throw new errors.ForbiddenError("Cannot change legacy.track");
}

if (
_.get(challenge, "trackId") &&
_.get(data, "trackId") &&
_.get(challenge, "trackId") !== _.get(data, "trackId")
) {
throw new errors.ForbiddenError("Cannot change trackId");
}

if (
_.get(challenge, "typeId") &&
_.get(data, "typeId") &&
_.get(challenge, "typeId") !== _.get(data, "typeId")
) {
throw new errors.ForbiddenError("Cannot change typeId");
}

if (
_.get(challenge, "legacy.pureV5Task") &&
_.get(data, "legacy.pureV5Task") &&
_.get(challenge, "legacy.pureV5Task") !== _.get(data, "legacy.pureV5Task")
) {
throw new errors.ForbiddenError("Cannot change legacy.pureV5Task");
}

if (
_.get(challenge, "legacy.pureV5") &&
_.get(data, "legacy.pureV5") &&
_.get(challenge, "legacy.pureV5") !== _.get(data, "legacy.pureV5")
) {
throw new errors.ForbiddenError("Cannot change legacy.pureV5");
}

if (
_.get(challenge, "legacy.selfService") &&
_.get(data, "legacy.selfService") &&
_.get(challenge, "legacy.selfService") !== _.get(data, "legacy.selfService")
) {
throw new errors.ForbiddenError("Cannot change legacy.selfService");
}

if (
(challenge.status === constants.challengeStatuses.Completed ||
challenge.status === constants.challengeStatuses.Cancelled) &&
data.status &&
data.status !== challenge.status &&
data.status !== constants.challengeStatuses.CancelledClientRequest
) {
throw new errors.BadRequestError(
`Cannot change ${challenge.status} challenge status to ${data.status} status`
);
}

if (
data.winners &&
data.winners.length > 0 &&
challenge.status !== constants.challengeStatuses.Completed &&
data.status !== constants.challengeStatuses.Completed
) {
throw new errors.BadRequestError(
`Cannot set winners for challenge with non-completed ${challenge.status} status`
);
}
}

sanitizeRepeatedFieldsInUpdateRequest(data) {
if (data.winners != null) {
data.winnerUpdate = {
winners: data.winners,
};
delete data.winners;
}

if (data.discussions != null) {
data.discussionUpdate = {
discussions: data.discussions,
};
delete data.discussions;
}

if (data.metadata != null) {
data.metadataUpdate = {
metadata: data.metadata,
};
delete data.metadata;
}

if (data.phases != null) {
data.phaseUpdate = {
phases: data.phases,
};
delete data.phases;
}

if (data.events != null) {
data.eventUpdate = {
events: data.events,
};
delete data.events;
}

if (data.terms != null) {
data.termUpdate = {
terms: data.terms,
};
delete data.terms;
}

if (data.prizeSets != null) {
data.prizeSetUpdate = {
prizeSets: data.prizeSets,
};
delete data.prizeSets;
}

if (data.tags != null) {
data.tagUpdate = {
tags: data.tags,
};
delete data.tags;
}

if (data.attachments != null) {
data.attachmentUpdate = {
attachments: data.attachments,
};
delete data.attachments;
}

if (data.groups != null) {
data.groupUpdate = {
groups: data.groups,
};
delete data.groups;
}

return data;
}

enrichChallengeForResponse(challenge, track, type) {
if (challenge.phases && challenge.phases.length > 0) {
const registrationPhase = _.find(challenge.phases, (p) => p.name === "Registration");
const submissionPhase = _.find(challenge.phases, (p) => p.name === "Submission");

challenge.currentPhase = challenge.phases
.slice()
.reverse()
.find((phase) => phase.isOpen);

challenge.currentPhaseNames = _.map(
_.filter(challenge.phases, (p) => p.isOpen === true),
"name"
);

if (registrationPhase) {
challenge.registrationStartDate =
registrationPhase.actualStartDate || registrationPhase.scheduledStartDate;
challenge.registrationEndDate =
registrationPhase.actualEndDate || registrationPhase.scheduledEndDate;
}
if (submissionPhase) {
challenge.submissionStartDate =
submissionPhase.actualStartDate || submissionPhase.scheduledStartDate;

challenge.submissionEndDate =
submissionPhase.actualEndDate || submissionPhase.scheduledEndDate;
}
}

challenge.created = new Date(challenge.created).toISOString();
challenge.updated = new Date(challenge.updated).toISOString();
challenge.startDate = new Date(challenge.startDate).toISOString();
challenge.endDate = new Date(challenge.endDate).toISOString();

if (track) {
challenge.track = track.name;
}

if (type) {
challenge.type = type.name;
}

challenge.metadata = challenge.metadata.map((m) => {
try {
m.value = JSON.stringify(JSON.parse(m.value)); // when we update how we index data, make this a JSON field
} catch (err) {
// do nothing
}
return m;
});
}

convertPrizeSetValuesToCents(prizeSets) {
prizeSets.forEach((prizeSet) => {
prizeSet.prizes.forEach((prize) => {
prize.amountInCents = prize.value * 100;
delete prize.value;
});
});
}

convertPrizeSetValuesToDollars(prizeSets, overview) {
prizeSets.forEach((prizeSet) => {
prizeSet.prizes.forEach((prize) => {
if (prize.amountInCents != null) {
prize.value = prize.amountInCents / 100;
delete prize.amountInCents;
}
});
});
if (overview && overview.totalPrizesInCents) {
overview.totalPrizes = overview.totalPrizesInCents / 100;
delete overview.totalPrizesInCents;
}
}
}

module.exports = new ChallengeHelper();
Loading