Skip to content

Commit ae4c4e7

Browse files
authored
Merge pull request #578 from topcoder-platform/refactor/challenge-update
fix: challenge update
2 parents a8d0301 + 1c4dd3a commit ae4c4e7

File tree

12 files changed

+871
-1119
lines changed

12 files changed

+871
-1119
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ workflows:
8282
branches:
8383
only:
8484
- refactor/domain-challenge-dev
85+
- refactor/challenge-update
8586

8687
- "build-qa":
8788
context: org-global

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,16 @@
4242
"dependencies": {
4343
"@grpc/grpc-js": "^1.8.12",
4444
"@opensearch-project/opensearch": "^2.2.0",
45-
"@topcoder-framework/domain-challenge": "^0.7.3",
46-
"@topcoder-framework/lib-common": "^0.7.3",
45+
"@topcoder-framework/domain-challenge": "^0.10.13",
46+
"@topcoder-framework/lib-common": "^0.10.13",
4747
"aws-sdk": "^2.1145.0",
4848
"axios": "^0.19.0",
4949
"axios-retry": "^3.4.0",
5050
"bluebird": "^3.5.1",
5151
"body-parser": "^1.15.1",
5252
"config": "^3.0.1",
5353
"cors": "^2.7.1",
54+
"deep-equal": "^2.2.0",
5455
"dotenv": "^8.2.0",
5556
"dynamoose": "^1.11.1",
5657
"elasticsearch": "^16.7.3",

src/common/challenge-helper.js

Lines changed: 248 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const constants = require("../../app-constants");
1010
const axios = require("axios");
1111
const { getM2MToken } = require("./m2m-helper");
1212
const { hasAdminRole } = require("./role-helper");
13+
const { ensureAcessibilityToModifiedGroups } = require("./group-helper");
1314

1415
class ChallengeHelper {
1516
/**
@@ -45,7 +46,7 @@ class ChallengeHelper {
4546
* @param {String} projectId the project id
4647
* @param {String} currentUser the user
4748
*/
48-
async ensureProjectExist(projectId, currentUser) {
49+
static async ensureProjectExist(projectId, currentUser) {
4950
let token = await getM2MToken();
5051
const url = `${config.PROJECTS_API_URL}/${projectId}`;
5152
try {
@@ -98,6 +99,252 @@ class ChallengeHelper {
9899
// check groups authorization
99100
await helper.ensureAccessibleByGroupsAccess(currentUser, challenge);
100101
}
102+
103+
async validateChallengeUpdateRequest(currentUser, challenge, data) {
104+
if (process.env.LOCAL != "true") {
105+
await helper.ensureUserCanModifyChallenge(currentUser, challenge);
106+
}
107+
108+
helper.ensureNoDuplicateOrNullElements(data.tags, "tags");
109+
helper.ensureNoDuplicateOrNullElements(data.groups, "groups");
110+
111+
if (data.projectId) {
112+
await ChallengeHelper.ensureProjectExist(data.projectId, currentUser);
113+
}
114+
115+
// check groups access to be updated group values
116+
if (data.groups) {
117+
await ensureAcessibilityToModifiedGroups(currentUser, data, challenge);
118+
}
119+
120+
// Ensure descriptionFormat is either 'markdown' or 'html'
121+
if (data.descriptionFormat && !_.includes(["markdown", "html"], data.descriptionFormat)) {
122+
throw new errors.BadRequestError("The property 'descriptionFormat' must be either 'markdown' or 'html'");
123+
}
124+
125+
// Ensure unchangeable fields are not changed
126+
if (
127+
_.get(challenge, "legacy.track") &&
128+
_.get(data, "legacy.track") &&
129+
_.get(challenge, "legacy.track") !== _.get(data, "legacy.track")
130+
) {
131+
throw new errors.ForbiddenError("Cannot change legacy.track");
132+
}
133+
134+
if (
135+
_.get(challenge, "trackId") &&
136+
_.get(data, "trackId") &&
137+
_.get(challenge, "trackId") !== _.get(data, "trackId")
138+
) {
139+
throw new errors.ForbiddenError("Cannot change trackId");
140+
}
141+
142+
if (
143+
_.get(challenge, "typeId") &&
144+
_.get(data, "typeId") &&
145+
_.get(challenge, "typeId") !== _.get(data, "typeId")
146+
) {
147+
throw new errors.ForbiddenError("Cannot change typeId");
148+
}
149+
150+
if (
151+
_.get(challenge, "legacy.pureV5Task") &&
152+
_.get(data, "legacy.pureV5Task") &&
153+
_.get(challenge, "legacy.pureV5Task") !== _.get(data, "legacy.pureV5Task")
154+
) {
155+
throw new errors.ForbiddenError("Cannot change legacy.pureV5Task");
156+
}
157+
158+
if (
159+
_.get(challenge, "legacy.pureV5") &&
160+
_.get(data, "legacy.pureV5") &&
161+
_.get(challenge, "legacy.pureV5") !== _.get(data, "legacy.pureV5")
162+
) {
163+
throw new errors.ForbiddenError("Cannot change legacy.pureV5");
164+
}
165+
166+
if (
167+
_.get(challenge, "legacy.selfService") &&
168+
_.get(data, "legacy.selfService") &&
169+
_.get(challenge, "legacy.selfService") !== _.get(data, "legacy.selfService")
170+
) {
171+
throw new errors.ForbiddenError("Cannot change legacy.selfService");
172+
}
173+
174+
if (
175+
(challenge.status === constants.challengeStatuses.Completed ||
176+
challenge.status === constants.challengeStatuses.Cancelled) &&
177+
data.status &&
178+
data.status !== challenge.status &&
179+
data.status !== constants.challengeStatuses.CancelledClientRequest
180+
) {
181+
throw new errors.BadRequestError(
182+
`Cannot change ${challenge.status} challenge status to ${data.status} status`
183+
);
184+
}
185+
186+
if (
187+
data.winners &&
188+
data.winners.length > 0 &&
189+
challenge.status !== constants.challengeStatuses.Completed &&
190+
data.status !== constants.challengeStatuses.Completed
191+
) {
192+
throw new errors.BadRequestError(
193+
`Cannot set winners for challenge with non-completed ${challenge.status} status`
194+
);
195+
}
196+
}
197+
198+
sanitizeRepeatedFieldsInUpdateRequest(data) {
199+
if (data.winners != null) {
200+
data.winnerUpdate = {
201+
winners: data.winners,
202+
};
203+
delete data.winners;
204+
}
205+
206+
if (data.discussions != null) {
207+
data.discussionUpdate = {
208+
discussions: data.discussions,
209+
};
210+
delete data.discussions;
211+
}
212+
213+
if (data.metadata != null) {
214+
data.metadataUpdate = {
215+
metadata: data.metadata,
216+
};
217+
delete data.metadata;
218+
}
219+
220+
if (data.phases != null) {
221+
data.phaseUpdate = {
222+
phases: data.phases,
223+
};
224+
delete data.phases;
225+
}
226+
227+
if (data.events != null) {
228+
data.eventUpdate = {
229+
events: data.events,
230+
};
231+
delete data.events;
232+
}
233+
234+
if (data.terms != null) {
235+
data.termUpdate = {
236+
terms: data.terms,
237+
};
238+
delete data.terms;
239+
}
240+
241+
if (data.prizeSets != null) {
242+
data.prizeSetUpdate = {
243+
prizeSets: data.prizeSets,
244+
};
245+
delete data.prizeSets;
246+
}
247+
248+
if (data.tags != null) {
249+
data.tagUpdate = {
250+
tags: data.tags,
251+
};
252+
delete data.tags;
253+
}
254+
255+
if (data.attachments != null) {
256+
data.attachmentUpdate = {
257+
attachments: data.attachments,
258+
};
259+
delete data.attachments;
260+
}
261+
262+
if (data.groups != null) {
263+
data.groupUpdate = {
264+
groups: data.groups,
265+
};
266+
delete data.groups;
267+
}
268+
269+
return data;
270+
}
271+
272+
enrichChallengeForResponse(challenge, track, type) {
273+
if (challenge.phases && challenge.phases.length > 0) {
274+
const registrationPhase = _.find(challenge.phases, (p) => p.name === "Registration");
275+
const submissionPhase = _.find(challenge.phases, (p) => p.name === "Submission");
276+
277+
challenge.currentPhase = challenge.phases
278+
.slice()
279+
.reverse()
280+
.find((phase) => phase.isOpen);
281+
282+
challenge.currentPhaseNames = _.map(
283+
_.filter(challenge.phases, (p) => p.isOpen === true),
284+
"name"
285+
);
286+
287+
if (registrationPhase) {
288+
challenge.registrationStartDate =
289+
registrationPhase.actualStartDate || registrationPhase.scheduledStartDate;
290+
challenge.registrationEndDate =
291+
registrationPhase.actualEndDate || registrationPhase.scheduledEndDate;
292+
}
293+
if (submissionPhase) {
294+
challenge.submissionStartDate =
295+
submissionPhase.actualStartDate || submissionPhase.scheduledStartDate;
296+
297+
challenge.submissionEndDate =
298+
submissionPhase.actualEndDate || submissionPhase.scheduledEndDate;
299+
}
300+
}
301+
302+
challenge.created = new Date(challenge.created).toISOString();
303+
challenge.updated = new Date(challenge.updated).toISOString();
304+
challenge.startDate = new Date(challenge.startDate).toISOString();
305+
challenge.endDate = new Date(challenge.endDate).toISOString();
306+
307+
if (track) {
308+
challenge.track = track.name;
309+
}
310+
311+
if (type) {
312+
challenge.type = type.name;
313+
}
314+
315+
challenge.metadata = challenge.metadata.map((m) => {
316+
try {
317+
m.value = JSON.stringify(JSON.parse(m.value)); // when we update how we index data, make this a JSON field
318+
} catch (err) {
319+
// do nothing
320+
}
321+
return m;
322+
});
323+
}
324+
325+
convertPrizeSetValuesToCents(prizeSets) {
326+
prizeSets.forEach((prizeSet) => {
327+
prizeSet.prizes.forEach((prize) => {
328+
prize.amountInCents = prize.value * 100;
329+
delete prize.value;
330+
});
331+
});
332+
}
333+
334+
convertPrizeSetValuesToDollars(prizeSets, overview) {
335+
prizeSets.forEach((prizeSet) => {
336+
prizeSet.prizes.forEach((prize) => {
337+
if (prize.amountInCents != null) {
338+
prize.value = prize.amountInCents / 100;
339+
delete prize.amountInCents;
340+
}
341+
});
342+
});
343+
if (overview && overview.totalPrizesInCents) {
344+
overview.totalPrizes = overview.totalPrizesInCents / 100;
345+
delete overview.totalPrizesInCents;
346+
}
347+
}
101348
}
102349

103350
module.exports = new ChallengeHelper();

src/common/group-helper.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const _ = require("lodash");
2+
const errors = require("./errors");
3+
const helper = require("./helper");
4+
5+
const { hasAdminRole } = require("./role-helper");
6+
7+
class GroupHelper {
8+
/**
9+
* Ensure the user can access the groups being updated to
10+
* @param {Object} currentUser the user who perform operation
11+
* @param {Object} data the challenge data to be updated
12+
* @param {String} challenge the original challenge data
13+
*/
14+
async ensureAcessibilityToModifiedGroups(currentUser, data, challenge) {
15+
const needToCheckForGroupAccess = !currentUser
16+
? true
17+
: !currentUser.isMachine && !hasAdminRole(currentUser);
18+
if (!needToCheckForGroupAccess) {
19+
return;
20+
}
21+
const userGroups = await helper.getUserGroups(currentUser.userId);
22+
const userGroupsIds = _.map(userGroups, (group) => group.id);
23+
const updatedGroups = _.difference(
24+
_.union(challenge.groups, data.groups),
25+
_.intersection(challenge.groups, data.groups)
26+
);
27+
const filtered = updatedGroups.filter((g) => !userGroupsIds.includes(g));
28+
if (filtered.length > 0) {
29+
throw new errors.ForbiddenError(
30+
"ensureAcessibilityToModifiedGroups :: You don't have access to this group!"
31+
);
32+
}
33+
}
34+
}
35+
36+
module.exports = new GroupHelper();

src/common/helper.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,16 +450,19 @@ axiosRetry(axios, {
450450
* @param {String} token The token
451451
* @returns
452452
*/
453-
async function createSelfServiceProject(name, description, type, token) {
453+
async function createSelfServiceProject(name, description, type) {
454454
const projectObj = {
455455
name,
456456
description,
457457
type,
458458
};
459+
460+
const token = await m2mHelper.getM2MToken();
459461
const url = `${config.PROJECTS_API_URL}`;
460462
const res = await axios.post(url, projectObj, {
461463
headers: { Authorization: `Bearer ${token}` },
462464
});
465+
463466
return _.get(res, "data.id");
464467
}
465468

@@ -942,7 +945,7 @@ async function listChallengesByMember(memberId) {
942945
* @returns {Promise<Array>} an array of resources.
943946
*/
944947
async function listResourcesByMemberAndChallenge(memberId, challengeId) {
945-
const token = await getM2MToken();
948+
const token = await m2mHelper.getM2MToken();
946949
let response = {};
947950
try {
948951
response = await axios.get(config.RESOURCES_API_URL, {
@@ -1123,7 +1126,7 @@ async function ensureUserCanViewChallenge(currentUser, challenge) {
11231126
*
11241127
* @param {Object} currentUser the user who perform operation
11251128
* @param {Object} challenge the challenge to check
1126-
* @returns {undefined}
1129+
* @returns {Promise}
11271130
*/
11281131
async function ensureUserCanModifyChallenge(currentUser, challenge) {
11291132
// check groups authorization

src/common/m2m-helper.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ const config = require("config");
33
const m2mAuth = require("tc-core-library-js").auth.m2m;
44

55
class M2MHelper {
6+
static m2m = null;
7+
68
constructor() {
7-
this.m2m = m2mAuth(_.pick(config, ["AUTH0_URL", "AUTH0_AUDIENCE", "TOKEN_CACHE_TIME"]));
9+
M2MHelper.m2m = m2mAuth(_.pick(config, ["AUTH0_URL", "AUTH0_AUDIENCE", "TOKEN_CACHE_TIME"]));
810
}
911
/**
1012
* Get M2M token.
1113
* @returns {Promise<String>} the M2M token
1214
*/
13-
async getM2MToken() {
14-
return this.m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET);
15+
getM2MToken() {
16+
return M2MHelper.m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET);
1517
}
1618
}
1719

0 commit comments

Comments
 (0)