Skip to content

Commit bc0da49

Browse files
author
James Cori
committed
Merge branch 'develop'
2 parents d61181c + 56ee1c4 commit bc0da49

File tree

6 files changed

+115
-25
lines changed

6 files changed

+115
-25
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ The following parameters can be set in config files or in env variables:
6262
- ES.ES_REFRESH: Elasticsearch refresh method. Default to string `true`(i.e. refresh immediately)
6363
- FILE_UPLOAD_SIZE_LIMIT: the file upload size limit in bytes
6464
- RESOURCES_API_URL: TC resources API base URL
65-
- V3_PROJECTS_API_URL: TC direct projects API base URL
6665
- GROUPS_API_URL: TC groups API base URL
6766
- PROJECTS_API_URL: TC projects API base URL
6867
- TERMS_API_URL: TC Terms API Base URL
6968
- COPILOT_RESOURCE_ROLE_IDS: copilot resource role ids allowed to upload attachment
7069
- HEALTH_CHECK_TIMEOUT: health check timeout in milliseconds
7170
- SCOPES: the configurable M2M token scopes, refer `config/default.js` for more details
7271
- M2M_AUDIT_HANDLE: the audit name used when perform create/update operation using M2M token
72+
- FORUM_TITLE_LENGTH_LIMIT: the forum title length limit
7373

7474
You can find sample `.env` files inside the `/docs` directory.
7575

