Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 316521d

Browse files
dhruvit-rsharathkumaranbu
authored andcommitted
Stream downloads instead of saving to a buffer (#23)
Support streaming downloads and fixing vulnerabilities
1 parent c1a95e6 commit 316521d

File tree

4 files changed

+57
-31
lines changed

4 files changed

+57
-31
lines changed

bin/topcoder-cli.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ program
4141
` "username": "<Topcoder username>",\n` +
4242
` "password": "<Topcoder password>",\n` +
4343
` "m2m": {\n` +
44-
` client_id: "<Client ID for M2M authentication>",\n` +
45-
` client_secret: "<Client Secret for M2M authentication>"\n` +
44+
` "client_id": "<Client ID for M2M authentication>",\n` +
45+
` "client_secret": "<Client Secret for M2M authentication>"\n` +
4646
` }\n` +
4747
`}\n` +
4848
`and execute command \`topcoder submit\` to submit the contents of ` +
@@ -72,8 +72,8 @@ program
7272
'Challenge ID for submissions to be fetched'
7373
)
7474
.option(
75-
'-u, --userId <id>',
76-
'Fetch only the submission of for a particular user id'
75+
'-m, --memberId <id>',
76+
'Fetch only the submission of for a particular member id'
7777
)
7878
.option(
7979
'-s, --submissionId <id>',
@@ -83,25 +83,25 @@ program
8383
.option('--dev', 'Points to Topcoder development environment')
8484
.on('--help', () => {
8585
console.log(
86-
`\nUse CLI parameters or create a file .topcoderrc in JSON format with below details` +
86+
`\nUse CLI parameters or create a file .topcoderrc in JSON format with below details\n` +
8787
`{\n` +
88-
` "userId": "<Topcoder memberId",\n` +
88+
` "memberId": "<Topcoder memberId",\n` +
8989
` "challengeId": "<Topcoder challengeId",\n` +
9090
` "submissionId": "<Topcoder submissionId",\n` +
9191
` "latest": true,\n` +
9292
` "username": "<Topcoder username>",\n` +
9393
` "password": "<Topcoder password>",\n` +
9494
` "m2m": {\n` +
95-
` client_id: "<Client ID for M2M authentication>",\n` +
96-
` client_secret: "<Client Secret for M2M authentication>"\n` +
95+
` "client_id": "<Client ID for M2M authentication>",\n` +
96+
` "client_secret": "<Client Secret for M2M authentication>"\n` +
9797
` }\n` +
9898
`}\n` +
9999
`and execute command \`topcoder fetch-submissions\` to fetch submissions ` +
100100
`for a challenge and save them.\n` +
101101
`You may specify the m2m config or the username and password config, ` +
102102
`but not both.\n` +
103103
`If the submissionId parameter is provided, you must not provide the ` +
104-
`userId or the latest parameters.\n` +
104+
`memberId or the latest parameters.\n` +
105105
`The challengeId parameter is always required.`
106106
)
107107
})
@@ -140,8 +140,8 @@ program
140140
` "username": "<Topcoder username>",\n` +
141141
` "password": "<Topcoder password>",\n` +
142142
` "m2m": {\n` +
143-
` client_id: "<Client ID for M2M authentication>",\n` +
144-
` client_secret: "<Client Secret for M2M authentication>"\n` +
143+
` "client_id": "<Client ID for M2M authentication>",\n` +
144+
` "client_secret": "<Client Secret for M2M authentication>"\n` +
145145
` }\n` +
146146
`}\n` +
147147
`and execute command \`topcoder fetch-artifacts\` to fetch submissions for` +

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@hapi/joi": "^16.1.8",
15-
"@topcoder-platform/topcoder-submission-api-wrapper": "^1.1.0",
15+
"@topcoder-platform/topcoder-submission-api-wrapper": "^1.2.0",
1616
"adm-zip": "^0.4.11",
1717
"commander": "^3.0.0",
1818
"content-disposition": "^0.5.3",
@@ -22,10 +22,8 @@
2222
"ini": "^1.3.5",
2323
"lodash": "^4.17.15",
2424
"moment": "^2.24.0",
25-
"progress": "^2.0.3",
2625
"prompts": "^2.2.1",
2726
"superagent": "^5.0.5",
28-
"tc-core-library-js": "https://github.com/appirio-tech/tc-core-library-js/archive/v2.6.3.tar.gz",
2927
"winston": "^3.2.1"
3028
},
3129
"bin": {

src/services/fetchArtifactsService.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,32 @@ async function downloadArtifacts (submissionId, artifacts, savePath) {
3838
)
3939
try {
4040
// Download the artifact
41-
const req = await submissionApiClient.downloadArtifact(
41+
let req = await submissionApiClient.downloadArtifact(
4242
submissionId,
43-
artifactId
43+
artifactId,
44+
null,
45+
true
4446
)
47+
// Get the temporary path
48+
const temporaryFilePath = path.join(savePath, `artifact-${artifactId}.tcdownload`)
49+
// Save the file
50+
const fStream = fs.createWriteStream(temporaryFilePath)
51+
const writeStream = req.pipe(fStream)
52+
// Wait for write to complete
53+
await new Promise((resolve, reject) => {
54+
req.on('response', (_req) => {
55+
req = _req
56+
})
57+
writeStream.on('finish', resolve)
58+
writeStream.on('error', reject)
59+
})
4560
// Get file name from headers
4661
const disposition = _.get(req, 'headers.content-disposition')
4762
const fileName = contentDisposition.parse(disposition).parameters.filename
48-
// Get file path
49-
const filePath = path.join(savePath, `${fileName}`)
63+
// Get the final file path
64+
const filePath = path.join(savePath, fileName)
5065
// Save the file
51-
await fs.writeFile(filePath, req.body)
66+
await fs.move(temporaryFilePath, filePath)
5267
// Log the result
5368
logger.info(
5469
`[${idx + 1}/${artifacts.length}] ` +

src/services/fetchSubmissionService.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ let submissionApiClient
1414
const schemaForRC = helper.defaultAuthSchema
1515
.keys({
1616
challengeId: Joi.string().required(),
17-
userId: Joi.number().integer(),
17+
memberId: Joi.number().integer(),
1818
submissionId: Joi.string(),
1919
latest: Joi.boolean()
2020
})
21-
.without('submissionId', ['userId', 'latest'])
22-
.without('userId', 'submissionId')
21+
.without('submissionId', ['memberId', 'latest'])
22+
.without('memberId', 'submissionId')
2323
.without('latest', 'submissionId')
2424
.unknown()
2525

2626
// Acceptable CLI params
27-
const validCLIParams = ['challengeId', 'userId', 'submissionId', 'latest']
27+
const validCLIParams = ['challengeId', 'memberId', 'submissionId', 'latest']
2828

2929
/**
3030
* Download the submissions to the savePath directory sequentially.s
@@ -43,14 +43,27 @@ async function downloadSubmissions (submissions, savePath) {
4343
)
4444
try {
4545
// Download the submission
46-
const req = await submissionApiClient.downloadSubmission(submission.id)
46+
let req = await submissionApiClient.downloadSubmission(submission.id, null, true)
47+
// Get the temporary path
48+
const temporaryFilePath = path.join(savePath, `submission-${submission.id}.tcdownload`)
49+
// Save the file
50+
const fStream = fs.createWriteStream(temporaryFilePath)
51+
const writeStream = req.pipe(fStream)
52+
// Wait for write to complete
53+
await new Promise((resolve, reject) => {
54+
req.on('response', (_req) => {
55+
req = _req
56+
})
57+
writeStream.on('finish', resolve)
58+
writeStream.on('error', reject)
59+
})
4760
// Get file name from headers
4861
const disposition = _.get(req, 'headers.content-disposition')
4962
const fileName = contentDisposition.parse(disposition).parameters.filename
50-
// Get file path
51-
const filePath = path.join(savePath, `${fileName}`)
63+
// Get the final file path
64+
const filePath = path.join(savePath, fileName)
5265
// Save the file
53-
await fs.writeFile(filePath, req.body)
66+
await fs.move(temporaryFilePath, filePath)
5467
// Log the result
5568
logger.info(
5669
`[${idx + 1}/${submissions.length}] ` +
@@ -79,7 +92,7 @@ async function fetchSubmissions (currDir, cliParams) {
7992
password,
8093
m2m,
8194
challengeId,
82-
userId,
95+
memberId,
8396
submissionId,
8497
latest
8598
} = await helper.readFromRCFile(rcPath, params, schemaForRC, validCLIParams)
@@ -116,9 +129,9 @@ async function fetchSubmissions (currDir, cliParams) {
116129
perPage: 100,
117130
page
118131
}
119-
// If there's a userId specified, filter by userId.
120-
if (userId) {
121-
query.memberId = userId
132+
// If there's a memberId specified, filter by memberId.
133+
if (memberId) {
134+
query.memberId = memberId
122135
}
123136
// Get a list of submissions
124137
const nextSubmissions = await submissionApiClient.searchSubmissions(query)

0 commit comments

Comments
 (0)