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

Issue 19 #20

Merged
merged 3 commits into from
Nov 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Dependencies

- nodejs (v10)
- Nodejs (v10)
- Mongodb (v4)

## Configuration
Expand Down Expand Up @@ -54,6 +54,7 @@ npm start
```

## Mock API

For verification purpose, we need a mock app for Topcoder Challenge API and Topcoder Member API. You can run command `npm run mock-api` to start the mock app.

## Heroku Deployment
Expand Down
2 changes: 1 addition & 1 deletion config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
PORT: process.env.PORT || 3000,

MONGODB_URL: process.env.MONGODB_URL || 'mongodb://localhost:27017/leaderboardDB',
CHALLENGE_API_URL: process.env.CHALLENGE_API_URL || 'https://api.topcoder-dev.com/v3/challenges',
CHALLENGE_API_URL: process.env.CHALLENGE_API_URL || 'https://api.topcoder-dev.com/v4/challenges',
MEMBER_API_URL: process.env.MEMBER_API_URL || 'https://api.topcoder-dev.com/v3/users',

GROUP_IDS: process.env.GROUP_IDS || '202343,20000000', // Comma separated string of Group IDs
Expand Down
2 changes: 1 addition & 1 deletion config/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

module.exports = {
CHALLENGE_API_URL: 'https://api.topcoder-dev.com/v3/challenges',
CHALLENGE_API_URL: 'https://api.topcoder-dev.com/v4/challenges',
MEMBER_API_URL: 'https://api.topcoder-dev.com/v3/users',
MOCK_API_PORT: 3001
}
20 changes: 10 additions & 10 deletions docs/Leaderboard API.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "85361413-4f1c-4fb2-9433-01dc999d7e4b",
"_postman_id": "adfe8d84-410e-4628-9a1c-f74e16e89c83",
"name": "Leaderboard API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
Expand Down Expand Up @@ -180,7 +180,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"submissionId\": \"261d3655-9c80-4f90-8051-e209e8c21701\",\n \"metadata\": {\n \"assertions\": {\n \"pending\": 0,\n \"failed\": 1,\n \"total\": 10\n },\n \"tests\": {\n \"total\": 10\n }\n },\n \"aggregateScore\": 90\n}",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"submissionId\": \"261d3655-9c80-4f90-8051-e209e8c21701\",\n \"metadata\": {\n \"tests\": {\n \"pending\": 0,\n \"failed\": 1,\n \"total\": 10\n }\n },\n \"score\": 90\n}",
"options": {
"raw": {
"language": "json"
Expand Down Expand Up @@ -217,7 +217,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"submissionId\": \"261d3655-9c80-4f90-8051-e209e8c21701\",\n \"aggregateScore\": 0\n}",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"submissionId\": \"261d3655-9c80-4f90-8051-e209e8c21701\",\n \"score\": 0\n}",
"options": {
"raw": {
"language": "json"
Expand Down Expand Up @@ -254,7 +254,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"submissionId\": \"261d3655-9c80-4f90-8051-e209e8c21701\",\n \"aggregateScore\": 0\n}",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"submissionId\": \"261d3655-9c80-4f90-8051-e209e8c21701\",\n \"score\": 0\n}",
"options": {
"raw": {
"language": "json"
Expand Down Expand Up @@ -296,7 +296,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"metadata\": {\n \"assertions\": {\n \"pending\": 0,\n \"failed\": 1,\n \"total\": 5\n },\n \"tests\": {\n \"total\": 5\n }\n },\n \"aggregateScore\": 80\n}"
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"metadata\": {\n \"test\": {\n \"pending\": 0,\n \"failed\": 1,\n \"total\": 5\n }\n },\n \"score\": 80\n}"
},
"url": {
"raw": "{{URL}}/leaderboard/challenge/30051825/member/8547899",
Expand Down Expand Up @@ -327,7 +327,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"aggregateScore\": 80\n}"
"raw": "{\n\t\"id\": \"161d3655-9c80-4f90-8051-e209e8c21701\",\n \"score\": 80\n}"
},
"url": {
"raw": "{{URL}}/leaderboard/challenge/30051825/member/5547899",
Expand Down Expand Up @@ -388,13 +388,13 @@
"method": "DELETE",
"header": [],
"url": {
"raw": "{{URL}}/leaderboard/reviewSummation/661d3655-9c80-4f90-8051-e209e8c21704",
"raw": "{{URL}}/leaderboard/review/661d3655-9c80-4f90-8051-e209e8c21704",
"host": [
"{{URL}}"
],
"path": [
"leaderboard",
"reviewSummation",
"review",
"661d3655-9c80-4f90-8051-e209e8c21704"
]
}
Expand All @@ -407,13 +407,13 @@
"method": "DELETE",
"header": [],
"url": {
"raw": "{{URL}}/leaderboard/reviewSummation/231d3655-9c80-4f90-8051-e209e8c21704",
"raw": "{{URL}}/leaderboard/review/231d3655-9c80-4f90-8051-e209e8c21704",
"host": [
"{{URL}}"
],
"path": [
"leaderboard",
"reviewSummation",
"review",
"231d3655-9c80-4f90-8051-e209e8c21704"
]
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"mock-api": "NODE_ENV=test node test/common/mock.js",
"lint": "standard",
"lint:fix": "standard --fix",
"test": "nyc --reporter=html --reporter=text mocha test/unit/test.js --require test/common/prepare.js --exit",
"e2e": "nyc --reporter=html --reporter=text mocha test/e2e/test.js --require test/common/prepare.js --exit"
"test": "nyc --reporter=html --reporter=text mocha test/unit/test.js --timeout=20000 --require test/common/prepare.js --exit",
"e2e": "nyc --reporter=html --reporter=text mocha test/e2e/test.js --timeout=20000 --require test/common/prepare.js --exit"
},
"author": "TCSCODER",
"license": "none",
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/LeaderboardController.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ async function updateLeaderboard (req, res) {
}

/**
* Delete leaderboard detail by review summation id
* Delete leaderboard detail by review id
* @param req the request
* @param res the response
*/
async function deleteLeaderboard (req, res) {
await LeaderboardService.deleteLeaderboard(req.params.reviewSummationId)
await LeaderboardService.deleteLeaderboard(req.params.reviewId)
res.status(204).end()
}

Expand Down
2 changes: 1 addition & 1 deletion src/models/Leaderboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
const Schema = require('mongoose').Schema

const LeaderboardSchema = new Schema({
reviewSummationId: { type: String },
reviewId: { type: String },
submissionId: { type: String },
challengeId: { type: String },
memberId: { type: String },
Expand Down
2 changes: 1 addition & 1 deletion src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = {
method: 'updateLeaderboard'
}
},
'/leaderboard/reviewSummation/:reviewSummationId': {
'/leaderboard/review/:reviewId': {
delete: {
controller: 'LeaderboardController',
method: 'deleteLeaderboard'
Expand Down
66 changes: 33 additions & 33 deletions src/services/LeaderboardService.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function getLeaderboard (challengeId, memberId) {
* @returns {Number} the test passed number
*/
function getTestsPassed (metadata) {
const tests = metadata.tests || { total: 0, pending: 0, failed: 0}
const tests = metadata.tests || { total: 0, pending: 0, failed: 0 }

let testsPassed = tests.total - tests.pending - tests.failed

Expand All @@ -42,35 +42,35 @@ function getTestsPassed (metadata) {
/**
* Calculate test passed number and total test cases
*
* @param {Object} reviewSummation the review summation
* @param {Object} review the review
* @return {Object} testsPassed and totalTestCases
*/
function calculateResult (reviewSummation) {
function calculateResult (review) {
let testsPassed = 0
let totalTestCases = 0

if (reviewSummation.metadata) {
testsPassed = getTestsPassed(reviewSummation.metadata)
totalTestCases = _.get(reviewSummation, 'metadata.tests.total', 0)
if (review.metadata) {
testsPassed = getTestsPassed(review.metadata)
totalTestCases = _.get(review, 'metadata.tests.total', 0)
}
return { testsPassed, totalTestCases }
}

/**
* Create leaderboard using review summation data
* Create leaderboard using review data
*
* @param {String} challengeId the challenge id
* @param {String} memberId the member id
* @param {Object} reviewSummation the review summation data
* @param {Object} review the review data
* @returns {Object} the created leaderboard
*/
async function createLeaderboard (challengeId, memberId, reviewSummation) {
async function createLeaderboard (challengeId, memberId, review) {
const existRecords = await getLeaderboard(challengeId, memberId)
if (existRecords.length > 0) {
throw new errors.ConflictError(`Leaderboard record with challenge # ${challengeId} and member # ${memberId} already exists.`)
}

