Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create submission API #86

Merged
merged 2 commits into from
Dec 21, 2020
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
57 changes: 42 additions & 15 deletions server/database.sql
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,10 @@ DROP TABLE IF EXISTS `leaderboard`;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `leaderboard` (
`id` int NOT NULL AUTO_INCREMENT,
`rank` int NOT NULL,
`username` varchar(45) NOT NULL,
`contest_id` int NOT NULL,
`score` int NOT NULL,
`total_time` timestamp NOT NULL,
`total_score` int NOT NULL,
`total_time` time NOT NULL,
`attempted_count` int NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_contests_users` (`username`,`contest_id`),
Expand Down Expand Up @@ -254,31 +253,59 @@ CREATE TABLE `questions_tags` (
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `submissions`
-- Table structure for table 'mcq_submissions'
--

DROP TABLE IF EXISTS `submissions`;
DROP TABLE IF EXISTS `mcq_submissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `submissions` (
CREATE TABLE `mcq_submissions` (
`id` int NOT NULL AUTO_INCREMENT,
`question_id` int NOT NULL,
`contest_id` int NOT NULL,
`username` varchar(45) NOT NULL,
`mcq_submission` int DEFAULT NULL,
`subjective_submission` text,
`output` text,
`response` int NOT NULL,
`submission_time` timestamp NOT NULL,
`score` int DEFAULT '0',
`judged` tinyint DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `fk_submissions_questions_idx` (`question_id`),
KEY `fk_submissions_contests_idx` (`contest_id`),
KEY `fk_submissions_users_idx` (`username`),
CONSTRAINT `fk_submissions_contests` FOREIGN KEY (`contest_id`) REFERENCES `contests` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_submissions_questions` FOREIGN KEY (`question_id`) REFERENCES `questions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_submissions_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`) ON DELETE CASCADE ON UPDATE CASCADE
UNIQUE KEY `unique_user_mcq_submission` (`username`,`contest_id`,`question_id`),
KEY `fk_mcq_submissions_questions_idx` (`question_id`),
KEY `fk_mcq_submissions_contests_idx` (`contest_id`),
KEY `fk_mcq_submissions_users_idx` (`username`),
CONSTRAINT `fk_mcq_submissions_contests` FOREIGN KEY (`contest_id`) REFERENCES `contests` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_mcq_submissions_questions` FOREIGN KEY (`question_id`) REFERENCES `questions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_mcq_submissions_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table 'subjective_submissions'
--

DROP TABLE IF EXISTS `subjective_submissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `subjective_submissions` (
`id` int NOT NULL AUTO_INCREMENT,
`question_id` int NOT NULL,
`contest_id` int NOT NULL,
`username` varchar(45) NOT NULL,
`response` text NOT NULL,
`submission_time` timestamp NOT NULL,
`score` int DEFAULT '0',
`judged` tinyint DEFAULT '0',
`feedback` varchar(110) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
UNIQUE KEY `unique_user_subjective_submission` (`username`,`contest_id`,`question_id`),
KEY `fk_subjective_submissions_questions_idx` (`question_id`),
KEY `fk_subjective_submissions_contests_idx` (`contest_id`),
KEY `fk_subjective_submissions_users_idx` (`username`),
CONSTRAINT `fk_subjective_submissions_contests` FOREIGN KEY (`contest_id`) REFERENCES `contests` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_subjective_submissions_questions` FOREIGN KEY (`question_id`) REFERENCES `questions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_subjective_submissions_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

Expand Down
7 changes: 4 additions & 3 deletions server/models/contests/participate.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ function participate({ params, username }) {
}
connection.query(
`INSERT INTO contests_participants (contest_id, participant) SELECT ?, ?
WHERE EXISTS(SELECT 1 FROM user_groups ug INNER JOIN contests_groups cg ON ug.group_id = cg.group_id WHERE ug.username=? AND cg.contest_id=?)
OR EXISTS(SELECT 1 FROM contests WHERE id=? AND public=1)`,
[contestId, username, username, contestId, contestId],
WHERE (EXISTS(SELECT 1 FROM user_groups ug INNER JOIN contests_groups cg ON ug.group_id = cg.group_id WHERE ug.username=? AND cg.contest_id=?)
OR EXISTS(SELECT 1 FROM contests WHERE id=? AND public=1))
AND EXISTS(SELECT 1 FROM contests WHERE id=? AND NOW()<=end_time)`,
[contestId, username, username, contestId, contestId, contestId],
(error, res) => {
if (error || res === undefined) {
return connection.rollback(() => {
Expand Down
139 changes: 139 additions & 0 deletions server/models/submissions/createSubmission.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const { pool } = require('../database')

/**
*
* @param {*} param0
* @param {String} param0.username
* @param {Number} param0.contestId
* @param {Number} param0.questionId
* @param {String} param0.subjective_submission
* @param {Number} param0.mcq_submission
* @return {Promise}
*/

function createSubmission({
username,
contestId,
questionId,
subjective_submission: subjectiveSubmission,
mcq_submission: mcqSubmission,
}) {
return new Promise((resolve, reject) => {
pool.getConnection((err, connection) => {
if (err) {
return reject(err)
}
connection.beginTransaction((err) => {
if (err) {
connection.release()
return reject(err)
}
let insertionQuery
let queryArr = [questionId, contestId, username]
if (typeof mcqSubmission !== 'undefined') {
insertionQuery = `INSERT INTO mcq_submissions(question_id,contest_id,username,submission_time,response,judged,score)
SELECT ?,?,?,NOW(),?,1,(?=q.correct)*cq.max_score FROM questions q INNER JOIN contests_questions cq ON q.id=cq.question_id
WHERE q.id=? AND cq.contest_id=? AND q.type=?
AND EXISTS(SELECT 1 FROM contests WHERE id=? AND NOW()>=start_time AND NOW()<=end_time)
AND EXISTS(SELECT 1 FROM contests_participants WHERE contest_id=? AND participant=?)`
queryArr.push(
mcqSubmission,
mcqSubmission,
questionId,
contestId,
'mcq',
contestId,
contestId,
username
)
} else if (typeof subjectiveSubmission !== 'undefined') {
insertionQuery = `INSERT INTO subjective_submissions(question_id,contest_id,username,submission_time,response)
SELECT ?,?,?,NOW(),? WHERE EXISTS(SELECT 1 FROM questions WHERE id=? AND type=?)
AND EXISTS(SELECT 1 FROM contests WHERE id=? AND NOW()>=start_time AND NOW()<=end_time)
AND EXISTS(SELECT 1 FROM contests_participants WHERE contest_id=? AND participant=?)
ON DUPLICATE KEY UPDATE response=?`
queryArr.push(
subjectiveSubmission,
questionId,
'subjective',
contestId,
contestId,
username,
subjectiveSubmission
)
}
// Logic for coding questions to be added later

connection.query(insertionQuery, queryArr, (error, results) => {
if (error) {
return connection.rollback(() => {
connection.release()
if (mcqSubmission && error.code === 'ER_DUP_ENTRY')
return reject(
'You have already submitted response for the question'
)
else {
return reject(
'Either you have not registered for the contest or the contest has ended'
)
}
})
}

const submissionId = results.insertId

if (typeof subjectiveSubmission !== 'undefined') {
return connection.commit((error) => {
if (error) {
return connection.rollback(() => {
connection.release()
return reject(error)
})
}
connection.release()
return resolve({
message: 'Response submitted successfully',
submissionId,
})
})
} else if (typeof mcqSubmission !== 'undefined') {
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 mcq_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=TIME((sub.score>0)*TIMEDIFF(sub.submission_time,c.start_time)+(sub.score=0)*(total_time)),
attempted_count=attempted_count+1`,
[username, contestId, submissionId],
(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: 'Response submitted successfully',
submissionId,
})
})
}
)
}
})
})
})
})
}

