-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
API for viewing submissions and leaderboard (#90)
* API for viewing submissions and leaderboard * Remove duplicate check for contest moderator in getAllSubmissions API Co-authored-by: ridhishjain <[email protected]>
- Loading branch information
1 parent
9038986
commit aea784a
Showing
19 changed files
with
1,002 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
const { pool } = require('../database') | ||
|
||
/** | ||
* | ||
* @param {Array} filterParams | ||
* @return {String} | ||
*/ | ||
function filterQuery(filterParams) { | ||
if (!Array.isArray(filterParams)) { | ||
return '' | ||
} | ||
const index = filterParams.find((param) => param.id === 'type') | ||
if (index) { | ||
filterParams.push(filterParams.splice(index, 1)[0]) | ||
} | ||
return filterParams | ||
.map(({ id, value }) => | ||
value.length | ||
? ` ${id === 'type' ? 'HAVING' : 'AND'} ${id} IN (${value | ||
.map((val) => (typeof val === 'string' ? `'${val}'` : val)) | ||
.join(',')}) ` | ||
: '' | ||
) | ||
.join('') | ||
} | ||
|
||
/** | ||
* | ||
* @param {Array} sortParams | ||
* @return {String} | ||
*/ | ||
function sortQuery(sortParams) { | ||
if (!Array.isArray(sortParams)) { | ||
return '' | ||
} | ||
return ( | ||
' ORDER BY ' + | ||
sortParams | ||
.map(({ id, desc }) => `${id} ${desc ? 'DESC' : 'ASC'}`) | ||
.join(', ') | ||
) | ||
} | ||
|
||
/** | ||
* @param {*} param0 | ||
* @param {String} param0.username | ||
* @param {Object} param0.params | ||
* @param {Object} param0.query | ||
* @return {Promise} | ||
*/ | ||
|
||
function getAllSubmissions({ username, params, query }) { | ||
return new Promise((resolve, reject) => { | ||
const { contestId } = params | ||
const { | ||
search, | ||
sort, | ||
page, | ||
size, | ||
download_csv: downloadCSV, | ||
filters, | ||
} = query | ||
|
||
let countQuery = `SELECT COUNT(*) AS total FROM | ||
(SELECT id, 'subjective' AS type FROM subjective_submissions WHERE contest_id=? ` | ||
let mainQuery = `SELECT s.id, s.question_id, q.name, s.type, s.username, s.submission_time, s.score, s.judged FROM | ||
(SELECT id, question_id, 'subjective' AS type, username, submission_time, score, judged FROM subjective_submissions | ||
WHERE contest_id=? ` | ||
|
||
if (search) { | ||
countQuery += ` AND username LIKE '${search}%' ` | ||
mainQuery += ` AND username LIKE '${search}%' ` | ||
} | ||
|
||
if (filters) { | ||
countQuery += filterQuery(JSON.parse(filters)) | ||
mainQuery += filterQuery(JSON.parse(filters)) | ||
} | ||
|
||
countQuery += ` UNION ALL SELECT id, 'mcq' AS type FROM mcq_submissions WHERE contest_id=? ` | ||
|
||
mainQuery += ` UNION ALL SELECT id, question_id, 'mcq' AS type, username, submission_time, score, judged FROM mcq_submissions | ||
WHERE contest_id=? ` | ||
const mainQueryArr = [contestId, contestId] | ||
|
||
if (search) { | ||
countQuery += ` AND username LIKE '${search}%' ` | ||
mainQuery += ` AND username LIKE '${search}%' ` | ||
} | ||
|
||
if (filters) { | ||
countQuery += filterQuery(JSON.parse(filters)) | ||
mainQuery += filterQuery(JSON.parse(filters)) | ||
} | ||
|
||
countQuery += `) s WHERE EXISTS (SELECT 1 FROM contests_moderators WHERE contest_id=? AND moderator=?) ` | ||
mainQuery += `) s INNER JOIN questions q ON q.id=s.question_id ` | ||
|
||
const countQueryArr = [contestId, contestId, contestId, username] | ||
|
||
if (sort) { | ||
mainQuery += sortQuery(JSON.parse(sort)) | ||
} | ||
|
||
pool.query(countQuery, countQueryArr, (error, countResults) => { | ||
if (error || !Array.isArray(countResults) || !countResults.length) { | ||
reject(error || 'Invalid Query') | ||
} | ||
|
||
const totalRows = countResults[0].total | ||
if (!totalRows) { | ||
return resolve({ submissions: [], page_count: totalRows }) | ||
} | ||
const pageSize = downloadCSV ? totalRows : parseInt(size, 10) || 10 | ||
const numPages = Math.ceil(totalRows / pageSize) | ||
const pageIndex = downloadCSV ? 0 : parseInt(page, 10) || 0 | ||
const offset = pageSize * pageIndex | ||
|
||
mainQuery += ` LIMIT ?,?` | ||
mainQueryArr.push(offset, pageSize) | ||
pool.query(mainQuery, mainQueryArr, (error, results) => { | ||
if (error || results === undefined) { | ||
return reject(error) | ||
} | ||
return resolve({ submissions: results, page_count: numPages }) | ||
}) | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = getAllSubmissions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
const { pool } = require('../database') | ||
|
||
/** | ||
* | ||
* @param {Array} filterParams | ||
* @return {String} | ||
*/ | ||
function filterQuery(filterParams) { | ||
if (!Array.isArray(filterParams)) { | ||
return '' | ||
} | ||
return filterParams | ||
.map(({ id, value }) => | ||
value.length | ||
? ` AND ${id} IN (${value | ||
.map((val) => (typeof val === 'string' ? `'${val}'` : val)) | ||
.join(',')}) ` | ||
: '' | ||
) | ||
.join('') | ||
} | ||
|
||
/** | ||
* | ||
* @param {Array} sortParams | ||
* @return {String} | ||
*/ | ||
function sortQuery(sortParams) { | ||
if (!Array.isArray(sortParams)) { | ||
return '' | ||
} | ||
return ( | ||
' ORDER BY ' + | ||
sortParams | ||
.map( | ||
({ id, desc }) => | ||
`${id === 'rank' ? '`rank`' : id} ${desc ? 'DESC' : 'ASC'}` | ||
) | ||
.join(', ') | ||
) | ||
} | ||
|
||
/** | ||
* @param {*} param0 | ||
* @param {String} param0.username | ||
* @param {Object} param0.params | ||
* @param {Object} param0.query | ||
* @return {Promise} | ||
*/ | ||
|
||
function getContestLeaderboard({ username, params, query }) { | ||
return new Promise((resolve, reject) => { | ||
const { contestId } = params | ||
const { | ||
search, | ||
sort, | ||
page, | ||
size, | ||
download_csv: downloadCSV, | ||
filters, | ||
} = query | ||
|
||
let countQuery = `SELECT COUNT(*) AS total | ||
FROM leaderboard l | ||
INNER JOIN users u ON l.username=u.username | ||
WHERE contest_id=? AND | ||
(EXISTS (SELECT 1 FROM contests_moderators WHERE contest_id=? AND moderator=?) | ||
OR EXISTS(SELECT 1 FROM contests WHERE id=? AND show_leaderboard=1)) ` | ||
const countQueryArr = [contestId, contestId, username, contestId] | ||
|
||
let mainQuery = `SELECT u.username, full_name, attempted_count, total_score, total_time, | ||
RANK() OVER (PARTITION BY contest_id ORDER BY total_score DESC, total_time ASC) \`rank\` FROM leaderboard l | ||
INNER JOIN users u ON l.username=u.username | ||
WHERE contest_id=? ` | ||
const mainQueryArr = [contestId] | ||
|
||
if (search) { | ||
countQuery += ` AND (u.username LIKE '${search}%' OR u.full_name LIKE '${search}%' OR u.admission_number LIKE '${search}%') ` | ||
mainQuery += ` AND (u.username LIKE '${search}%' OR u.full_name LIKE '${search}%' OR u.admission_number LIKE '${search}%') ` | ||
} | ||
|
||
if (filters) { | ||
countQuery += filterQuery(JSON.parse(filters)) | ||
mainQuery += filterQuery(JSON.parse(filters)) | ||
} | ||
|
||
if (sort) { | ||
mainQuery += sortQuery(JSON.parse(sort)) | ||
} | ||
|
||
pool.query(countQuery, countQueryArr, (error, countResults) => { | ||
if (error || !Array.isArray(countResults) || !countResults.length) { | ||
reject(error || 'Invalid Query') | ||
} | ||
|
||
const totalRows = countResults[0].total | ||
if (!totalRows) { | ||
return resolve({ leaderboard: [], page_count: totalRows }) | ||
} | ||
const pageSize = downloadCSV ? totalRows : parseInt(size, 10) || 10 | ||
const numPages = Math.ceil(totalRows / pageSize) | ||
const pageIndex = downloadCSV ? 0 : parseInt(page, 10) || 0 | ||
const offset = pageSize * pageIndex | ||
|
||
mainQuery += ` LIMIT ?,?` | ||
mainQueryArr.push(offset, pageSize) | ||
pool.query(mainQuery, mainQueryArr, (error, results) => { | ||
if (error || results === undefined) { | ||
return reject(error) | ||
} | ||
return resolve({ leaderboard: results, page_count: numPages }) | ||
}) | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = getContestLeaderboard |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
const { pool } = require('../database') | ||
|
||
/** | ||
* @param {*} param0 | ||
* @param {String} param0.username | ||
* @param {Object} param0.params | ||
* @param {Object} param0.query | ||
* @return {Promise} | ||
*/ | ||
|
||
function getMCQSubmission({ username, params, query }) { | ||
return new Promise((resolve, reject) => { | ||
const { contestId, submissionId } = params | ||
const { moderator } = query | ||
let queryString, queryArr | ||
if (moderator) { | ||
queryString = `SELECT question_id, contest_id, username, response, submission_time, score, judged | ||
FROM mcq_submissions | ||
WHERE id=? AND EXISTS(SELECT 1 FROM contests_moderators WHERE contest_id=? AND moderator=?)` | ||
queryArr = [submissionId, contestId, username] | ||
} else { | ||
queryString = `SELECT question_id, contest_id, username, response, submission_time, | ||
score*EXISTS(SELECT 1 FROM contests WHERE id=? AND (NOW()>end_time OR show_leaderboard=1)) AS score, judged | ||
FROM mcq_submissions | ||
WHERE id=? AND username=? AND EXISTS(SELECT 1 FROM contests WHERE id=? | ||
AND ((NOW()>=start_time AND NOW()<=end_time) | ||
OR (NOW()>end_time AND confidential_questions=0)))` | ||
queryArr = [contestId, submissionId, username, contestId] | ||
} | ||
|
||
pool.query(queryString, queryArr, (error, results) => { | ||
if (error) { | ||
reject(error) | ||
} | ||
if (!results.length) { | ||
reject('You do not have permission to view this submission') | ||
} | ||
return resolve(results) | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = getMCQSubmission |
Oops, something went wrong.