diff --git a/framework/serializers/quizzes.js b/framework/serializers/quizzes.js index d90fb45..df8ae5f 100644 --- a/framework/serializers/quizzes.js +++ b/framework/serializers/quizzes.js @@ -1,39 +1,39 @@ -module.exports = function (included = [], type, config) { - if (typeof type === 'object') { - config = type - } +module.exports = function(included = [], type, config) { + if (typeof type === 'object') { + config = type + } - if (type === 'deserialize') { - return { - keyForAttribute: 'camelCase', - questions: { - valueForRelationship (relationship) { - return relationship.id + if (type === 'deserialize') { + return { + keyForAttribute: 'camelCase', + questions: { + valueForRelationship(relationship) { + return relationship.id + } + } } - } } - } - const options = { - attributes: ['title', 'description', 'image', 'duration', 'maxAttempts', 'startDate', 'endDate', 'user', 'questions'], - meta: { - pagination: function (record) { - return record.pagination - } - }, - user: { - ref: 'id', - attributes: ['firstname', 'lastname', 'email', 'role'], - included: included.includes('user') - }, - questions: { - ref: 'id', - ...require('./questions')([], 'serialize'), - included: included.includes('questions') - }, + const options = { + attributes: ['title', 'description', 'image', 'duration', 'maxAttempts', 'startDate', 'endDate', 'user', 'questions', 'totalQuestions'], + meta: { + pagination: function(record) { + return record.pagination + } + }, + user: { + ref: 'id', + attributes: ['firstname', 'lastname', 'email', 'role'], + included: included.includes('user') + }, + questions: { + ref: 'id', + ...require('./questions')([], 'serialize'), + included: included.includes('questions') + }, - ...config - } + ...config + } - return options + return options } \ No newline at end of file diff --git a/routes/api/quiz/controller.js b/routes/api/quiz/controller.js index 236ee9a..a5c63d5 100644 --- a/routes/api/quiz/controller.js +++ b/routes/api/quiz/controller.js @@ -2,131 +2,188 @@ const { Controller: BaseController } = require('@coding-blocks/express-jsonapi-c const DB = require('../../../models') const U = require('../../../utils') const R = require('ramda') +const Sequelize = require('sequelize') class QuizController extends BaseController { - constructor () { - super(...arguments) - new Array('handleSubmit').forEach(fn => { - this[fn] = this[fn].bind(this) - }) - } - - async handleUpdateById (req, res) { - const modelObj = await this.deserialize(req.body) - let questions = modelObj.questions || [] - const quiz = await this._model.findById(req.params.id, { - include: DB.questions - }) - const oldQuestions = quiz.questions.map(q => q.id) - - questions = questions.map(q => +q) - const questionsToAdd = R.difference(questions, oldQuestions) - const questionToRemove = R.difference(oldQuestions, questions) - - const addQuestions = quiz.addQuestions(questionsToAdd, { - through: { - updatedById: req.user.id - } - }) - - const removeQuestions = DB.quizQuestions.destroy({ - where:{ - quizId: quiz.id, - questionId: { - $in: questionToRemove + constructor() { + super(...arguments) + new Array('handleSubmit', 'handleQueryById', 'handleQuery').forEach(fn => { + this[fn] = this[fn].bind(this) + }) + } + async handleQuery(req, res) { + try { + let { rows, count } = await this.findAll(...arguments) + const includeModelNames = this.getIncludeModelNames(req) + const limit = this.generateLimitStatement(req) + const offset = this.generateOffsetStatement(req) + let quizQuestionCounts = await DB.quizzes.findAll({ + includeIgnoreAttributes: false, + attributes: ['id', [Sequelize.fn('count', Sequelize.col('questions->quizQuestions.id')), 'total']], + include: { + model: DB.questions, + }, + where: { + id: { + $in: rows.map(q => q.id) + } + }, + raw: true, + group: ['quizzes.id'] + }) + quizQuestionCounts = R.groupBy(obj => obj.id)(quizQuestionCounts) + rows = rows.map(_ => _.get({ plain: true })) + rows = rows.map(row => { + row.totalQuestions = quizQuestionCounts[row.id][0].total + return row + }) + rows.pagination = { + count, + currentOffset: offset, + nextOffset: offset + limit < count ? offset + limit : count, + prevOffset: offset - limit > 0 ? offset - limit : 0, + } + const result = this.serialize(rows, includeModelNames) + res.json(result) + } catch (err) { + this.handleError(err, res) } - } - }) - - const setUpdatedBy = DB.quizQuestions.update({ - updatedById: req.user.id - }, { - where: { - quizId: quiz.id - } - }) - - await Promise.all([addQuestions, removeQuestions, setUpdatedBy]) - return super.handleUpdateById(...arguments) - } - - // body : { - // questions: [{ - // id: '', - // markedChoices: [] - // }] - // } - - async handleSubmit (req, res, next) { - let markedQuestions = req.body.questions - - const quiz = await this._model.findById(req.params.id, { - include: { - model: DB.questions, - include: { - model: DB.choices, - attributes: ['id', 'title', 'positiveWeight', 'negativeWeight'] + } + + async handleQueryById(req, res, next) { + try { + const row = await this.findById(...arguments) + const includeModelNames = this.getIncludeModelNames(req) + const data = row.get({ plain: true }) + const count = await DB.quizQuestions.count({ + where: { + quizId: row.id + } + }) + data.totalQuestions = count + res.json(this.serialize(data, includeModelNames)) + } catch (err) { + this.handleError(err, res) } - } - }) + } + + + async handleUpdateById(req, res) { + const modelObj = await this.deserialize(req.body) + let questions = modelObj.questions || [] + const quiz = await this._model.findById(req.params.id, { + include: DB.questions + }) + const oldQuestions = quiz.questions.map(q => q.id) + + questions = questions.map(q => +q) + const questionsToAdd = R.difference(questions, oldQuestions) + const questionToRemove = R.difference(oldQuestions, questions) + + const addQuestions = quiz.addQuestions(questionsToAdd, { + through: { + updatedById: req.user.id + } + }) + + const removeQuestions = DB.quizQuestions.destroy({ + where: { + quizId: quiz.id, + questionId: { + $in: questionToRemove + } + } + }) + + const setUpdatedBy = DB.quizQuestions.update({ + updatedById: req.user.id + }, { + where: { + quizId: quiz.id + } + }) - if (!Array.isArray(markedQuestions)) { - //user has not marked any choice - markedQuestions = [] + await Promise.all([addQuestions, removeQuestions, setUpdatedBy]) + return super.handleUpdateById(...arguments) } - - const results = quiz.questions.map(question => { - const markedQuestion = markedQuestions.find(el => el.id == question.id) - - if (!markedQuestion) { - //user marked no response for this question - return { - id: question.id, - score: 0, - correctlyAnswered: [], - incorrectlyAnswered: [], - answers: req.query.showAnswers ? question.correctAnswers : undefined + + // body : { + // questions: [{ + // id: '', + // markedChoices: [] + // }] + // } + + async handleSubmit(req, res, next) { + let markedQuestions = req.body.questions + + const quiz = await this._model.findById(req.params.id, { + include: { + model: DB.questions, + include: { + model: DB.choices, + attributes: ['id', 'title', 'positiveWeight', 'negativeWeight'] + } + } + }) + + if (!Array.isArray(markedQuestions)) { + //user has not marked any choice + markedQuestions = [] } - } - - // we only interested in POJO - question = question.get({plain: true}) - - // parse the array as integer - const markedChoices = U.parseIntArray(markedQuestion.markedChoices) - - // check if the markedChoice are contained in possibleChoices - const areMarkedChoiceValid = U.isContainedIn(markedChoices, question.choices.map(_ => _.id)) - - if(!areMarkedChoiceValid) { - res.status(400).json({ - error: 'markedChoices are out of bounds' + + const results = quiz.questions.map(question => { + const markedQuestion = markedQuestions.find(el => el.id == question.id) + + if (!markedQuestion) { + //user marked no response for this question + return { + id: question.id, + score: 0, + correctlyAnswered: [], + incorrectlyAnswered: [], + answers: req.query.showAnswers ? question.correctAnswers : undefined + } + } + + // we only interested in POJO + question = question.get({ plain: true }) + + // parse the array as integer + const markedChoices = U.parseIntArray(markedQuestion.markedChoices) + + // check if the markedChoice are contained in possibleChoices + const areMarkedChoiceValid = U.isContainedIn(markedChoices, question.choices.map(_ => _.id)) + + if (!areMarkedChoiceValid) { + res.status(400).json({ + error: 'markedChoices are out of bounds' + }) + return; + } + + const { score, correctlyAnswered, incorrectlyAnswered } = U.getScore(markedChoices, U.parseIntArray(question.correctAnswers), question) + + return { + id: markedQuestion.id, + score, + correctlyAnswered, + incorrectlyAnswered, + answers: req.query.showAnswers ? question.correctAnswers : undefined + } }) - return ; - } - - const { score, correctlyAnswered, incorrectlyAnswered } = U.getScore(markedChoices, U.parseIntArray(question.correctAnswers), question) - - return { - id: markedQuestion.id, - score, - correctlyAnswered, - incorrectlyAnswered, - answers: req.query.showAnswers ? question.correctAnswers : undefined - } - }) - - if (!res.headersSent) { - res.json({ - id: quiz.id, - type: 'quiz', - score: results.reduce((acc, val) => acc + val.score, 0), - questions: results - }) - - } - } + if (!res.headersSent) { + res.json({ + id: quiz.id, + type: 'quiz', + score: results.reduce((acc, val) => acc + val.score, 0), + questions: results + }) + + } + + } } -module.exports = QuizController +module.exports = QuizController \ No newline at end of file diff --git a/routes/api/quiz/index.js b/routes/api/quiz/index.js index 40444c5..62f14dc 100644 --- a/routes/api/quiz/index.js +++ b/routes/api/quiz/index.js @@ -8,7 +8,6 @@ const serializer = require('../../../framework/serializers/quizzes') const controller = new BaseController(DB.quizzes, DB, serializer) -routes.use(passport.authenticate('bearer', {session: false}), adminOnly) routes.get('/', controller.handleQuery) routes.get('/:id', controller.handleQueryById) @@ -18,18 +17,18 @@ routes.delete('/:id', controller.handleDeleteById) // TODO: write the handleSubmit function in controller. routes.post('/:id/submit', (req, res, next) => { - if (req.query.showAnswers) { - // make sure req.user is admin - if (req.user.role === 'ADMIN') { - next(null, req, res) + if (req.query.showAnswers) { + // make sure req.user is admin + if (req.user.role === 'ADMIN') { + next(null, req, res) + } else { + res.status(400).json({ + error: 'You cannot request answers as non-admin' + }) + } } else { - res.status(400).json({ - error: 'You cannot request answers as non-admin' - }) + next(null, req, res) } - } else { - next(null, req, res) - } }, controller.handleSubmit) module.exports = routes; \ No newline at end of file