const { testsPassed, totalTestCases } = calculateResult(reviewSummation)
const { testsPassed, totalTestCases } = calculateResult(review)

const challengeDetailRes = await helper.reqToAPI(
`${config.CHALLENGE_API_URL}?filter=id=${challengeId}`)
Expand All @@ -94,12 +94,12 @@ async function createLeaderboard (challengeId, memberId, reviewSummation) {

// Record to be written into MongoDB
const record = {
reviewSummationId: reviewSummation.id,
submissionId: reviewSummation.submissionId,
reviewId: review.id,
submissionId: review.submissionId,
memberId,
challengeId,
handle: member.handle,
aggregateScore: reviewSummation.aggregateScore,
aggregateScore: review.score, // For TCO scenario, we will only have 1 review - per member and per challenge
testsPassed,
totalTestCases,
groupIds: _.map(groupIds, e => String(e))
Expand All @@ -111,40 +111,40 @@ async function createLeaderboard (challengeId, memberId, reviewSummation) {
createLeaderboard.schema = {
challengeId: joi.string().required(),
memberId: joi.string().required(),
reviewSummation: joi.object().keys({
review: joi.object().keys({
id: joi.string().required(),
submissionId: joi.string().required(),
aggregateScore: joi.number().required()
score: joi.number().required()
}).unknown(true).required()
}

/**
* Update leaderboard detail using review summation data
* Update leaderboard detail using review data
*
* @param {String} challengeId the challenge id
* @param {String} memberId the member id
* @param {Object} reviewSummation the review summation data
* @param {Object} review the review data
* @returns the updated leaderboard detail
*/
async function updateLeaderboard (challengeId, memberId, reviewSummation) {
async function updateLeaderboard (challengeId, memberId, review) {
const existRecords = await getLeaderboard(challengeId, memberId)
if (existRecords.length === 0) {
throw new errors.NotFoundError(`Leaderboard record with challenge # ${challengeId} and member # ${memberId} doesn't exist`)
}

let scoreLevel = 'na'

const { testsPassed, totalTestCases } = calculateResult(reviewSummation)
const { testsPassed, totalTestCases } = calculateResult(review)

if (existRecords[0].aggregateScore > reviewSummation.aggregateScore) {
scoreLevel = 'down';
} else if (existRecords[0].aggregateScore < reviewSummation.aggregateScore) {
scoreLevel = 'up';
if (existRecords[0].aggregateScore > review.score) {
scoreLevel = 'down'
} else if (existRecords[0].aggregateScore < review.score) {
scoreLevel = 'up'
}

_.assignIn(existRecords[0], {
aggregateScore: reviewSummation.aggregateScore,
reviewSummationId: reviewSummation.id,
aggregateScore: review.score,
reviewId: review.id,
testsPassed,
totalTestCases,
scoreLevel
Expand All @@ -156,9 +156,9 @@ async function updateLeaderboard (challengeId, memberId, reviewSummation) {
updateLeaderboard.schema = {
challengeId: joi.string().required(),
memberId: joi.string().required(),
reviewSummation: joi.object().keys({
review: joi.object().keys({
id: joi.string().required(),
aggregateScore: joi.number().required()
score: joi.number().required()
}).unknown(true).required()
}

Expand Down Expand Up @@ -222,20 +222,20 @@ searchLeaderboards.schema = {
}

/**
* Delete leaderboard by review summation id.
* Delete leaderboard by review id.
*
* @param {String} reviewSummationId the review summation id
* @param {String} reviewId the review id
*/
async function deleteLeaderboard (reviewSummationId) {
const entity = await Leaderboard.findOne({ reviewSummationId })
async function deleteLeaderboard (reviewId) {
const entity = await Leaderboard.findOne({ reviewId })
if (!entity) {
throw new errors.NotFoundError(`Leaderboard record with reviewSummation ID: ${reviewSummationId} doesn't exist`)
throw new errors.NotFoundError(`Leaderboard record with review id: ${reviewId} doesn't exist`)
}
await entity.remove()
}

deleteLeaderboard.schema = {
reviewSummationId: joi.string().required()
reviewId: joi.string().required()
}

module.exports = {
Expand Down
14 changes: 7 additions & 7 deletions src/test-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const logger = require('./common/logger')

const insertData = async () => {
await Leaderboard.create({
reviewSummationId: '661d3655-9c80-4f90-8051-e209e8c21704',
reviewId: '661d3655-9c80-4f90-8051-e209e8c21704',
submissionId: '2b5e54b9-f03c-418b-92f3-5f072b0f3bf4',
challengeId: '30104644',
memberId: '123456',
Expand All @@ -18,7 +18,7 @@ const insertData = async () => {
groupIds: ['100', '200']
})
await Leaderboard.create({
reviewSummationId: '661d3655-9c80-4f90-8051-e209e8c21705',
reviewId: '661d3655-9c80-4f90-8051-e209e8c21705',
submissionId: '2b5e54b9-f03c-418b-92f3-5f072b0f3bf5',
challengeId: '30104644',
memberId: '123457',
Expand All @@ -29,7 +29,7 @@ const insertData = async () => {
groupIds: ['100', '200']
})
await Leaderboard.create({
reviewSummationId: '661d3655-9c80-4f90-8051-e209e8c21706',
reviewId: '661d3655-9c80-4f90-8051-e209e8c21706',
submissionId: '2b5e54b9-f03c-418b-92f3-5f072b0f3bf6',
challengeId: '30104644',
memberId: '123458',
Expand All @@ -40,7 +40,7 @@ const insertData = async () => {
groupIds: ['100', '200']
})
await Leaderboard.create({
reviewSummationId: '661d3655-9c80-4f90-8051-e209e8c21701',
reviewId: '661d3655-9c80-4f90-8051-e209e8c21701',
submissionId: '2b5e54b9-f03c-418b-92f3-5f072b0f3bf1',
challengeId: '30104645',
memberId: '123456',
Expand All @@ -51,7 +51,7 @@ const insertData = async () => {
groupIds: ['100', '200']
})
await Leaderboard.create({
reviewSummationId: '661d3655-9c80-4f90-8051-e209e8c21702',
reviewId: '661d3655-9c80-4f90-8051-e209e8c21702',
submissionId: '2b5e54b9-f03c-418b-92f3-5f072b0f3bf2',
challengeId: '30104645',
memberId: '123457',
Expand All @@ -62,7 +62,7 @@ const insertData = async () => {
groupIds: ['100', '200']
})
await Leaderboard.create({
reviewSummationId: '661d3655-9c80-4f90-8051-e209e8c21703',
reviewId: '661d3655-9c80-4f90-8051-e209e8c21703',
submissionId: '2b5e54b9-f03c-418b-92f3-5f072b0f3bf3',
challengeId: '30104646',
memberId: '123456',
Expand All @@ -73,7 +73,7 @@ const insertData = async () => {
groupIds: ['100']
})
await Leaderboard.create({
reviewSummationId: '661d3655-9c80-4f90-8051-e209e8c21709',
reviewId: '661d3655-9c80-4f90-8051-e209e8c21709',
submissionId: '2b5e54b9-f03c-418b-92f3-5f072b0f3bf9',
challengeId: '30104646',
memberId: '123458',
Expand Down
Loading