config/default.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ module.exports = {
5454
GROUPS_API_URL: process.env.GROUPS_API_URL || 'http://localhost:4000/v5/groups',
5555
PROJECTS_API_URL: process.env.PROJECTS_API_URL || 'http://localhost:4000/v5/projects',
5656
TERMS_API_URL: process.env.TERMS_API_URL || 'http://localhost:4000/v5/terms',
57-
V3_PROJECTS_API_URL: process.env.V3_PROJECTS_API_URL || 'http://localhost:4000/v3/direct/projects',
5857
// copilot resource role ids allowed to upload attachment
5958
COPILOT_RESOURCE_ROLE_IDS: process.env.COPILOT_RESOURCE_ROLE_IDS
6059
? process.env.COPILOT_RESOURCE_ROLE_IDS.split(',') : ['10ba038e-48da-487b-96e8-8d3b99b6d18b'],
@@ -76,5 +75,7 @@ module.exports = {
7675

7776
DEFAULT_CONFIDENTIALITY_TYPE: process.env.DEFAULT_CONFIDENTIALITY_TYPE || 'public',
7877

79-
M2M_AUDIT_HANDLE: process.env.M2M_AUDIT_HANDLE || 'tcwebservice'
78+
M2M_AUDIT_HANDLE: process.env.M2M_AUDIT_HANDLE || 'tcwebservice',
79+
80+
FORUM_TITLE_LENGTH_LIMIT: process.env.FORUM_TITLE_LENGTH_LIMIT || 90
8081
}

docs/swagger.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,18 @@ paths:
190190
required: false
191191
default: true
192192
type: boolean
193+
- name: totalPrizesFrom
194+
in: query
195+
description: >-
196+
Filter by the lowest amount of total prizes on the challenge
197+
type: number
198+
required: false
199+
- name: totalPrizesTo
200+
in: query
201+
description: >-
202+
Filter by the highest amount of total prizes on the challenge
203+
type: number
204+
required: false
193205
- name: events
194206
in: query
195207
description: >-

src/common/helper.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -757,15 +757,26 @@ async function getProjectDefaultTerms (projectId) {
757757
* @param {Number} projectId The id of the project for which to get the default terms of use
758758
* @returns {Promise<Number>} The billing account ID
759759
*/
760-
async function getProjectBillingAccount (projectId) {
760+
async function getProjectBillingInformation (projectId) {
761761
const token = await getM2MToken()
762-
const projectUrl = `${config.V3_PROJECTS_API_URL}/${projectId}`
762+
const projectUrl = `${config.PROJECTS_API_URL}/${projectId}/billingAccount`
763763
try {
764764
const res = await axios.get(projectUrl, { headers: { Authorization: `Bearer ${token}` } })
765-
return _.get(res, 'data.result.content.billingAccountIds[0]', null)
765+
let markup = _.get(res, 'data.markup', null) ? _.toNumber(_.get(res, 'data.markup', null)) : null
766+
if (markup && markup > 0) {
767+
// TODO - Hack to change int returned from api to decimal
768+
markup = markup / 100
769+
}
770+
return {
771+
billingAccountId: _.get(res, 'data.tcBillingAccountId', null),
772+
markup
773+
}
766774
} catch (err) {
767775
if (_.get(err, 'response.status') === HttpStatus.NOT_FOUND) {
768-
throw new errors.BadRequestError(`Project with id: ${projectId} doesn't exist`)
776+
return {
777+
billingAccountId: null,
778+
markup: null
779+
}
769780
} else {
770781
// re-throw other error
771782
throw err
@@ -978,7 +989,7 @@ module.exports = {
978989
validateESRefreshMethod,
979990
getProjectDefaultTerms,
980991
validateChallengeTerms,
981-
getProjectBillingAccount,
992+
getProjectBillingInformation,
982993
expandWithSubGroups,
983994
getCompleteUserGroupTreeIds,
984995
expandWithParentGroups,

src/models/Challenge.js

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ const schema = new Schema({
2828
type: Object,
2929
required: false
3030
},
31+
billing: {
32+
type: Object,
33+
required: false
34+
},
3135
name: {
3236
type: String,
3337
required: true

src/services/ChallengeService.js

+79-17
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,17 @@ async function searchChallenges (currentUser, criteria) {
264264
})
265265
}
266266

267+
if (criteria.totalPrizesFrom || criteria.totalPrizesTo) {
268+
const prizeRangeQuery = {}
269+
if (criteria.totalPrizesFrom) {
270+
prizeRangeQuery.gte = criteria.totalPrizesFrom
271+
}
272+
if (criteria.totalPrizesTo) {
273+
prizeRangeQuery.lte = criteria.totalPrizesTo
274+
}
275+
boolQuery.push({ range: { 'overview.totalPrizes': prizeRangeQuery } })
276+
}
277+
267278
if (criteria.useSchedulingAPI) {
268279
boolQuery.push({ match_phrase: { 'legacy.useSchedulingAPI': criteria.useSchedulingAPI } })
269280
}
@@ -557,8 +568,7 @@ async function searchChallenges (currentUser, criteria) {
557568
}
558569
}
559570

560-
// logger.debug(`es Query ${JSON.stringify(esQuery)}`)
561-
571+
logger.debug(`es Query ${JSON.stringify(esQuery)}`)
562572
// Search with constructed query
563573
let docs
564574
try {
@@ -580,6 +590,7 @@ async function searchChallenges (currentUser, criteria) {
580590
// Hide privateDescription for non-register challenges
581591
if (currentUser) {
582592
if (!currentUser.isMachine && !helper.hasAdminRole(currentUser)) {
593+
result = _.each(result, (val) => _.unset(val, 'billing'))
583594
const ids = await helper.listChallengesByMember(currentUser.userId)
584595
result = _.each(result, (val) => {
585596
if (!_.includes(ids, val.id)) {
@@ -588,7 +599,11 @@ async function searchChallenges (currentUser, criteria) {
588599
})
589600
}
590601
} else {
591-
result = _.each(result, val => _.unset(val, 'privateDescription'))
602+
result = _.each(result, val => {
603+
_.unset(val, 'billing')
604+
_.unset(val, 'privateDescription')
605+
return val
606+
})
592607
}
593608

594609
if (criteria.isLightweight === 'true') {
@@ -677,7 +692,9 @@ searchChallenges.schema = {
677692
taskMemberId: Joi.string(),
678693
events: Joi.array().items(Joi.string()),
679694
includeAllEvents: Joi.boolean().default(true),
680-
useSchedulingAPI: Joi.boolean()
695+
useSchedulingAPI: Joi.boolean(),
696+
totalPrizesFrom: Joi.number().min(0),
697+
totalPrizesTo: Joi.number().min(0)
681698
}).unknown(true)
682699
}
683700

@@ -829,6 +846,12 @@ async function createChallenge (currentUser, challenge) {
829846
}
830847
challenge.name = xss(challenge.name)
831848
challenge.description = xss(challenge.description)
849+
if (!challenge.status) {
850+
challenge.status = constants.challengeStatuses.New
851+
}
852+
if (!challenge.startDate) {
853+
challenge.startDate = new Date()
854+
}
832855
if (challenge.status === constants.challengeStatuses.Active) {
833856
throw new errors.BadRequestError('You cannot create an Active challenge. Please create a Draft challenge and then change the status to Active.')
834857
}
@@ -837,6 +860,13 @@ async function createChallenge (currentUser, challenge) {
837860
_.set(challenge, 'legacy.directProjectId', directProjectId)
838861
}
839862
const { track, type } = await validateChallengeData(challenge)
863+
const { billingAccountId, markup } = await helper.getProjectBillingInformation(_.get(challenge, 'projectId'))
864+
if (billingAccountId && _.isUndefined(_.get(challenge, 'billing.billingAccountId'))) {
865+
_.set(challenge, 'billing.billingAccountId', billingAccountId)
866+
}
867+
if (markup && _.isUndefined(_.get(challenge, 'billing.markup'))) {
868+
_.set(challenge, 'billing.markup', markup)
869+
}
840870
if (_.get(type, 'isTask')) {
841871
_.set(challenge, 'task.isTask', true)
842872
if (_.isUndefined(_.get(challenge, 'task.isAssigned'))) {
@@ -851,6 +881,7 @@ async function createChallenge (currentUser, challenge) {
851881
if (challenge.discussions && challenge.discussions.length > 0) {
852882
for (let i = 0; i < challenge.discussions.length; i += 1) {
853883
challenge.discussions[i].id = uuid()
884+
challenge.discussions[i].name = challenge.discussions[i].name.substring(0, config.FORUM_TITLE_LENGTH_LIMIT)
854885
}
855886
}
856887
if (challenge.phases && challenge.phases.length > 0) {
@@ -982,6 +1013,10 @@ createChallenge.schema = {
9821013
useSchedulingAPI: Joi.boolean(),
9831014
pureV5Task: Joi.boolean()
9841015
}),
1016+
billing: Joi.object().keys({
1017+
billingAccountId: Joi.string(),
1018+
markup: Joi.number().min(0).max(100)
1019+
}).unknown(true),
9851020
task: Joi.object().keys({
9861021
isTask: Joi.boolean().default(false),
9871022
isAssigned: Joi.boolean().default(false),
@@ -1026,7 +1061,7 @@ createChallenge.schema = {
10261061
projectId: Joi.number().integer().positive().required(),
10271062
legacyId: Joi.number().integer().positive(),
10281063
startDate: Joi.date(),
1029-
status: Joi.string().valid(_.values(constants.challengeStatuses)).required(),
1064+
status: Joi.string().valid(_.values(constants.challengeStatuses)),
10301065
groups: Joi.array().items(Joi.optionalId()).unique(),
10311066
// gitRepoURLs: Joi.array().items(Joi.string().uri()),
10321067
terms: Joi.array().items(Joi.object().keys({
@@ -1097,12 +1132,14 @@ async function getChallenge (currentUser, id) {
10971132
let memberChallengeIds
10981133
if (currentUser) {
10991134
if (!currentUser.isMachine && !helper.hasAdminRole(currentUser)) {
1135+
_.unset(challenge, 'billing')
11001136
memberChallengeIds = await helper.listChallengesByMember(currentUser.userId)
11011137
if (!_.includes(memberChallengeIds, challenge.id)) {
11021138
_.unset(challenge, 'privateDescription')
11031139
}
11041140
}
11051141
} else {
1142+
_.unset(challenge, 'billing')
11061143
_.unset(challenge, 'privateDescription')
11071144
}
11081145

@@ -1203,25 +1240,28 @@ async function update (currentUser, challengeId, data, isFull) {
12031240
// helper.ensureNoDuplicateOrNullElements(data.gitRepoURLs, 'gitRepoURLs')
12041241

12051242
const challenge = await helper.getById('Challenge', challengeId)
1206-
// FIXME: Tech Debt
1207-
let billingAccountId
1243+
const { billingAccountId, markup } = await helper.getProjectBillingInformation(_.get(challenge, 'projectId'))
1244+
if (billingAccountId && _.isUndefined(_.get(challenge, 'billing.billingAccountId'))) {
1245+
_.set(data, 'billing.billingAccountId', billingAccountId)
1246+
}
1247+
if (markup && _.isUndefined(_.get(challenge, 'billing.markup'))) {
1248+
_.set(data, 'billing.markup', markup)
1249+
}
12081250
if (data.status) {
12091251
if (data.status === constants.challengeStatuses.Active) {
1210-
if (!_.get(challenge, 'legacy.pureV5Task') && _.isUndefined(_.get(challenge, 'legacy.directProjectId'))) {
1252+
if (!_.get(challenge, 'legacy.pureV5Task') && _.isUndefined(_.get(challenge, 'legacyId'))) {
12111253
throw new errors.BadRequestError('You cannot activate the challenge as it has not been created on legacy yet. Please try again later or contact support.')
12121254
}
1213-
billingAccountId = await helper.getProjectBillingAccount(_.get(challenge, 'legacy.directProjectId'))
12141255
// if activating a challenge, the challenge must have a billing account id
12151256
if ((!billingAccountId || billingAccountId === null) &&
12161257
challenge.status === constants.challengeStatuses.Draft) {
1217-
throw new errors.BadRequestError('Cannot Activate this project, it has no active billing accounts.')
1258+
throw new errors.BadRequestError('Cannot Activate this project, it has no active billing account.')
12181259
}
12191260
}
12201261
if (data.status === constants.challengeStatuses.Completed) {
12211262
if (challenge.status !== constants.challengeStatuses.Active) {
12221263
throw new errors.BadRequestError('You cannot mark a Draft challenge as Completed')
12231264
}
1224-
billingAccountId = await helper.getProjectBillingAccount(_.get(challenge, 'legacy.directProjectId'))
12251265
}
12261266
}
12271267

@@ -1246,6 +1286,12 @@ async function update (currentUser, challengeId, data, isFull) {
12461286
_.extend(challenge.legacy, data.legacy)
12471287
}
12481288

1289+
if (!_.isUndefined(challenge.billing) && !_.isUndefined(data.billing)) {
1290+
_.extend(challenge.billing, data.billing)
1291+
} else if (_.isUndefined(challenge.billing) && !_.isUndefined(data.billing)) {
1292+
challenge.billing = data.billing
1293+
}
1294+
12491295
await helper.ensureUserCanModifyChallenge(currentUser, challenge)
12501296

12511297
// check groups access to be updated group values
@@ -1414,6 +1460,9 @@ async function update (currentUser, challengeId, data, isFull) {
14141460
_.intersection(oldIds, newIds).length !== value.length) {
14151461
op = '$PUT'
14161462
}
1463+
} else if (key === 'billing' || key === 'legacy') {
1464+
// make sure that's always being udpated
1465+
op = '$PUT'
14171466
} else if (_.isUndefined(challenge[key]) || challenge[key] !== value) {
14181467
op = '$PUT'
14191468
}
@@ -1609,11 +1658,7 @@ async function update (currentUser, challengeId, data, isFull) {
16091658

16101659
// post bus event
16111660
logger.debug(`Post Bus Event: ${constants.Topics.ChallengeUpdated} ${JSON.stringify(challenge)}`)
1612-
const busEventPayload = { ...challenge }
1613-
if (billingAccountId) {
1614-
busEventPayload.billingAccountId = billingAccountId
1615-
}
1616-
await helper.postBusEvent(constants.Topics.ChallengeUpdated, busEventPayload)
1661+
await helper.postBusEvent(constants.Topics.ChallengeUpdated, challenge)
16171662
if (phasesHaveBeenModified === true && _.get(challenge, 'legacy.useSchedulingAPI')) {
16181663
await helper.postBusEvent(config.SCHEDULING_TOPIC, { id: challengeId })
16191664
}
@@ -1687,6 +1732,12 @@ function sanitizeChallenge (challenge) {
16871732
'pureV5Task'
16881733
])
16891734
}
1735+
if (challenge.billing) {
1736+
sanitized.billing = _.pick(challenge.billing, [
1737+
'billingAccountId',
1738+
'markup'
1739+
])
1740+
}
16901741
if (challenge.metadata) {
16911742
sanitized.metadata = _.map(challenge.metadata, meta => _.pick(meta, ['name', 'value']))
16921743
}
@@ -1706,7 +1757,10 @@ function sanitizeChallenge (challenge) {
17061757
sanitized.winners = _.map(challenge.winners, winner => _.pick(winner, ['userId', 'handle', 'placement']))
17071758
}
17081759
if (challenge.discussions) {
1709-
sanitized.discussions = _.map(challenge.discussions, discussion => _.pick(discussion, ['id', 'provider', 'name', 'type', 'url', 'options']))
1760+
sanitized.discussions = _.map(challenge.discussions, discussion => ({
1761+
..._.pick(discussion, ['id', 'provider', 'name', 'type', 'url', 'options']),
1762+
name: _.get(discussion, 'name', '').substring(0, config.FORUM_TITLE_LENGTH_LIMIT)
1763+
}))
17101764
}
17111765
if (challenge.terms) {
17121766
sanitized.terms = _.map(challenge.terms, term => _.pick(term, ['id', 'roleId']))
@@ -1743,6 +1797,10 @@ fullyUpdateChallenge.schema = {
17431797
useSchedulingAPI: Joi.boolean(),
17441798
pureV5Task: Joi.boolean()
17451799
}).unknown(true),
1800+
billing: Joi.object().keys({
1801+
billingAccountId: Joi.string(),
1802+
markup: Joi.number().min(0).max(100)
1803+
}).unknown(true),
17461804
task: Joi.object().keys({
17471805
isTask: Joi.boolean().default(false),
17481806
isAssigned: Joi.boolean().default(false),
@@ -1845,6 +1903,10 @@ partiallyUpdateChallenge.schema = {
18451903
isAssigned: Joi.boolean().default(false),
18461904
memberId: Joi.string().allow(null)
18471905
}),
1906+
billing: Joi.object().keys({
1907+
billingAccountId: Joi.string(),
1908+
markup: Joi.number().min(0).max(100)
1909+
}).unknown(true),
18481910
trackId: Joi.optionalId(),
18491911
typeId: Joi.optionalId(),
18501912
name: Joi.string(),

0 commit comments

Comments
 (0)