Skip to content
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
45 changes: 30 additions & 15 deletions backend/models/Result.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
const mongoose = require('mongoose');

const resultDetailSchema = new mongoose.Schema({
question: { type: String, required: true },
userAnswer: { type: String },
correctAnswer: { type: String, required: true },
isCorrect: { type: Boolean, required: true },
});

const resultSchema = new mongoose.Schema({
quizId: { type: mongoose.Schema.Types.ObjectId, ref: 'Quiz' },
score: { type: Number, required: true },
total: { type: Number, required: true },
percentage: { type: Number, required: true },
results: { type: [resultDetailSchema], required: true },
submittedAt:{ type: Date, default: Date.now },
});
const resultSchema = new mongoose.Schema(
{
quizId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Quiz',
default: null,
},
score: {
type: Number,
required: true,
},
total: {
type: Number,
required: true,
},
correctAnswers: {
type: Number,
required: true,
},
wrongAnswers: {
type: Number,
required: true,
},
answers: {
type: [String],
default: [],
},
},
{ timestamps: true }
);

module.exports = mongoose.model('Result', resultSchema);
92 changes: 87 additions & 5 deletions backend/routes/quizRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,107 @@ router.post('/submit', async (req, res) => {
if (!questions || !Array.isArray(questions)) {
return res.status(400).json({ error: 'Bad Request: "questions" must be an array' });
}

if (!answers || !Array.isArray(answers)) {
return res.status(400).json({ error: 'Bad Request: "answers" must be an array' });
}

if (questions.length === 0) {
return res.status(400).json({ error: 'Bad Request: "questions" array cannot be empty' });
return res.status(400).json({ error: 'Bad Request: "questions" cannot be empty' });
}

if (questions.length !== answers.length) {
return res.status(400).json({ error: 'Mismatch between questions and answers length' });
}

const scoreResult = calculateScore(questions, answers);

// Ensure all required fields exist
const normalizedResult = {
score: scoreResult.score,
total: scoreResult.total,
correctAnswers: scoreResult.correctAnswers,
wrongAnswers: scoreResult.wrongAnswers,
};

// Save result to MongoDB
const result = await Result.create({
quizId: quizId || null,
...scoreResult,
...normalizedResult,
answers,
});

return res.status(200).json({
resultId: result._id,
...normalizedResult,
});

return res.status(200).json({ resultId: result._id, ...scoreResult });
} catch (error) {
const statusCode = error.message.includes('Mismatch') || error.message.includes('malformed') ? 400 : 500;
return res.status(statusCode).json({ error: 'Failed to calculate score', details: error.message });
return res.status(500).json({
error: 'Failed to calculate score',
details: error.message,
});
}
});

router.get('/results', async (req, res) => {
try {
const results = await Result.find()
.populate('quizId')
.sort({ createdAt: -1 });

return res.status(200).json(results);
} catch (error) {
return res.status(500).json({
error: 'Failed to fetch results',
details: error.message,
});
}
});

router.get('/dashboard', async (req, res) => {
try {
const results = await Result.find()
.populate('quizId')
.sort({ createdAt: -1 });

const completed = results.length;

const avgScore = completed
? Math.round(
results.reduce((sum, item) => sum + (item.total ? (item.score / item.total) * 100 : 0), 0) / completed
)
: 0;

const recentQuizzes = results.slice(0, 5).map((item) => ({
id: item._id,
topic: item.quizId?.topic || 'Untitled Quiz',
score: item.score,
total: item.total,
percentage: item.total ? Math.round((item.score / item.total) * 100) : 0,
date: item.createdAt,
}));

const chartData = results
.slice(0, 7)
.reverse()
.map((item) => ({
id: item._id,
percentage: item.total ? Math.round((item.score / item.total) * 100) : 0,
}));

return res.status(200).json({
completed,
avgScore,
recentQuizzes,
chartData,
latestQuiz: recentQuizzes[0] || null,
});
} catch (error) {
return res.status(500).json({
error: 'Failed to fetch dashboard data',
details: error.message,
});
}
});

Expand Down
70 changes: 20 additions & 50 deletions backend/services/scoreService.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,40 @@
// Calculate score from quiz submission
function calculateScore(questions, userAnswers) {
// Validate inputs
if (!Array.isArray(questions) || questions.length === 0) {
throw new Error('Questions array must be a non-empty array');
function calculateScore(questions, answers) {
if (!Array.isArray(questions) || !Array.isArray(answers)) {
throw new Error("Questions and answers must be arrays");
}

if (!Array.isArray(userAnswers)) {
throw new Error('User answers must be an array');
if (questions.length !== answers.length) {
throw new Error("Mismatch between questions and answers length");
}

if (questions.length !== userAnswers.length) {
throw new Error(
`Mismatch: ${questions.length} questions but ${userAnswers.length} answers provided`
);
}

let score = 0;
const results = [];
let correctAnswers = 0;

for (let i = 0; i < questions.length; i++) {
const question = questions[i];
const userAnswer = userAnswers[i];
const userAnswer = answers[i];

// Validate question structure
if (
!question ||
typeof question !== 'object' ||
!question.question ||
!question.answer
) {
throw new Error(`Question ${i + 1} is malformed`);
if (!question || typeof question !== "object") {
throw new Error(`Malformed question at index ${i}`);
}

// Handle null/undefined answers
const normalizedUserAnswer =
userAnswer === null || userAnswer === undefined
? null
: String(userAnswer).toUpperCase().trim();

const correctAnswer = String(question.answer).toUpperCase().trim();
const isCorrect =
normalizedUserAnswer === correctAnswer && normalizedUserAnswer !== null;

if (isCorrect) {
score++;
if (!question.answer) {
throw new Error(`Missing answer field in question at index ${i}`);
}

results.push({
question: question.question,
userAnswer: normalizedUserAnswer,
correctAnswer: correctAnswer,
isCorrect: isCorrect,
});
if (userAnswer === question.answer) {
correctAnswers++;
}
}

const total = questions.length;
const percentage = Math.round((score / total) * 100);
const wrongAnswers = total - correctAnswers;

return {
score: score,
total: total,
percentage: percentage,
results: results,
score: correctAnswers,
total,
correctAnswers,
wrongAnswers,
};
}

module.exports = {
calculateScore,
};
module.exports = { calculateScore };
Loading
Loading