diff --git a/server/models/submissions/getAllSubmissions.js b/server/models/submissions/getAllSubmissions.js new file mode 100644 index 0000000..d78e169 --- /dev/null +++ b/server/models/submissions/getAllSubmissions.js @@ -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 diff --git a/server/models/submissions/getContestLeaderboard.js b/server/models/submissions/getContestLeaderboard.js new file mode 100644 index 0000000..c7d69bc --- /dev/null +++ b/server/models/submissions/getContestLeaderboard.js @@ -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 diff --git a/server/models/submissions/getMCQSubmission.js b/server/models/submissions/getMCQSubmission.js new file mode 100644 index 0000000..251f79a --- /dev/null +++ b/server/models/submissions/getMCQSubmission.js @@ -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 diff --git a/server/models/submissions/getQuestionLeaderboard.js b/server/models/submissions/getQuestionLeaderboard.js new file mode 100644 index 0000000..4332cb9 --- /dev/null +++ b/server/models/submissions/getQuestionLeaderboard.js @@ -0,0 +1,125 @@ +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 getQuestionLeaderboard({ username, params, query }) { + return new Promise((resolve, reject) => { + const { contestId, questionId } = params + const { + type, + search, + sort, + page, + size, + download_csv: downloadCSV, + filters, + } = query + + let countQuery = `SELECT COUNT(*) AS total + FROM ${type === 'mcq' ? 'mcq_submissions' : 'subjective_submissions'} s + INNER JOIN users u ON s.username=u.username + WHERE contest_id=? AND question_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, + questionId, + contestId, + username, + contestId, + ] + + let mainQuery = `SELECT u.username, full_name, score, submission_time, judged, + RANK() OVER (PARTITION BY contest_id, question_id ORDER BY score DESC, submission_time ASC) \`rank\` + FROM ${type === 'mcq' ? 'mcq_submissions' : 'subjective_submissions'} s + INNER JOIN users u ON s.username=u.username + WHERE contest_id=? AND question_id=?` + const mainQueryArr = [contestId, questionId] + + 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 = getQuestionLeaderboard diff --git a/server/models/submissions/getSubjectiveSubmission.js b/server/models/submissions/getSubjectiveSubmission.js new file mode 100644 index 0000000..3ef3479 --- /dev/null +++ b/server/models/submissions/getSubjectiveSubmission.js @@ -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 getSubjectiveSubmission({ 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, feedback, score, judged + FROM subjective_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, feedback, + score*EXISTS(SELECT 1 FROM contests WHERE id=? AND (NOW()>end_time OR show_leaderboard=1)) AS score, judged + FROM subjective_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 = getSubjectiveSubmission diff --git a/server/models/submissions/getUserContestSubmissions.js b/server/models/submissions/getUserContestSubmissions.js new file mode 100644 index 0000000..c37f377 --- /dev/null +++ b/server/models/submissions/getUserContestSubmissions.js @@ -0,0 +1,117 @@ +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 getUserContestSubmissions({ username, params, query }) { + return new Promise((resolve, reject) => { + const { contestId, userId } = params + if (userId != username) { + reject('You do not have the required permissions') + } + const { sort, page, size, filters } = query + + let countQuery = `SELECT COUNT(*) AS total FROM + (SELECT id, 'subjective' AS type FROM subjective_submissions WHERE contest_id=? AND username=? ` + let mainQuery = `SELECT s.id, s.question_id, q.name, s.type, s.username, s.submission_time, s.score*EXISTS(SELECT 1 FROM contests WHERE id=? AND (NOW()>end_time OR show_leaderboard=1)) AS score, s.judged FROM + (SELECT id, question_id, 'subjective' AS type, username, submission_time, score, judged FROM subjective_submissions + WHERE contest_id=? AND username=? ` + + 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=? 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)))` + mainQuery += ` UNION ALL SELECT id, question_id, 'mcq' AS type, username, submission_time, score, judged FROM mcq_submissions + WHERE contest_id=? AND username=? ` + + if (filters) { + countQuery += filterQuery(JSON.parse(filters)) + mainQuery += filterQuery(JSON.parse(filters)) + } + + countQuery += `) s ` + mainQuery += `) s INNER JOIN questions q ON q.id=s.question_id ` + + if (sort) { + mainQuery += sortQuery(JSON.parse(sort)) + } + + const countQueryArr = [contestId, username, contestId, username, contestId] + const mainQueryArr = [contestId, contestId, username, contestId, username] + + 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 = parseInt(size, 10) || 10 + const numPages = Math.ceil(totalRows / pageSize) + const pageIndex = 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 = getUserContestSubmissions diff --git a/server/models/submissions/gradeSubjectiveSubmission.js b/server/models/submissions/gradeSubjectiveSubmission.js new file mode 100644 index 0000000..4c86752 --- /dev/null +++ b/server/models/submissions/gradeSubjectiveSubmission.js @@ -0,0 +1,127 @@ +const { pool } = require('../database') + +/** + * + * @param {*} param0 + * @param {String} param0.username + * @param {Number} param0.contestId + * @param {Number} param0.submissionId + * @param {Number} param0.score + * @param {String} param0.feedback + * @return {Promise} + */ + +function gradeSubjectiveSubmission({ + username, + contestId, + submissionId, + score, + feedback, +}) { + return new Promise((resolve, reject) => { + pool.getConnection((err, connection) => { + if (err) { + return reject(err) + } + connection.beginTransaction((err) => { + if (err) { + connection.release() + return reject(err) + } + connection.query( + `SELECT score, judged, username FROM subjective_submissions WHERE id=? + AND EXISTS(SELECT 1 FROM contests_moderators WHERE contest_id=? AND moderator=?)`, + [submissionId, contestId, username], + (error, results) => { + if (error) { + connection.release() + return reject(error) + } + if (!Array.isArray(results) || !results.length) { + connection.release() + return reject('You do not have the required permissions') + } + const prevScore = results[0].score + const prevJudged = results[0].judged + const requestedUser = results[0].username + let scoreUpdateQuery = `UPDATE subjective_submissions s + INNER JOIN contests_questions cq + ON s.contest_id=cq.contest_id AND s.question_id=cq.question_id + SET s.judged=1, score=? ` + let queryArr = [score] + if (feedback) { + scoreUpdateQuery += `, feedback=? ` + queryArr.push(feedback) + } + scoreUpdateQuery += ` WHERE ?<=cq.max_score` + queryArr.push(score) + + connection.query(scoreUpdateQuery, queryArr, (error, results) => { + if (error) { + return connection.rollback(() => { + connection.release() + return reject(error) + }) + } + const { affectedRows } = results + if (!affectedRows) { + return connection.rollback(() => { + connection.release() + return reject( + 'Requested score is greater than maximum score for this question' + ) + }) + } + connection.query( + `INSERT INTO leaderboard(username,contest_id,total_score,total_time,attempted_count) + SELECT ?,?,sub.score,TIMEDIFF(sub.submission_time,c.start_time),1 FROM subjective_submissions sub + INNER JOIN contests c ON c.id=sub.contest_id + WHERE sub.id=? + ON DUPLICATE KEY UPDATE + total_score=total_score+(sub.score-?), + total_time=TIMEDIFF((SELECT MAX(st.submission_time) FROM + (SELECT submission_time FROM mcq_submissions WHERE username=? AND score>0 + UNION ALL + SELECT submission_time FROM subjective_submissions WHERE username=? AND score>0) st), + c.start_time), + attempted_count=attempted_count+(?=0)`, + [ + username, + contestId, + submissionId, + prevScore, + requestedUser, + requestedUser, + prevJudged, + ], + (error) => { + if (error) { + return connection.rollback(() => { + connection.release() + return reject(error) + }) + } + return connection.commit((error) => { + if (error) { + return connection.rollback(() => { + connection.release() + return reject(error) + }) + } + connection.release() + return resolve({ + message: 'Submission graded successfully', + submissionId, + }) + }) + } + ) + }) + } + ) + }) + }) + }) +} + +module.exports = gradeSubjectiveSubmission diff --git a/server/models/submissions/index.js b/server/models/submissions/index.js index 2b79029..f88fa78 100644 --- a/server/models/submissions/index.js +++ b/server/models/submissions/index.js @@ -1,5 +1,19 @@ const createSubmission = require('./createSubmission') +const getAllSubmissions = require('./getAllSubmissions') +const getUserContestSubmissions = require('./getUserContestSubmissions') +const getMCQSubmission = require('./getMCQSubmission') +const getSubjectiveSubmission = require('./getSubjectiveSubmission') +const gradeSubjectiveSubmission = require('./gradeSubjectiveSubmission') +const getContestLeaderboard = require('./getContestLeaderboard') +const getQuestionLeaderboard = require('./getQuestionLeaderboard') module.exports = { createSubmission, + getAllSubmissions, + getUserContestSubmissions, + getMCQSubmission, + getSubjectiveSubmission, + gradeSubjectiveSubmission, + getContestLeaderboard, + getQuestionLeaderboard, } diff --git a/server/routes/index.js b/server/routes/index.js index 211272e..add51fb 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -52,6 +52,14 @@ router.use('/notifications', notificationsRouter.getCreatorNotificationsRouter) router.use('/search', searchRouter.searchUsersRouter) router.use('/contests', submissionsRouter.createSubmission) +router.use('/contests', submissionsRouter.getUserContestSubmissions) +router.use('/contests', submissionsRouter.getMCQSubmission) +router.use('/contests', submissionsRouter.getSubjectiveSubmission) +router.use('/contests', submissionsRouter.getAllSubmissions) +router.use('/contests', submissionsRouter.gradeSubjectiveSubmission) +router.use('/contests', submissionsRouter.getContestLeaderboard) +router.use('/contests', submissionsRouter.getQuestionLeaderboard) +// router.use('/practice', submissionsRouter.getPracticeLeaderboard) router.use('/contests', contestsRouter.createContestRouter) router.use('/contests', contestsRouter.updateContestRouter) diff --git a/server/routes/submissions/getAllSubmissions.js b/server/routes/submissions/getAllSubmissions.js new file mode 100644 index 0000000..dbd40a7 --- /dev/null +++ b/server/routes/submissions/getAllSubmissions.js @@ -0,0 +1,33 @@ +const express = require('express') +const { verifyUserAccessToken } = require('../middlewares') +const router = express.Router() +const { getAllSubmissions } = require('../../models/submissions') +// eslint-disable-next-line no-unused-vars +const csv = require('csv-express') + +router.get('/:contestId/submissions', verifyUserAccessToken, (req, res) => { + const { query } = req + const { download_csv } = query + getAllSubmissions(req) + .then((results) => { + if (!download_csv) { + return res.status(200).json({ + success: true, + results, + error: null, + }) + } else { + const { submissions } = results + return res.csv(submissions, true) + } + }) + .catch((error) => { + return res.status(400).json({ + success: false, + results: null, + error, + }) + }) +}) + +module.exports = router diff --git a/server/routes/submissions/getContestLeaderboard.js b/server/routes/submissions/getContestLeaderboard.js new file mode 100644 index 0000000..0502475 --- /dev/null +++ b/server/routes/submissions/getContestLeaderboard.js @@ -0,0 +1,33 @@ +const express = require('express') +const { verifyUserAccessToken } = require('../middlewares') +const router = express.Router() +const { getContestLeaderboard } = require('../../models/submissions') +// eslint-disable-next-line no-unused-vars +const csv = require('csv-express') + +router.get('/:contestId/leaderboard', verifyUserAccessToken, (req, res) => { + const { query } = req + const { download_csv } = query + getContestLeaderboard(req) + .then((results) => { + if (!download_csv) { + return res.status(200).json({ + success: true, + results, + error: null, + }) + } else { + const { leaderboard } = results + return res.csv(leaderboard, true) + } + }) + .catch((error) => { + return res.status(400).json({ + success: false, + results: null, + error, + }) + }) +}) + +module.exports = router diff --git a/server/routes/submissions/getMCQSubmission.js b/server/routes/submissions/getMCQSubmission.js new file mode 100644 index 0000000..2d9cc81 --- /dev/null +++ b/server/routes/submissions/getMCQSubmission.js @@ -0,0 +1,28 @@ +const express = require('express') +const { verifyUserAccessToken } = require('../middlewares') +const router = express.Router() +const { getMCQSubmission } = require('../../models/submissions') + +router.get( + '/:contestId/mcq_submissions/:submissionId', + verifyUserAccessToken, + (req, res) => { + getMCQSubmission(req) + .then((results) => { + return res.status(200).json({ + success: true, + results, + error: null, + }) + }) + .catch((error) => { + return res.status(400).json({ + success: false, + results: null, + error, + }) + }) + } +) + +module.exports = router diff --git a/server/routes/submissions/getQuestionLeaderboard.js b/server/routes/submissions/getQuestionLeaderboard.js new file mode 100644 index 0000000..871f00d --- /dev/null +++ b/server/routes/submissions/getQuestionLeaderboard.js @@ -0,0 +1,37 @@ +const express = require('express') +const { verifyUserAccessToken } = require('../middlewares') +const router = express.Router() +const { getQuestionLeaderboard } = require('../../models/submissions') +// eslint-disable-next-line no-unused-vars +const csv = require('csv-express') + +router.get( + '/:contestId/questions/:questionId/leaderboard', + verifyUserAccessToken, + (req, res) => { + const { query } = req + const { download_csv } = query + getQuestionLeaderboard(req) + .then((results) => { + if (!download_csv) { + return res.status(200).json({ + success: true, + results, + error: null, + }) + } else { + const { leaderboard } = results + return res.csv(leaderboard, true) + } + }) + .catch((error) => { + return res.status(400).json({ + success: false, + results: null, + error, + }) + }) + } +) + +module.exports = router diff --git a/server/routes/submissions/getSubjectiveSubmission.js b/server/routes/submissions/getSubjectiveSubmission.js new file mode 100644 index 0000000..596dcfd --- /dev/null +++ b/server/routes/submissions/getSubjectiveSubmission.js @@ -0,0 +1,28 @@ +const express = require('express') +const { verifyUserAccessToken } = require('../middlewares') +const router = express.Router() +const { getSubjectiveSubmission } = require('../../models/submissions') + +router.get( + '/:contestId/subjective_submissions/:submissionId', + verifyUserAccessToken, + (req, res) => { + getSubjectiveSubmission(req) + .then((results) => { + return res.status(200).json({ + success: true, + results, + error: null, + }) + }) + .catch((error) => { + return res.status(400).json({ + success: false, + results: null, + error, + }) + }) + } +) + +module.exports = router diff --git a/server/routes/submissions/getUserContestSubmissions.js b/server/routes/submissions/getUserContestSubmissions.js new file mode 100644 index 0000000..7beb23e --- /dev/null +++ b/server/routes/submissions/getUserContestSubmissions.js @@ -0,0 +1,28 @@ +const express = require('express') +const { verifyUserAccessToken } = require('../middlewares') +const router = express.Router() +const { getUserContestSubmissions } = require('../../models/submissions') + +router.get( + '/:contestId/users/:userId/submissions', + verifyUserAccessToken, + (req, res) => { + getUserContestSubmissions(req) + .then((results) => { + return res.status(200).json({ + success: true, + results, + error: null, + }) + }) + .catch((error) => { + return res.status(400).json({ + success: false, + results: null, + error, + }) + }) + } +) + +module.exports = router diff --git a/server/routes/submissions/gradeSubjectiveSubmission.js b/server/routes/submissions/gradeSubjectiveSubmission.js new file mode 100644 index 0000000..a632be5 --- /dev/null +++ b/server/routes/submissions/gradeSubjectiveSubmission.js @@ -0,0 +1,54 @@ +const express = require('express') +const { verifyUserAccessToken } = require('../middlewares') +const router = express.Router() +const ajv = require('../../schema') +const { gradeSubjectiveSubmissionSchema } = require('../../schema/submissions') +const { gradeSubjectiveSubmission } = require('../../models/submissions') + +/** + * + * @param {Array} errArray + * @return {String} + */ +function sumErrors(errArray) { + const cb = (a, b) => a + b.message + ', ' + return errArray.reduce(cb, '') +} + +router.post( + '/:contestId/subjective_submissions/:submissionId/grade', + verifyUserAccessToken, + (req, res) => { + const validate = ajv.compile(gradeSubjectiveSubmissionSchema) + const isValid = validate(req.body) + if (!isValid) { + return res.status(400).json({ + success: false, + error: sumErrors(validate.errors), + results: null, + }) + } + gradeSubjectiveSubmission({ + ...req.body, + username: req.username, + submissionId: req.params.submissionId, + contestId: req.params.contestId, + }) + .then((results) => { + return res.status(200).json({ + success: true, + results, + error: null, + }) + }) + .catch((error) => { + return res.status(400).json({ + success: false, + results: null, + error, + }) + }) + } +) + +module.exports = router diff --git a/server/routes/submissions/index.js b/server/routes/submissions/index.js index 2b79029..f88fa78 100644 --- a/server/routes/submissions/index.js +++ b/server/routes/submissions/index.js @@ -1,5 +1,19 @@ const createSubmission = require('./createSubmission') +const getAllSubmissions = require('./getAllSubmissions') +const getUserContestSubmissions = require('./getUserContestSubmissions') +const getMCQSubmission = require('./getMCQSubmission') +const getSubjectiveSubmission = require('./getSubjectiveSubmission') +const gradeSubjectiveSubmission = require('./gradeSubjectiveSubmission') +const getContestLeaderboard = require('./getContestLeaderboard') +const getQuestionLeaderboard = require('./getQuestionLeaderboard') module.exports = { createSubmission, + getAllSubmissions, + getUserContestSubmissions, + getMCQSubmission, + getSubjectiveSubmission, + gradeSubjectiveSubmission, + getContestLeaderboard, + getQuestionLeaderboard, } diff --git a/server/schema/submissions/gradeSubjectiveSubmission.js b/server/schema/submissions/gradeSubjectiveSubmission.js new file mode 100644 index 0000000..7988aaf --- /dev/null +++ b/server/schema/submissions/gradeSubjectiveSubmission.js @@ -0,0 +1,20 @@ +const schema = { + required: ['score'], + properties: { + score: { + type: 'number', + }, + feedback: { + type: 'string', + }, + }, + errorMessage: { + properties: { + score: 'Score should be a number', + feedback: 'Invalid feedback', + }, + _: 'Invalid data', + }, +} + +module.exports = schema diff --git a/server/schema/submissions/index.js b/server/schema/submissions/index.js index 9b03620..a0717df 100644 --- a/server/schema/submissions/index.js +++ b/server/schema/submissions/index.js @@ -1,5 +1,7 @@ const createSubmissionSchema = require('./createSubmission') +const gradeSubjectiveSubmissionSchema = require('./gradeSubjectiveSubmission.js') module.exports = { createSubmissionSchema, + gradeSubjectiveSubmissionSchema, }