Skip to content

Commit 2676dcf

Browse files
Merge branch 'develop' into increase-cov
2 parents 14a502e + 1d77b48 commit 2676dcf

File tree

12 files changed

+234
-121
lines changed

12 files changed

+234
-121
lines changed

README.md

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module.exports = {
2323
// bus API config params
2424
BUSAPI_URL: process.env.BUSAPI_URL || 'https://api.topcoder-dev.com/v5',
2525
KAFKA_ERROR_TOPIC: process.env.KAFKA_ERROR_TOPIC || 'common.error.reporting',
26+
SCHEDULING_TOPIC: process.env.SCHEDULING_TOPIC || 'challenge.notification.schedule.update',
2627

2728
AMAZON: {
2829
// AWS_ACCESS_KEY_ID: process.env.AWS_FAKE_ID || 'FAKE_ACCESS_KEY',
@@ -54,7 +55,6 @@ module.exports = {
5455
GROUPS_API_URL: process.env.GROUPS_API_URL || 'http://localhost:4000/v5/groups',
5556
PROJECTS_API_URL: process.env.PROJECTS_API_URL || 'http://localhost:4000/v5/projects',
5657
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',
5858
// copilot resource role ids allowed to upload attachment
5959
COPILOT_RESOURCE_ROLE_IDS: process.env.COPILOT_RESOURCE_ROLE_IDS
6060
? process.env.COPILOT_RESOURCE_ROLE_IDS.split(',') : ['10ba038e-48da-487b-96e8-8d3b99b6d18b'],
@@ -76,5 +76,7 @@ module.exports = {
7676

7777
DEFAULT_CONFIDENTIALITY_TYPE: process.env.DEFAULT_CONFIDENTIALITY_TYPE || 'public',
7878

79-
M2M_AUDIT_HANDLE: process.env.M2M_AUDIT_HANDLE || 'TopcoderService'
79+
M2M_AUDIT_HANDLE: process.env.M2M_AUDIT_HANDLE || 'tcwebservice',
80+
81+
FORUM_TITLE_LENGTH_LIMIT: process.env.FORUM_TITLE_LENGTH_LIMIT || 110
8082
}

docs/swagger.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ paths:
400400
Will be ignored for regular users or not authenticated users
401401
required: false
402402
type: string
403+
- name: useSchedulingAPI
404+
in: query
405+
description: Search based on `legacy.useSchedulingAPI`
406+
type: boolean
407+
required: false
403408
- in: body
404409
name: body
405410
required: false
@@ -2095,7 +2100,7 @@ paths:
20952100
tags:
20962101
- Attachments
20972102
description: >
2098-
Create a new attachment in the system.
2103+
Create a new attachment in the system. If you want to create multiple attachment, you can pass an array of objects instead of a single object.
20992104
security:
21002105
- bearer: []
21012106
produces:

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"drop-tables": "node src/scripts/drop-tables.js",
1414
"create-tables": "node src/scripts/create-tables.js",
1515
"seed-tables": "node src/scripts/seed-tables.js",
16+
"check-timeline-templates": "node src/scripts/check-templates.js",
1617
"view-data": "node src/scripts/view-data.js",
1718
"view-es-data": "node src/scripts/view-es-data.js",
1819
"test": "mocha --require test/prepare.js -t 30000 test/unit/*.test.js --exit",

src/common/helper.js

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -607,13 +607,20 @@ function getESClient () {
607607
/**
608608
* Ensure project exist
609609
* @param {String} projectId the project id
610-
* @param {String} userToken the user token
610+
* @param {String} currentUser the user
611611
*/
612-
async function ensureProjectExist (projectId, userToken) {
612+
async function ensureProjectExist (projectId, currentUser) {
613613
let token = await getM2MToken()
614614
const url = `${config.PROJECTS_API_URL}/${projectId}`
615615
try {
616-
await axios.get(url, { headers: { Authorization: `Bearer ${token}` } })
616+
const res = await axios.get(url, { headers: { Authorization: `Bearer ${token}` } })
617+
if (currentUser.isMachine || hasAdminRole(currentUser)) {
618+
return res.data
619+
}
620+
if (!_.find(_.get(res, 'data.members', []), m => _.toString(m.userId) === _.toString(currentUser.userId))) {
621+
throw new errors.ForbiddenError(`You don't have access to project with ID: ${projectId}`)
622+
}
623+
return res.data
617624
} catch (err) {
618625
if (_.get(err, 'response.status') === HttpStatus.NOT_FOUND) {
619626
throw new errors.BadRequestError(`Project with id: ${projectId} doesn't exist`)
@@ -655,9 +662,28 @@ function calculateChallengeEndDate (challenge, data) {
655662
*/
656663
async function listChallengesByMember (memberId) {
657664
const token = await getM2MToken()
658-
const url = `${config.RESOURCES_API_URL}/${memberId}/challenges?perPage=10000`
659-
const res = await axios.get(url, { headers: { Authorization: `Bearer ${token}` } })
660-
return res.data || []
665+
let allIds = []
666+
// get search is paginated, we need to get all pages' data
667+
let page = 1
668+
while (true) {
669+
const result = await axios.get(`${config.RESOURCES_API_URL}/${memberId}/challenges`, {
670+
headers: { Authorization: `Bearer ${token}` },
671+
params: {
672+
page,
673+
perPage: 10000
674+
}
675+
})
676+
const ids = result.data || []
677+
if (ids.length === 0) {
678+
break
679+
}
680+
allIds = allIds.concat(ids)
681+
page += 1
682+
if (result.headers['x-total-pages'] && page > Number(result.headers['x-total-pages'])) {
683+
break
684+
}
685+
}
686+
return allIds
661687
}
662688

663689
/**
@@ -699,12 +725,20 @@ async function getProjectDefaultTerms (projectId) {
699725
* @param {Number} projectId The id of the project for which to get the default terms of use
700726
* @returns {Promise<Number>} The billing account ID
701727
*/
702-
async function getProjectBillingAccount (projectId) {
728+
async function getProjectBillingInformation (projectId) {
703729
const token = await getM2MToken()
704-
const projectUrl = `${config.V3_PROJECTS_API_URL}/${projectId}`
730+
const projectUrl = `${config.PROJECTS_API_URL}/${projectId}/billingAccount`
705731
try {
706732
const res = await axios.get(projectUrl, { headers: { Authorization: `Bearer ${token}` } })
707-
return _.get(res, 'data.result.content.billingAccountIds[0]', null)
733+
let markup = _.get(res, 'data.markup', null) ? _.toNumber(_.get(res, 'data.markup', null)) : null
734+
if (markup && markup > 0) {
735+
// TODO - Hack to change int returned from api to decimal
736+
markup = markup / 100
737+
}
738+
return {
739+
billingAccountId: _.get(res, 'data.tcBillingAccountId', null),
740+
markup
741+
}
708742
} catch (err) {
709743
if (_.get(err, 'response.status') === HttpStatus.NOT_FOUND) {
710744
throw new errors.BadRequestError(`Project with id: ${projectId} doesn't exist`)
@@ -806,18 +840,16 @@ async function ensureAccessibleByGroupsAccess (currentUser, challenge) {
806840
* @param {Object} challenge the challenge to check
807841
*/
808842
async function _ensureAccessibleForTaskChallenge (currentUser, challenge) {
809-
let memberChallengeIds
810-
// Remove privateDescription for unregistered users
843+
let challengeResourceIds
811844
if (currentUser) {
812845
if (!currentUser.isMachine) {
813-
memberChallengeIds = await listChallengesByMember(currentUser.userId)
814-
if (!_.includes(memberChallengeIds, challenge.id)) {
815-
}
846+
const challengeResources = await getChallengeResources(challenge.id)
847+
challengeResourceIds = _.map(challengeResources, r => _.toString(r.memberId))
816848
}
817849
}
818850
// Check if challenge is task and apply security rules
819851
if (_.get(challenge, 'task.isTask', false) && _.get(challenge, 'task.isAssigned', false)) {
820-
const canAccesChallenge = _.isUndefined(currentUser) ? false : _.includes((memberChallengeIds || []), challenge.id) || currentUser.isMachine || hasAdminRole(currentUser)
852+
const canAccesChallenge = _.isUndefined(currentUser) ? false : currentUser.isMachine || hasAdminRole(currentUser) || _.includes((challengeResourceIds || []), _.toString(currentUser.userId))
821853
if (!canAccesChallenge) {
822854
throw new errors.ForbiddenError(`You don't have access to view this challenge`)
823855
}
@@ -920,7 +952,7 @@ module.exports = {
920952
validateESRefreshMethod,
921953
getProjectDefaultTerms,
922954
validateChallengeTerms,
923-
getProjectBillingAccount,
955+
getProjectBillingInformation,
924956
expandWithSubGroups,
925957
getCompleteUserGroupTreeIds,
926958
expandWithParentGroups,

src/controllers/AttachmentController.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Controller for attachment endpoints
33
*/
44
const HttpStatus = require('http-status-codes')
5+
const _ = require('lodash')
56
const service = require('../services/AttachmentService')
67

78
/**
@@ -10,7 +11,8 @@ const service = require('../services/AttachmentService')
1011
* @param {Object} res the response
1112
*/
1213
async function createAttachment (req, res) {
13-
const result = await service.createAttachment(req.authUser, req.params.challengeId, req.body)
14+
const body = _.isArray(req.body) ? req.body : [req.body]
15+
const result = await service.createAttachment(req.authUser, req.params.challengeId, body)
1416
res.status(HttpStatus.CREATED).send(result)
1517
}
1618

src/controllers/ChallengeController.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async function searchChallenges (req, res) {
2424
*/
2525
async function createChallenge (req, res) {
2626
logger.debug(`createChallenge User: ${JSON.stringify(req.authUser)} - Body: ${JSON.stringify(req.body)}`)
27-
const result = await service.createChallenge(req.authUser, req.body, req.userToken)
27+
const result = await service.createChallenge(req.authUser, req.body)
2828
res.status(HttpStatus.CREATED).send(result)
2929
}
3030

@@ -45,7 +45,7 @@ async function getChallenge (req, res) {
4545
*/
4646
async function fullyUpdateChallenge (req, res) {
4747
logger.debug(`fullyUpdateChallenge User: ${JSON.stringify(req.authUser)} - ChallengeID: ${req.params.challengeId} - Body: ${JSON.stringify(req.body)}`)
48-
const result = await service.fullyUpdateChallenge(req.authUser, req.params.challengeId, req.body, req.userToken)
48+
const result = await service.fullyUpdateChallenge(req.authUser, req.params.challengeId, req.body)
4949
res.send(result)
5050
}
5151

@@ -56,7 +56,7 @@ async function fullyUpdateChallenge (req, res) {
5656
*/
5757
async function partiallyUpdateChallenge (req, res) {
5858
logger.debug(`partiallyUpdateChallenge User: ${JSON.stringify(req.authUser)} - ChallengeID: ${req.params.challengeId} - Body: ${JSON.stringify(req.body)}`)
59-
const result = await service.partiallyUpdateChallenge(req.authUser, req.params.challengeId, req.body, req.userToken)
59+
const result = await service.partiallyUpdateChallenge(req.authUser, req.params.challengeId, req.body)
6060
res.send(result)
6161
}
6262

src/models/Challenge.js

Lines changed: 4 additions & 0 deletions
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/scripts/check-templates.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const _ = require('lodash')
2+
const axios = require('axios')
3+
const TEMPLATE_ID = '2d0807fa-ece1-4328-a260-76f5f6b559e0' // RUX challenge
4+
// const TEMPLATE_ID = '7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c' // dev challenge
5+
// const TEMPLATE_ID = 'd4201ca4-8437-4d63-9957-3f7708184b07' // design with checkpoint
6+
async function main () {
7+
let res
8+
res = await axios.get('http://api.topcoder-dev.com/v5/timeline-templates')
9+
const template = _.find(res.data, entry => entry.id === TEMPLATE_ID)
10+
res = await axios.get('http://api.topcoder-dev.com/v5/challenge-phases')
11+
const phases = res.data
12+
_.each(template.phases, (phase) => {
13+
const phaseInstance = _.find(phases, p => p.id === phase.phaseId)
14+
const pred = phase.predecessor ? _.find(phases, p => p.id === phase.predecessor) : null
15+
console.log(`Phase Length: ${phase.defaultDuration / 60 / 60} hrs \t ${phaseInstance.name} - Depends on ${pred ? pred.name : 'nothing'}`)
16+
})
17+
}
18+
main()

src/scripts/seed/TimelineTemplate.json

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
{
33
"id": "7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c",
44
"name": "Default Challenge",
5-
"description": "Standard Default Challenge Timeline",
5+
"description": "Default Challenge Timeline",
66
"isActive": true,
77
"phases": [
88
{
99
"phaseId": "a93544bc-c165-4af4-b55e-18f3593b457a",
10-
"defaultDuration": 172800
10+
"defaultDuration": 432000
1111
},
1212
{
1313
"phaseId": "6950164f-3c5e-4bdc-abc8-22aaf5a1bd49",
14-
"predecessor": "a93544bc-c165-4af4-b55e-18f3593b457a",
1514
"defaultDuration": 432000
1615
},
1716
{
@@ -22,12 +21,12 @@
2221
{
2322
"phaseId": "1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6",
2423
"predecessor": "aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b",
25-
"defaultDuration": 43200
24+
"defaultDuration": 86400
2625
},
2726
{
2827
"phaseId": "797a6af7-cd3f-4436-9fca-9679f773bee9",
2928
"predecessor": "1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6",
30-
"defaultDuration": 57600
29+
"defaultDuration": 43200
3130
}
3231
]
3332
},
@@ -44,7 +43,6 @@
4443
},
4544
{
4645
"phaseId": "6950164f-3c5e-4bdc-abc8-22aaf5a1bd49",
47-
"predecessor": "a93544bc-c165-4af4-b55e-18f3593b457a",
4846
"defaultDuration": 345600
4947
},
5048
{
@@ -55,7 +53,7 @@
5553
{
5654
"phaseId": "1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6",
5755
"predecessor": "aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b",
58-
"defaultDuration": 43200
56+
"defaultDuration": 86400
5957
},
6058
{
6159
"phaseId": "797a6af7-cd3f-4436-9fca-9679f773bee9",
@@ -92,7 +90,6 @@
9290
},
9391
{
9492
"phaseId": "6950164f-3c5e-4bdc-abc8-22aaf5a1bd49",
95-
"predecessor": "a93544bc-c165-4af4-b55e-18f3593b457a",
9693
"defaultDuration": 1209600
9794
},
9895
{
@@ -106,33 +103,20 @@
106103
{
107104
"id": "d4201ca4-8437-4d63-9957-3f7708184b07",
108105
"name": "Design with Checkpoints",
109-
"description": "Standard Design challenge timeline",
106+
"description": "Standard Design Challenge Timeline",
110107
"isActive": true,
111108
"phases": [
112-
{
113-
"phaseId": "fb21431c-119e-4bc7-b447-d0af3f2be6b4",
114-
"defaultDuration": 86400
115-
},
116-
{
117-
"phaseId": "2752454b-0952-4a42-a4f0-f3fb88a9b065",
118-
"predecessor": "fb21431c-119e-4bc7-b447-d0af3f2be6b4",
119-
"defaultDuration": 43200
120-
},
121-
122109
{
123110
"phaseId": "a93544bc-c165-4af4-b55e-18f3593b457a",
124-
"predecessor": "2752454b-0952-4a42-a4f0-f3fb88a9b065",
125-
"defaultDuration": 345600
111+
"defaultDuration": 518400
126112
},
127113
{
128114
"phaseId": "6950164f-3c5e-4bdc-abc8-22aaf5a1bd49",
129-
"predecessor": "a93544bc-c165-4af4-b55e-18f3593b457a",
130-
"defaultDuration": 777600
115+
"defaultDuration": 518400
131116
},
132117
{
133118
"phaseId": "d8a2cdbe-84d1-4687-ab75-78a6a7efdcc8",
134-
"predecessor": "6950164f-3c5e-4bdc-abc8-22aaf5a1bd49",
135-
"defaultDuration": 302400
119+
"defaultDuration": 259200
136120
},
137121
{
138122
"phaseId": "ce1afb4c-74f9-496b-9e4b-087ae73ab032",
@@ -142,31 +126,21 @@
142126
{
143127
"phaseId": "84b43897-2aab-44d6-a95a-42c433657eed",
144128
"predecessor": "ce1afb4c-74f9-496b-9e4b-087ae73ab032",
145-
"defaultDuration": 302400
129+
"defaultDuration": 172800
146130
},
147131
{
148132
"phaseId": "2d7d3d85-0b29-4989-b3b4-be7f2b1d0aa6",
149-
"predecessor": "84b43897-2aab-44d6-a95a-42c433657eed",
150-
"defaultDuration": 43200
133+
"predecessor": "6950164f-3c5e-4bdc-abc8-22aaf5a1bd49",
134+
"defaultDuration": 14400
151135
},
152136
{
153137
"phaseId": "aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b",
154138
"predecessor": "2d7d3d85-0b29-4989-b3b4-be7f2b1d0aa6",
155139
"defaultDuration": 432000
156140
},
157-
{
158-
"phaseId": "3e2afca6-9542-4763-a135-96b33f12c082",
159-
"predecessor": "aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b",
160-
"defaultDuration": 172800
161-
},
162-
{
163-
"phaseId": "f3acaf26-1dd5-42ae-9f0d-8eb0fd24ae59",
164-
"predecessor": "3e2afca6-9542-4763-a135-96b33f12c082",
165-
"defaultDuration": 86400
166-
},
167141
{
168142
"phaseId": "ad985cff-ad3e-44de-b54e-3992505ba0ae",
169-
"predecessor": "f3acaf26-1dd5-42ae-9f0d-8eb0fd24ae59",
143+
"predecessor": "aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b",
170144
"defaultDuration": 172800
171145
}
172146
]
@@ -183,7 +157,6 @@
183157
},
184158
{
185159
"phaseId": "6950164f-3c5e-4bdc-abc8-22aaf5a1bd49",
186-
"predecessor": "a93544bc-c165-4af4-b55e-18f3593b457a",
187160
"defaultDuration": 172800
188161
},
189162
{

0 commit comments

Comments
 (0)