module.exports = createSubmission
5 changes: 5 additions & 0 deletions server/models/submissions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const createSubmission = require('./createSubmission')

module.exports = {
createSubmission,
}
4 changes: 3 additions & 1 deletion server/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const notificationsRouter = require('./notifications')
const searchRouter = require('./search')
const contestsRouter = require('./contests')
const questionsRouter = require('./questions')
// const submissionsRouter = require('./submissions')
const submissionsRouter = require('./submissions')
const tagsRouter = require('./tags')

router.use('/auth', authRouter.signupRouter)
Expand Down Expand Up @@ -51,6 +51,8 @@ router.use('/notifications', notificationsRouter.getCreatorNotificationsRouter)

router.use('/search', searchRouter.searchUsersRouter)

router.use('/contests', submissionsRouter.createSubmission)

router.use('/contests', contestsRouter.createContestRouter)
router.use('/contests', contestsRouter.updateContestRouter)
router.use('/contests', contestsRouter.addModeratorRouter)
Expand Down
54 changes: 54 additions & 0 deletions server/routes/submissions/createSubmission.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const express = require('express')
const { verifyUserAccessToken } = require('../middlewares')
const router = express.Router()
const ajv = require('../../schema')
const { createSubmissionSchema } = require('../../schema/submissions')
const { createSubmission } = 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/questions/:questionId/submit',
verifyUserAccessToken,
(req, res) => {
const validate = ajv.compile(createSubmissionSchema)
const isValid = validate(req.body)
if (!isValid) {
return res.status(400).json({
success: false,
error: sumErrors(validate.errors),
results: null,
})
}
createSubmission({
...req.body,
username: req.username,
questionId: req.params.questionId,
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
5 changes: 5 additions & 0 deletions server/routes/submissions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const createSubmission = require('./createSubmission')

module.exports = {
createSubmission,
}
3 changes: 0 additions & 3 deletions server/schema/submissions/createSubmission.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ const schema = {
},
},
errorMessage: {
required: {
mcq_submission: 'One of the mcq or subjective submissions is required',
},
properties: {
subjective_submission: 'Invalid subjective submission',
mcq_submission: 'Invalid MCQ submission',
Expand Down