Commit d2648061 authored by AravindR-K's avatar AravindR-K

Fixed quiz genreation bugs + editing bugs

parent 1d54e11a
......@@ -2,4 +2,4 @@ PORT=5000
#MONGODB_URI=mongodb://localhost:27017/quiz_app
MONGODB_URI=mongodb://127.0.0.1:27017/quiz_app
JWT_SECRET=quiz_app_super_secret_key_2026
JWT_EXPIRES_IN=7d
JWT_EXPIRES_IN=1d
......@@ -43,10 +43,10 @@ const quizSchema = new mongoose.Schema({
}],
difficulty: {
type: String,
enum: ['Beginner', 'Intermediate', 'Advanced'],
default: 'Intermediate'
enum: ['easy', 'medium', 'hard'],
default: 'medium'
},
topic: {
topic: {
type: String,
trim: true
},
......
......@@ -4,7 +4,7 @@
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"dev": "nodemon --ignore uploads/ server.js",
"seed": "node seedAdmin.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
......
const express = require('express');
const xlsx = require('xlsx');
const fs = require('fs');
const User = require('../models/User');
const Quiz = require('../models/Quiz');
const Question = require('../models/Question');
const Submission = require('../models/Submission');
const { protect, authorize } = require('../middleware/auth');
const upload = require('../middleware/upload');
const router = express.Router();
// All admin routes require authentication + admin role
router.use(protect, authorize('admin'));
// ============ USER MANAGEMENT ============
// @route GET /api/admin/users
// @desc Get all candidates and HR users
// @access Admin
router.get('/users', async (req, res) => {
try {
const { role } = req.query;
const filter = role ? { role } : { role: { $in: ['candidate', 'hr'] } };
const users = await User.find(filter)
.select('-password')
.sort({ createdAt: -1 });
res.json({ users });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/users/logged-in
// @desc Get all currently logged-in users
// @access Admin
router.get('/users/logged-in', async (req, res) => {
try {
const users = await User.find({ role: { $in: ['candidate', 'hr'] }, isLoggedIn: true })
.select('-password')
.sort({ createdAt: -1 });
res.json({ users });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
const express = require('express');
const xlsx = require('xlsx');
const fs = require('fs');
const User = require('../models/User');
const Quiz = require('../models/Quiz');
const Question = require('../models/Question');
const Submission = require('../models/Submission');
const { protect, authorize } = require('../middleware/auth');
const upload = require('../middleware/upload');
const router = express.Router();
// All admin routes require authentication + admin role
router.use(protect, authorize('admin'));
// ============ USER MANAGEMENT ============
// @route GET /api/admin/users
// @desc Get all candidates and HR users
// @access Admin
router.get('/users', async (req, res) => {
try {
const { role } = req.query;
const filter = role ? { role } : { role: { $in: ['candidate', 'hr'] } };
const users = await User.find(filter)
.select('-password')
.sort({ createdAt: -1 });
res.json({ users });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route POST /api/admin/users/create-hr
// @desc Create an HR user
// @access Admin
router.post('/users/create-hr', async (req, res) => {
try {
const { name, email, password } = req.body;
// @route GET /api/admin/users/logged-in
// @desc Get all currently logged-in users
// @access Admin
router.get('/users/logged-in', async (req, res) => {
try {
const users = await User.find({ role: { $in: ['candidate', 'hr'] }, isLoggedIn: true })
.select('-password')
.sort({ createdAt: -1 });
res.json({ users });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route POST /api/admin/users/create-hr
// @desc Create an HR user
// @access Admin
router.post('/users/create-hr', async (req, res) => {
try {
const { name, email, password } = req.body;
if (!name || !email || !password) {
return res.status(400).json({ message: 'Please provide name, email, and password' });
}
if (!name || !email || !password) {
return res.status(400).json({ message: 'Please provide name, email, and password' });
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: 'User with this email already exists' });
}
const user = await User.create({ name, email, password, role: 'hr' });
res.status(201).json({
message: 'HR user created successfully',
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
}
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: 'User with this email already exists' });
// @route DELETE /api/admin/users/:userId
// @desc Delete a user
// @access Admin
router.delete('/users/:userId', async (req, res) => {
try {
const { userId } = req.params;
const user = await User.findById(userId);
if (!user) return res.status(404).json({ message: 'User not found' });
if (user.role === 'admin') return res.status(403).json({ message: 'Cannot delete admin users' });
await Submission.deleteMany({ studentId: userId });
await User.findByIdAndDelete(userId);
res.json({ message: 'User deleted successfully' });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
const user = await User.create({ name, email, password, role: 'hr' });
// @route GET /api/admin/users/:userId/history
// @desc Get a user's test history
// @access Admin
router.get('/users/:userId/history', async (req, res) => {
try {
const { userId } = req.params;
res.status(201).json({
message: 'HR user created successfully',
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
const user = await User.findById(userId).select('-password');
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route DELETE /api/admin/users/:userId
// @desc Delete a user
// @access Admin
router.delete('/users/:userId', async (req, res) => {
try {
const { userId } = req.params;
const user = await User.findById(userId);
if (!user) return res.status(404).json({ message: 'User not found' });
if (user.role === 'admin') return res.status(403).json({ message: 'Cannot delete admin users' });
await Submission.deleteMany({ studentId: userId });
await User.findByIdAndDelete(userId);
res.json({ message: 'User deleted successfully' });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/users/:userId/history
// @desc Get a user's test history
// @access Admin
router.get('/users/:userId/history', async (req, res) => {
try {
const { userId } = req.params;
const user = await User.findById(userId).select('-password');
if (!user) {
return res.status(404).json({ message: 'User not found' });
const submissions = await Submission.find({ studentId: userId })
.populate('quizId', 'title timer totalQuestions category')
.sort({ submittedAt: -1 });
res.json({ user, submissions });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
const submissions = await Submission.find({ studentId: userId })
.populate('quizId', 'title timer totalQuestions category')
.sort({ submittedAt: -1 });
// @route GET /api/admin/submissions/:submissionId
// @desc Get detailed submission - answers, correct answers, scores
// @access Admin
router.get('/submissions/:submissionId', async (req, res) => {
try {
const { submissionId } = req.params;
res.json({ user, submissions });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
const submission = await Submission.findById(submissionId)
.populate('studentId', 'name email')
.populate('quizId', 'title timer totalQuestions');
// @route GET /api/admin/submissions/:submissionId
// @desc Get detailed submission - answers, correct answers, scores
// @access Admin
router.get('/submissions/:submissionId', async (req, res) => {
try {
const { submissionId } = req.params;
if (!submission) {
return res.status(404).json({ message: 'Submission not found' });
}
const submission = await Submission.findById(submissionId)
.populate('studentId', 'name email')
.populate('quizId', 'title timer totalQuestions');
// Get all questions for this quiz
const questions = await Question.find({ quizId: submission.quizId._id });
// Build detailed result with question, student answer, correct answer
const detailedAnswers = questions.map(q => {
const studentAnswer = submission.answers.find(
a => a.questionId.toString() === q._id.toString()
);
return {
questionId: q._id,
question: q.question,
options: q.options,
type: q.type,
correctAnswers: q.correctAnswers,
studentAnswers: studentAnswer ? studentAnswer.selectedAnswers : [],
isCorrect: (studentAnswer && studentAnswer.selectedAnswers)
? checkAnswersMatch(studentAnswer.selectedAnswers, q.correctAnswers)
: false
};
});
if (!submission) {
return res.status(404).json({ message: 'Submission not found' });
res.json({
submission: {
id: submission._id,
student: submission.studentId,
quiz: submission.quizId,
score: submission.score,
totalMarks: submission.totalMarks,
percentage: submission.percentage,
timeTaken: submission.timeTaken,
submittedAt: submission.submittedAt
},
detailedAnswers
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// Get all questions for this quiz
const questions = await Question.find({ quizId: submission.quizId._id });
// Build detailed result with question, student answer, correct answer
const detailedAnswers = questions.map(q => {
const studentAnswer = submission.answers.find(
a => a.questionId.toString() === q._id.toString()
);
// ============ QUIZ MANAGEMENT ============
return {
questionId: q._id,
question: q.question,
options: q.options,
type: q.type,
correctAnswers: q.correctAnswers,
studentAnswers: studentAnswer ? studentAnswer.selectedAnswers : [],
isCorrect: (studentAnswer && studentAnswer.selectedAnswers)
? checkAnswersMatch(studentAnswer.selectedAnswers, q.correctAnswers)
: false
};
});
// @route POST /api/admin/quiz/create
// @desc Create quiz with Excel upload
// @access Admin
router.post('/quiz/create', upload.single('questionsFile'), async (req, res) => {
try {
const { title, timer, category, difficulty, topic, assignToAll, assignees, assignedGroups } = req.body;
res.json({
submission: {
id: submission._id,
student: submission.studentId,
quiz: submission.quizId,
score: submission.score,
totalMarks: submission.totalMarks,
percentage: submission.percentage,
timeTaken: submission.timeTaken,
submittedAt: submission.submittedAt
},
detailedAnswers
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
if (!title || !timer) {
return res.status(400).json({ message: 'Please provide quiz title and timer' });
}
// ============ QUIZ MANAGEMENT ============
if (!req.file) {
return res.status(400).json({ message: 'Please upload an Excel file with questions' });
}
console.log("FILE:", req.file);
console.log("BODY:", req.body);
// Parse Excel file
const workbook = xlsx.readFile(req.file.path);
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const data = xlsx.utils.sheet_to_json(sheet);
if (data.length === 0) {
fs.unlinkSync(req.file.path);
return res.status(400).json({ message: 'Excel file is empty' });
}
// @route POST /api/admin/quiz/create
// @desc Create quiz with Excel upload
// @access Admin
router.post('/quiz/create', upload.single('questionsFile'), async (req, res) => {
try {
const { title, timer, category, difficulty, topic, assignToAll, assignees, assignedGroups } = req.body;
// Parse assignees
let parsedAssignees = [];
if (assignees) {
try { parsedAssignees = JSON.parse(assignees); } catch (e) { parsedAssignees = []; }
}
if (!title || !timer) {
return res.status(400).json({ message: 'Please provide quiz title and timer' });
}
let parsedGroups = [];
if (assignedGroups) {
try { parsedGroups = JSON.parse(assignedGroups); } catch (e) { parsedGroups = []; }
}
if (!req.file) {
return res.status(400).json({ message: 'Please upload an Excel file with questions' });
}
// Create quiz
const quiz = await Quiz.create({
title,
timer: parseInt(timer),
totalQuestions: data.length,
createdBy: req.user._id,
category: category || 'General',
difficulty: difficulty || 'Intermediate',
topic: topic || '',
assignToAll: assignToAll === 'true' || assignToAll === true,
assignees: parsedAssignees,
assignedGroups: parsedGroups
});
// Parse Excel file
const workbook = xlsx.readFile(req.file.path);
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const data = xlsx.utils.sheet_to_json(sheet);
// Parse and create questions
const questions = parseExcelQuestions(data, quiz._id);
console.log("PARSED QUESTIONS:", questions);
await Question.insertMany(questions);
if (data.length === 0) {
// Clean up uploaded file
fs.unlinkSync(req.file.path);
return res.status(400).json({ message: 'Excel file is empty' });
}
// Parse assignees
let parsedAssignees = [];
if (assignees) {
try { parsedAssignees = JSON.parse(assignees); } catch (e) { parsedAssignees = []; }
res.status(201).json({
message: 'Quiz created successfully',
quiz: {
id: quiz._id,
title: quiz.title,
timer: quiz.timer,
totalQuestions: quiz.totalQuestions,
category: quiz.category
}
});
} catch (error) {
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({ message: 'Server error', error: error.message });
}
});
let parsedGroups = [];
if (assignedGroups) {
try { parsedGroups = JSON.parse(assignedGroups); } catch (e) { parsedGroups = []; }
}
// @route POST /api/admin/quiz/create-manual
// @desc Create quiz manually (for AI-generated quizzes or manual entry)
// @access Admin
router.post('/quiz/create-manual', async (req, res) => {
try {
const { title, timer, category, difficulty, topic, assignToAll, assignees, assignedGroups, questions, generatedByAI } = req.body;
// Create quiz
const quiz = await Quiz.create({
title,
timer: parseInt(timer),
totalQuestions: data.length,
createdBy: req.user._id,
category: category || 'General',
difficulty: difficulty || 'Intermediate',
topic: topic || '',
assignToAll: assignToAll === 'true' || assignToAll === true,
assignees: parsedAssignees,
assignedGroups: parsedGroups
});
if (!title || !timer) {
return res.status(400).json({ message: 'Please provide quiz title and timer' });
}
// Parse and create questions
const questions = parseExcelQuestions(data, quiz._id);
await Question.insertMany(questions);
// Clean up uploaded file
fs.unlinkSync(req.file.path);
res.status(201).json({
message: 'Quiz created successfully',
quiz: {
id: quiz._id,
title: quiz.title,
timer: quiz.timer,
totalQuestions: quiz.totalQuestions,
category: quiz.category
if (!questions || questions.length === 0) {
return res.status(400).json({ message: 'Please provide at least one question' });
}
});
} catch (error) {
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
// Create quiz
const quiz = await Quiz.create({
title,
timer: parseInt(timer),
totalQuestions: questions.length,
createdBy: req.user._id,
category: category || 'General',
difficulty: difficulty || 'Intermediate',
topic: topic || '',
assignToAll: assignToAll || false,
assignees: assignees || [],
assignedGroups: assignedGroups || [],
generatedByAI: generatedByAI || false
});
// Create questions
const questionDocs = questions.map(q => ({
quizId: quiz._id,
question: q.question,
options: q.options,
correctAnswers: q.correctAnswers,
type: q.correctAnswers.length > 1 ? 'mcq' : 'single'
}));
await Question.insertMany(questionDocs);
res.status(201).json({
message: 'Quiz created successfully',
quiz: {
id: quiz._id,
title: quiz.title,
timer: quiz.timer,
totalQuestions: quiz.totalQuestions,
category: quiz.category
}
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
res.status(500).json({ message: 'Server error', error: error.message });
}
});
});
// @route POST /api/admin/quiz/create-manual
// @desc Create quiz manually (for AI-generated quizzes or manual entry)
// @access Admin
router.post('/quiz/create-manual', async (req, res) => {
try {
const { title, timer, category, difficulty, topic, assignToAll, assignees, assignedGroups, questions, generatedByAI } = req.body;
// @route GET /api/admin/quizzes
// @desc Get all quizzes with attempt count
// @access Admin
router.get('/quizzes', async (req, res) => {
try {
const quizzes = await Quiz.find()
.populate('createdBy', 'name email')
.sort({ createdAt: -1 });
// Get attempt counts for each quiz
const quizzesWithAttempts = await Promise.all(
quizzes.map(async (quiz) => {
const attemptCount = await Submission.countDocuments({ quizId: quiz._id });
return { ...quiz.toObject(), attemptCount };
})
);
if (!title || !timer) {
return res.status(400).json({ message: 'Please provide quiz title and timer' });
res.json({ quizzes: quizzesWithAttempts });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/quiz/:quizId
// @desc Get quiz with its questions for editing
// @access Admin
router.get('/quiz/:quizId', async (req, res) => {
try {
const { quizId } = req.params;
const quiz = await Quiz.findById(quizId)
.populate('createdBy', 'name email')
.populate('assignees', 'name email');
if (!quiz) {
return res.status(404).json({ message: 'Quiz not found' });
}
if (!questions || questions.length === 0) {
return res.status(400).json({ message: 'Please provide at least one question' });
const questions = await Question.find({ quizId });
const attemptCount = await Submission.countDocuments({ quizId });
res.json({ quiz, questions, attemptCount });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// Create quiz
const quiz = await Quiz.create({
title,
timer: parseInt(timer),
totalQuestions: questions.length,
createdBy: req.user._id,
category: category || 'General',
difficulty: difficulty || 'Intermediate',
topic: topic || '',
assignToAll: assignToAll || false,
assignees: assignees || [],
assignedGroups: assignedGroups || [],
generatedByAI: generatedByAI || false
});
// @route PUT /api/admin/quiz/:quizId
// @desc Edit quiz (only if no attempts have been made)
// @access Admin
router.put('/quiz/:quizId', async (req, res) => {
try {
const { quizId } = req.params;
// Check if any attempts exist
const attemptCount = await Submission.countDocuments({ quizId });
if (attemptCount > 0) {
return res.status(403).json({
message: 'This quiz cannot be edited because it has already been attempted by users.',
attemptCount
});
}
// Create questions
const questionDocs = questions.map(q => ({
quizId: quiz._id,
question: q.question,
options: q.options,
correctAnswers: q.correctAnswers,
type: q.correctAnswers.length > 1 ? 'mcq' : 'single'
}));
await Question.insertMany(questionDocs);
res.status(201).json({
message: 'Quiz created successfully',
quiz: {
id: quiz._id,
title: quiz.title,
timer: quiz.timer,
totalQuestions: quiz.totalQuestions,
category: quiz.category
const { title, timer, category, difficulty, topic, assignToAll, assignees, assignedGroups, questions } = req.body;
// Update quiz metadata
const updateData = {};
if (title) updateData.title = title;
if (timer) updateData.timer = parseInt(timer);
if (category) updateData.category = category;
if (difficulty) updateData.difficulty = difficulty;
if (topic !== undefined) updateData.topic = topic;
if (assignToAll !== undefined) updateData.assignToAll = assignToAll;
if (assignees) updateData.assignees = assignees;
if (assignedGroups) updateData.assignedGroups = assignedGroups;
// If questions are provided, replace them
if (questions && questions.length > 0) {
await Question.deleteMany({ quizId });
const questionDocs = questions.map(q => ({
quizId,
question: q.question,
options: q.options,
correctAnswers: q.correctAnswers,
type: q.correctAnswers.length > 1 ? 'mcq' : 'single'
}));
await Question.insertMany(questions);
updateData.totalQuestions = questions.length;
}
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/quizzes
// @desc Get all quizzes with attempt count
// @access Admin
router.get('/quizzes', async (req, res) => {
try {
const quizzes = await Quiz.find()
.populate('createdBy', 'name email')
.sort({ createdAt: -1 });
// Get attempt counts for each quiz
const quizzesWithAttempts = await Promise.all(
quizzes.map(async (quiz) => {
const attemptCount = await Submission.countDocuments({ quizId: quiz._id });
return { ...quiz.toObject(), attemptCount };
})
);
res.json({ quizzes: quizzesWithAttempts });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/quiz/:quizId
// @desc Get quiz with its questions for editing
// @access Admin
router.get('/quiz/:quizId', async (req, res) => {
try {
const { quizId } = req.params;
const quiz = await Quiz.findById(quizId)
.populate('createdBy', 'name email')
.populate('assignees', 'name email');
if (!quiz) {
return res.status(404).json({ message: 'Quiz not found' });
}
const questions = await Question.find({ quizId });
const attemptCount = await Submission.countDocuments({ quizId });
const quiz = await Quiz.findByIdAndUpdate(quizId, updateData, { new: true });
res.json({ quiz, questions, attemptCount });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route PUT /api/admin/quiz/:quizId
// @desc Edit quiz (only if no attempts have been made)
// @access Admin
router.put('/quiz/:quizId', async (req, res) => {
try {
const { quizId } = req.params;
// Check if any attempts exist
const attemptCount = await Submission.countDocuments({ quizId });
if (attemptCount > 0) {
return res.status(403).json({
message: 'This quiz cannot be edited because it has already been attempted by users.',
attemptCount
});
res.json({ message: 'Quiz updated successfully', quiz });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route PUT /api/admin/quiz/:quizId/assign
// @desc Assign quiz to users/groups
// @access Admin
router.put('/quiz/:quizId/assign', async (req, res) => {
try {
const { quizId } = req.params;
const { assignToAll, assignees, assignedGroups } = req.body;
const quiz = await Quiz.findByIdAndUpdate(quizId, {
assignToAll: assignToAll || false,
assignees: assignees || [],
assignedGroups: assignedGroups || []
}, { new: true });
if (!quiz) return res.status(404).json({ message: 'Quiz not found' });
res.json({ message: 'Quiz assignment updated', quiz });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route DELETE /api/admin/quiz/:quizId
// @desc Delete a quiz (only if no attempts have been made)
// @access Admin
router.delete('/quiz/:quizId', async (req, res) => {
try {
const { quizId } = req.params;
// Check if any attempts exist
const attemptCount = await Submission.countDocuments({ quizId });
if (attemptCount > 0) {
return res.status(403).json({
message: 'This quiz cannot be deleted because it has already been attempted by users.',
attemptCount
});
}
const { title, timer, category, difficulty, topic, assignToAll, assignees, assignedGroups, questions } = req.body;
// Update quiz metadata
const updateData = {};
if (title) updateData.title = title;
if (timer) updateData.timer = parseInt(timer);
if (category) updateData.category = category;
if (difficulty) updateData.difficulty = difficulty;
if (topic !== undefined) updateData.topic = topic;
if (assignToAll !== undefined) updateData.assignToAll = assignToAll;
if (assignees) updateData.assignees = assignees;
if (assignedGroups) updateData.assignedGroups = assignedGroups;
// If questions are provided, replace them
if (questions && questions.length > 0) {
await Question.deleteMany({ quizId });
await Quiz.findByIdAndDelete(quizId);
const questionDocs = questions.map(q => ({
quizId,
question: q.question,
options: q.options,
correctAnswers: q.correctAnswers,
type: q.correctAnswers.length > 1 ? 'mcq' : 'single'
}));
res.json({ message: 'Quiz deleted successfully' });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
await Question.insertMany(questionDocs);
updateData.totalQuestions = questions.length;
// @route GET /api/admin/categories
// @desc Get all unique quiz categories
// @access Admin
router.get('/categories', async (req, res) => {
try {
const categories = await Quiz.distinct('category');
res.json({ categories });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
const quiz = await Quiz.findByIdAndUpdate(quizId, updateData, { new: true });
// @route GET /api/admin/groups
// @desc Get all unique user groups
// @access Admin
router.get('/groups', async (req, res) => {
try {
const groups = await User.distinct('group');
res.json({ groups });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
res.json({ message: 'Quiz updated successfully', quiz });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route PUT /api/admin/quiz/:quizId/assign
// @desc Assign quiz to users/groups
// @access Admin
router.put('/quiz/:quizId/assign', async (req, res) => {
try {
const { quizId } = req.params;
const { assignToAll, assignees, assignedGroups } = req.body;
const quiz = await Quiz.findByIdAndUpdate(quizId, {
assignToAll: assignToAll || false,
assignees: assignees || [],
assignedGroups: assignedGroups || []
}, { new: true });
if (!quiz) return res.status(404).json({ message: 'Quiz not found' });
res.json({ message: 'Quiz assignment updated', quiz });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route DELETE /api/admin/quiz/:quizId
// @desc Delete a quiz (only if no attempts have been made)
// @access Admin
router.delete('/quiz/:quizId', async (req, res) => {
try {
const { quizId } = req.params;
// Check if any attempts exist
const attemptCount = await Submission.countDocuments({ quizId });
if (attemptCount > 0) {
return res.status(403).json({
message: 'This quiz cannot be deleted because it has already been attempted by users.',
attemptCount
// @route POST /api/admin/groups
// @desc Create a new group
// @access Admin
router.post('/groups', async (req, res) => {
try {
const { name } = req.body;
if (!name || !name.trim()) {
return res.status(400).json({ message: 'Group name is required' });
}
// Check if already exists
const existing = await User.findOne({ group: name.trim() });
if (existing) {
return res.status(400).json({ message: 'Group already exists' });
}
// ⚠️ Since you're using group as string,
// we don't store separately — just return success
res.status(201).json({ message: 'Group created successfully', group: name.trim() });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/stats
// @desc Get dashboard statistics
// @access Admin
router.get('/stats', async (req, res) => {
try {
const totalUsers = await User.countDocuments({ role: 'candidate' });
const totalHR = await User.countDocuments({ role: 'hr' });
const totalQuizzes = await Quiz.countDocuments();
const totalSubmissions = await Submission.countDocuments();
const onlineUsers = await User.countDocuments({ isLoggedIn: true, role: { $in: ['candidate', 'hr'] } });
// Recent submissions
const recentSubmissions = await Submission.find()
.populate('studentId', 'name email')
.populate('quizId', 'title')
.sort({ submittedAt: -1 })
.limit(5);
res.json({
stats: { totalUsers, totalHR, totalQuizzes, totalSubmissions, onlineUsers },
recentSubmissions
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
await Question.deleteMany({ quizId });
await Quiz.findByIdAndDelete(quizId);
// ============ HELPER FUNCTIONS ============
res.json({ message: 'Quiz deleted successfully' });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/categories
// @desc Get all unique quiz categories
// @access Admin
router.get('/categories', async (req, res) => {
try {
const categories = await Quiz.distinct('category');
res.json({ categories });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/groups
// @desc Get all unique user groups
// @access Admin
router.get('/groups', async (req, res) => {
try {
const groups = await User.distinct('group');
res.json({ groups });
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// @route GET /api/admin/stats
// @desc Get dashboard statistics
// @access Admin
router.get('/stats', async (req, res) => {
try {
const totalUsers = await User.countDocuments({ role: 'candidate' });
const totalHR = await User.countDocuments({ role: 'hr' });
const totalQuizzes = await Quiz.countDocuments();
const totalSubmissions = await Submission.countDocuments();
const onlineUsers = await User.countDocuments({ isLoggedIn: true, role: { $in: ['candidate', 'hr'] } });
// Recent submissions
const recentSubmissions = await Submission.find()
.populate('studentId', 'name email')
.populate('quizId', 'title')
.sort({ submittedAt: -1 })
.limit(5);
res.json({
stats: { totalUsers, totalHR, totalQuizzes, totalSubmissions, onlineUsers },
recentSubmissions
});
} catch (error) {
res.status(500).json({ message: 'Server error', error: error.message });
}
});
// ============ HELPER FUNCTIONS ============
function parseExcelQuestions(data, quizId) {
return data.map(row => {
const question = row['Question'] || row['question'] || '';
const option1 = row['Option1'] || row['option1'] || row['Option 1'] || '';
const option2 = row['Option2'] || row['option2'] || row['Option 2'] || '';
const option3 = row['Option3'] || row['option3'] || row['Option 3'] || '';
const option4 = row['Option4'] || row['option4'] || row['Option 4'] || '';
const correct = row['Correct'] || row['correct'] || row['Answer'] || row['answer'] || '';
const correctStr = correct.toString().trim();
const correctAnswers = correctStr.includes(',')
? correctStr.split(',').map(a => a.trim())
: [correctStr];
const type = correctAnswers.length > 1 ? 'mcq' : 'single';
return {
quizId,
question: question.toString().trim(),
options: [
function parseExcelQuestions(data, quizId) {
return data.map(row => {
const question = row['Question'] || row['question'] || '';
const option1 = row['Option1'] || row['option1'] || row['Option 1'] || '';
const option2 = row['Option2'] || row['option2'] || row['Option 2'] || '';
const option3 = row['Option3'] || row['option3'] || row['Option 3'] || '';
const option4 = row['Option4'] || row['option4'] || row['Option 4'] || '';
const correct = row['Correct'] || row['correct'] || row['Answer'] || row['answer'] || '';
const options = [
option1.toString().trim(),
option2.toString().trim(),
option3.toString().trim(),
option4.toString().trim()
],
correctAnswers,
type
};
});
}
];
const correctStr = correct.toString().trim();
const correctArr = correctStr.split(',').map(s => s.trim());
// 🔥 convert text → index
const correctAnswers = options
.map((opt, index) => correctArr.includes(opt) ? index.toString() : null)
.filter(val => val !== null);
if (correctAnswers.length === 0) {
throw new Error(`Correct answer "${correctStr}" not matching options`);
}
const type = correctAnswers.length > 1 ? 'mcq' : 'single';
return {
quizId,
question: question.toString().trim(),
options: [
option1.toString().trim(),
option2.toString().trim(),
option3.toString().trim(),
option4.toString().trim()
],
correctAnswers,
type
};
});
}
function checkAnswersMatch(arr1, arr2) {
if (!arr1 || !arr2 || arr1.length !== arr2.length) return false;
const a = arr1.map(x => x.toString().trim().toLowerCase()).sort().join('||');
const b = arr2.map(x => x.toString().trim().toLowerCase()).sort().join('||');
return a === b;
}
function checkAnswersMatch(arr1, arr2) {
if (!arr1 || !arr2 || arr1.length !== arr2.length) return false;
const a = arr1.map(x => x.toString().trim().toLowerCase()).sort().join('||');
const b = arr2.map(x => x.toString().trim().toLowerCase()).sort().join('||');
return a === b;
}
module.exports = router;
module.exports = router;
......@@ -7,7 +7,7 @@ const router = express.Router();
// Generate JWT Token
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
expiresIn: process.env.JWT_EXPIRES_IN || '1d'
});
};
......
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
// Load env vars
dotenv.config();
// Connect to database
connectDB();
const app = express();
// Middleware
const allowedOrigins = [
'http://localhost:4200',
'http://localhost:3000',
process.env.FRONTEND_URL
].filter(Boolean);
app.use(cors({
origin: function (origin, callback) {
// Allow requests with no origin (mobile apps, curl, Postman, server-to-server)
if (!origin) return callback(null, true);
// Allow any vercel.app previews and production deployments
if (origin.endsWith('.vercel.app') || origin.includes('vercel.app')) {
return callback(null, true);
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
// Load env vars
dotenv.config();
// Connect to database
connectDB();
const app = express();
// Middleware
const allowedOrigins = [
'http://localhost:4200',
'http://localhost:3000',
process.env.FRONTEND_URL
].filter(Boolean);
app.use(cors({
origin: function (origin, callback) {
// Allow requests with no origin (mobile apps, curl, Postman, server-to-server)
if (!origin) return callback(null, true);
// Allow any vercel.app previews and production deployments
if (origin.endsWith('.vercel.app') || origin.includes('vercel.app')) {
return callback(null, true);
}
// Allow explicitly listed origins
if (allowedOrigins.includes(origin)) {
return callback(null, true);
}
// Block everything else
console.warn(`CORS blocked origin: ${origin}`);
return callback(new Error(`CORS policy: origin ${origin} not allowed`), false);
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/admin', require('./routes/admin'));
app.use('/api/hr', require('./routes/hr'));
app.use('/api/candidate', require('./routes/candidate'));
// Keep backward compatibility for old student endpoints
app.use('/api/student', require('./routes/candidate'));
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', message: 'QuizMaster Pro API is running' });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
if (err.name === 'MulterError') {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ message: 'File size exceeds 5MB limit' });
}
return res.status(400).json({ message: err.message });
}
// Allow explicitly listed origins
if (allowedOrigins.includes(origin)) {
return callback(null, true);
}
// Block everything else
console.warn(`CORS blocked origin: ${origin}`);
return callback(new Error(`CORS policy: origin ${origin} not allowed`), false);
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/admin', require('./routes/admin'));
app.use('/api/hr', require('./routes/hr'));
app.use('/api/candidate', require('./routes/candidate'));
// Keep backward compatibility for old student endpoints
app.use('/api/student', require('./routes/candidate'));
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', message: 'QuizMaster Pro API is running' });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
if (err.name === 'MulterError') {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ message: 'File size exceeds 5MB limit' });
}
return res.status(400).json({ message: err.message });
}
res.status(500).json({ message: 'Something went wrong!', error: err.message });
});
res.status(500).json({ message: 'Something went wrong!', error: err.message });
});
const PORT = process.env.PORT || 5000;
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('token');
const token = sessionStorage.getItem('token');
if (token) {
const cloned = req.clone({
......
......@@ -56,34 +56,53 @@ export class EditQuizComponent implements OnInit {
}
});
}
onSave(): void {
if (!this.title.trim()) {
this.error.set('Title is required');
return;
}
this.saving.set(true);
this.error.set('');
onSave(): void {
if (!this.title.trim()) { this.error.set('Title is required'); return; }
this.saving.set(true);
this.error.set('');
// 🔥 Format questions correctly
const formattedQuestions = this.questions().map(q => {
const options = q.options;
const data = {
title: this.title,
timer: this.timer,
category: this.category,
difficulty: this.difficulty,
topic: this.topic,
questions: this.questions()
const correctIndex = options.findIndex(
(opt: string) => opt == q.correctAnswer
);
return {
question: q.question,
options,
correctAnswers: [correctIndex.toString()],
type: 'single'
};
}); // ✅ closes map()
this.quizService.updateQuiz(this.quizId, data).subscribe({
next: () => {
this.saving.set(false);
this.success.set('Quiz updated successfully!');
setTimeout(() => this.router.navigate(['/admin/quizzes']), 1200);
},
error: (err) => {
this.saving.set(false);
this.error.set(err.error?.message || 'Failed to update quiz');
}
});
}
// 🔥 Final data
const data = {
title: this.title,
timer: this.timer,
category: this.category,
difficulty: this.difficulty,
topic: this.topic,
questions: formattedQuestions
}; // ✅ closes data object
this.quizService.updateQuiz(this.quizId, data).subscribe({
next: () => {
this.saving.set(false);
this.success.set('Quiz updated successfully!');
setTimeout(() => this.router.navigate(['/admin/quizzes']), 1200);
},
error: (err) => {
this.saving.set(false);
this.error.set(err.error?.message || 'Failed to update quiz');
}
}); // ✅ closes subscribe
} // ✅ closes function
updateQuestion(index: number, field: string, value: any): void {
const q = [...this.questions()];
q[index] = { ...q[index], [field]: value };
......
<div class="dashboard-layout">
<aside class="sidebar">
<div class="sidebar-header">
<span class="logo-icon">📝</span><h2>QuizMaster</h2><span class="role-badge">Admin</span>
<span class="logo-icon">📝</span>
<h2>QuizMaster</h2><span class="role-badge">Admin</span>
</div>
<nav class="sidebar-nav">
<a routerLink="/admin/dashboard" class="nav-item"><span class="nav-icon">🏠</span><span>Dashboard</span></a>
<a routerLink="/admin/users" class="nav-item"><span class="nav-icon">👥</span><span>Users</span></a>
<a routerLink="/admin/generate-quiz" class="nav-item active"><span class="nav-icon"></span><span>Generate Quiz</span></a>
<a routerLink="/admin/generate-quiz" class="nav-item active"><span class="nav-icon"></span><span>Generate
Quiz</span></a>
</nav>
<div class="sidebar-footer">
<div class="user-info">
<div class="user-avatar">{{ authService.currentUser()?.name?.charAt(0) || 'A' }}</div>
<div class="user-details"><span class="user-name">{{ authService.currentUser()?.name }}</span><span class="user-email">{{ authService.currentUser()?.email }}</span></div>
<div class="user-details"><span class="user-name">{{ authService.currentUser()?.name }}</span><span
class="user-email">{{ authService.currentUser()?.email }}</span></div>
</div>
<button class="logout-btn" (click)="logout()"><span>🚪</span> Logout</button>
</div>
......@@ -24,10 +27,10 @@
</div>
@if (success()) {
<div class="alert alert-success">✅ {{ success() }}</div>
<div class="alert alert-success">✅ {{ success() }}</div>
}
@if (error()) {
<div class="alert alert-error">⚠️ {{ error() }}</div>
<div class="alert alert-error">⚠️ {{ error() }}</div>
}
<div class="create-section">
......@@ -35,7 +38,8 @@
<div class="form-row">
<div class="form-group">
<label for="title">Quiz Name</label>
<input type="text" id="title" [(ngModel)]="title" name="title" placeholder="e.g., JavaScript Fundamentals" />
<input type="text" id="title" [(ngModel)]="title" name="title"
placeholder="e.g., JavaScript Fundamentals" />
</div>
<div class="form-group">
<label for="timer">Timer (minutes)</label>
......@@ -48,53 +52,77 @@
<div class="file-upload" (click)="fileInput.click()">
<input #fileInput type="file" accept=".xlsx,.xls" (change)="onFileSelected($event)" hidden />
@if (fileName()) {
<span class="file-icon">📄</span>
<span class="file-name">{{ fileName() }}</span>
<span class="file-change">Change</span>
<span class="file-icon">📄</span>
<span class="file-name">{{ fileName() }}</span>
<span class="file-change">Change</span>
} @else {
<span class="upload-icon">📤</span>
<span class="upload-text">Click to upload Excel file</span>
<span class="upload-hint">Format: Question | Option1 | Option2 | Option3 | Option4 | Correct</span>
<span class="upload-icon">📤</span>
<span class="upload-text">Click to upload Excel file</span>
<span class="upload-hint">Format: Question | Option1 | Option2 | Option3 | Option4 | Correct</span>
}
</div>
</div>
<button type="submit" class="btn btn-primary" [disabled]="loading()">
<div class="form-group">
<label>Assign Quiz</label>
<div style="margin-bottom: 10px;">
<label>
<input type="checkbox" [checked]="assignToAll()" (change)="assignToAll.set(!assignToAll())" />
Assign to All Users
</label>
</div>
@if (!assignToAll()) {
<div class="group-list">
@for (group of groups(); track group._id) {
<label style="display:block; margin-bottom:6px;">
<input type="checkbox" [value]="group.name" (change)="onGroupToggle(group.name, $event)" />
{{ group.name }}
</label>
}
</div>
}
</div>
<button mat-raised-button color="primary" type="submit" class="btn btn-primary" [disabled]="loading()">
@if (loading()) {
<span class="spinner"></span> Creating Quiz...
<span class="spinner"></span> Creating Quiz...
} @else {
🚀 Create Quiz
🚀 Create Quiz
}
</button>
</form>
</div>
<!-- Existing Quizzes -->
<h2 class="section-title">Existing Quizzes</h2>
@if (loadingQuizzes()) {
<div class="loading-state"><div class="loader"></div></div>
<div class="loading-state">
<div class="loader"></div>
</div>
} @else if (quizzes().length === 0) {
<div class="empty-state">
<span class="empty-icon">📋</span>
<h3>No quizzes yet</h3>
<p>Create your first quiz above</p>
</div>
<div class="empty-state">
<span class="empty-icon">📋</span>
<h3>No quizzes yet</h3>
<p>Create your first quiz above</p>
</div>
} @else {
<div class="quiz-grid">
@for (quiz of quizzes(); track quiz._id) {
<div class="quiz-card">
<div class="quiz-card-header">
<h4>{{ quiz.title }}</h4>
<button class="delete-btn" (click)="deleteQuiz(quiz._id)">🗑️</button>
</div>
<div class="quiz-meta">
<span>⏱️ {{ quiz.timer }} min</span>
<span>📝 {{ quiz.totalQuestions }} questions</span>
</div>
<span class="quiz-date">Created {{ quiz.createdAt | date:'mediumDate' }}</span>
</div>
}
<div class="quiz-grid">
@for (quiz of quizzes(); track quiz._id) {
<div class="quiz-card">
<div class="quiz-card-header">
<h4>{{ quiz.title }}</h4>
<button class="delete-btn" (click)="deleteQuiz(quiz._id)">🗑️</button>
</div>
<div class="quiz-meta">
<span>⏱️ {{ quiz.timer }} min</span>
<span>📝 {{ quiz.totalQuestions }} questions</span>
</div>
<span class="quiz-date">Created {{ quiz.createdAt | date:'mediumDate' }}</span>
</div>
}
</div>
}
</main>
</div>
</div>
\ No newline at end of file
......@@ -4,11 +4,12 @@ import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
import { AuthService } from '../../../services/auth.service';
import { QuizService } from '../../../services/quiz.service';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-generate-quiz',
standalone: true,
imports: [CommonModule, FormsModule, RouterLink],
imports: [CommonModule, FormsModule, RouterLink, MatButtonModule],
templateUrl: './generate-quiz.html',
styleUrl: './generate-quiz.css'
})
......@@ -17,7 +18,9 @@ export class GenerateQuizComponent implements OnInit {
timer = 30;
selectedFile: File | null = null;
fileName = signal<string>('');
groups = signal<any[]>([]);
selectedGroups = signal<string[]>([]);
assignToAll = signal<boolean>(true);
loading = signal<boolean>(false);
success = signal<string>('');
error = signal<string>('');
......@@ -25,11 +28,18 @@ export class GenerateQuizComponent implements OnInit {
quizzes = signal<any[]>([]);
loadingQuizzes = signal<boolean>(true);
constructor(public authService: AuthService, private quizService: QuizService) {}
constructor(public authService: AuthService, private quizService: QuizService) { }
ngOnInit(): void {
console.log("Component loaded");
this.loadQuizzes();
this.quizService.getAdminGroups().subscribe({
next: (res) => {
this.groups.set(res.groups || []);
},
error: () => {
console.log("Failed to load groups");
}
});
}
loadQuizzes(): void {
......@@ -72,7 +82,8 @@ export class GenerateQuizComponent implements OnInit {
formData.append('title', this.title);
formData.append('timer', this.timer.toString());
formData.append('questionsFile', this.selectedFile);
formData.append('assignToAll', this.assignToAll().toString());
formData.append('assignedGroups', JSON.stringify(this.selectedGroups()));
this.quizService.createQuiz(formData).subscribe({
next: (res) => {
this.loading.set(false);
......@@ -98,6 +109,15 @@ export class GenerateQuizComponent implements OnInit {
});
}
}
onGroupToggle(groupName: string, event: any): void {
const selected = this.selectedGroups();
if (event.target.checked) {
this.selectedGroups.set([...selected, groupName]);
} else {
this.selectedGroups.set(selected.filter(g => g !== groupName));
}
}
logout(): void {
this.authService.logout();
......
......@@ -148,7 +148,7 @@
.main-content {
flex: 1;
margin-left: 260px;
margin-left: 0px;
padding: 24px 32px;
}
......
......@@ -31,8 +31,8 @@ export class AuthService {
}
private loadUserFromStorage(): void {
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
const token = sessionStorage.getItem('token');
const user = sessionStorage.getItem('user');
if (token && user) {
this.currentUser.set(JSON.parse(user));
this.isLoggedIn.set(true);
......@@ -49,7 +49,7 @@ export class AuthService {
}
logout(): void {
const token = localStorage.getItem('token');
const token = sessionStorage.getItem('token');
if (token) {
this.http.post(`${this.apiUrl}/logout`, {}).subscribe({
complete: () => this.clearAuth(),
......@@ -62,23 +62,23 @@ export class AuthService {
private handleAuth(res: AuthResponse): void {
if (res.token) {
localStorage.setItem('token', res.token);
sessionStorage.setItem('token', res.token);
}
localStorage.setItem('user', JSON.stringify(res.user));
sessionStorage.setItem('user', JSON.stringify(res.user));
this.currentUser.set(res.user);
this.isLoggedIn.set(true);
}
private clearAuth(): void {
localStorage.removeItem('token');
localStorage.removeItem('user');
sessionStorage.removeItem('token');
sessionStorage.removeItem('user');
this.currentUser.set(null);
this.isLoggedIn.set(false);
this.router.navigate(['/login']);
}
getToken(): string | null {
return localStorage.getItem('token');
return sessionStorage.getItem('token');
}
getUserRole(): string | null {
......
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class QuizService {
private adminUrl = 'http://localhost:5000/api/admin';
private hrUrl = 'http://localhost:5000/api/hr';
private candidateUrl = 'http://localhost:5000/api/candidate';
@Injectable({
providedIn: 'root'
})
export class QuizService {
private adminUrl = 'http://localhost:5000/api/admin';
private hrUrl = 'http://localhost:5000/api/hr';
private candidateUrl = 'http://localhost:5000/api/candidate';
constructor(private http: HttpClient) {}
constructor(private http: HttpClient) {}
// ========== ADMIN ENDPOINTS ==========
// ========== ADMIN ENDPOINTS ==========
getAdminStats(): Observable<any> {
return this.http.get(`${this.adminUrl}/stats`);
}
getAdminStats(): Observable<any> {
return this.http.get(`${this.adminUrl}/stats`);
}
getUsers(role?: string): Observable<any> {
const url = role ? `${this.adminUrl}/users?role=${role}` : `${this.adminUrl}/users`;
return this.http.get(url);
}
getUsers(role?: string): Observable<any> {
const url = role ? `${this.adminUrl}/users?role=${role}` : `${this.adminUrl}/users`;
return this.http.get(url);
}
getLoggedInUsers(): Observable<any> {
return this.http.get(`${this.adminUrl}/users/logged-in`);
}
getLoggedInUsers(): Observable<any> {
return this.http.get(`${this.adminUrl}/users/logged-in`);
}
createHRUser(data: { name: string; email: string; password: string }): Observable<any> {
return this.http.post(`${this.adminUrl}/users/create-hr`, data);
}
createHRUser(data: { name: string; email: string; password: string }): Observable<any> {
return this.http.post(`${this.adminUrl}/users/create-hr`, data);
}
deleteUser(userId: string): Observable<any> {
return this.http.delete(`${this.adminUrl}/users/${userId}`);
}
deleteUser(userId: string): Observable<any> {
return this.http.delete(`${this.adminUrl}/users/${userId}`);
}
getUserHistory(userId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/users/${userId}/history`);
}
getUserHistory(userId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/users/${userId}/history`);
}
getSubmissionDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/submissions/${submissionId}`);
}
getSubmissionDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/submissions/${submissionId}`);
}
createQuiz(formData: FormData): Observable<any> {
return this.http.post(`${this.adminUrl}/quiz/create`, formData);
}
createQuiz(formData: FormData): Observable<any> {
return this.http.post(`${this.adminUrl}/quiz/create`, formData);
}
createQuizManual(data: any): Observable<any> {
return this.http.post(`${this.adminUrl}/quiz/create-manual`, data);
}
createQuizManual(data: any): Observable<any> {
return this.http.post(`${this.adminUrl}/quiz/create-manual`, data);
}
getAdminQuizzes(): Observable<any> {
return this.http.get(`${this.adminUrl}/quizzes`);
}
getAdminQuizzes(): Observable<any> {
return this.http.get(`${this.adminUrl}/quizzes`);
}
getAdminQuiz(quizId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/quiz/${quizId}`);
}
getAdminQuiz(quizId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/quiz/${quizId}`);
}
updateQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.adminUrl}/quiz/${quizId}`, data);
}
updateQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.adminUrl}/quiz/${quizId}`, data);
}
assignQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.adminUrl}/quiz/${quizId}/assign`, data);
}
assignQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.adminUrl}/quiz/${quizId}/assign`, data);
}
deleteQuiz(quizId: string): Observable<any> {
return this.http.delete(`${this.adminUrl}/quiz/${quizId}`);
}
deleteQuiz(quizId: string): Observable<any> {
return this.http.delete(`${this.adminUrl}/quiz/${quizId}`);
}
getAdminCategories(): Observable<any> {
return this.http.get(`${this.adminUrl}/categories`);
}
getAdminCategories(): Observable<any> {
return this.http.get(`${this.adminUrl}/categories`);
}
getAdminGroups(): Observable<any> {
return this.http.get(`${this.adminUrl}/groups`);
}
getAdminGroups(): Observable<any> {
return this.http.get(`${this.adminUrl}/groups`);
}
// ========== HR ENDPOINTS ==========
// ========== HR ENDPOINTS ==========
getHRStats(): Observable<any> {
return this.http.get(`${this.hrUrl}/stats`);
}
getHRStats(): Observable<any> {
return this.http.get(`${this.hrUrl}/stats`);
}
getHRQuizzes(): Observable<any> {
return this.http.get(`${this.hrUrl}/quizzes`);
}
getHRQuizzes(): Observable<any> {
return this.http.get(`${this.hrUrl}/quizzes`);
}
getHRQuiz(quizId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/quiz/${quizId}`);
}
getHRQuiz(quizId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/quiz/${quizId}`);
}
createHRQuiz(formData: FormData): Observable<any> {
return this.http.post(`${this.hrUrl}/quiz/create`, formData);
}
createHRQuiz(formData: FormData): Observable<any> {
return this.http.post(`${this.hrUrl}/quiz/create`, formData);
}
createHRQuizManual(data: any): Observable<any> {
return this.http.post(`${this.hrUrl}/quiz/create-manual`, data);
}
createHRQuizManual(data: any): Observable<any> {
return this.http.post(`${this.hrUrl}/quiz/create-manual`, data);
}
updateHRQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.hrUrl}/quiz/${quizId}`, data);
}
updateHRQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.hrUrl}/quiz/${quizId}`, data);
}
deleteHRQuiz(quizId: string): Observable<any> {
return this.http.delete(`${this.hrUrl}/quiz/${quizId}`);
}
deleteHRQuiz(quizId: string): Observable<any> {
return this.http.delete(`${this.hrUrl}/quiz/${quizId}`);
}
getHRCandidates(): Observable<any> {
return this.http.get(`${this.hrUrl}/candidates`);
}
getHRCandidates(): Observable<any> {
return this.http.get(`${this.hrUrl}/candidates`);
}
getHRCandidateHistory(userId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/candidates/${userId}/history`);
}
getHRCandidateHistory(userId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/candidates/${userId}/history`);
}
getHRSubmissionDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/submissions/${submissionId}`);
}
getHRSubmissionDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/submissions/${submissionId}`);
}
getHRCategories(): Observable<any> {
return this.http.get(`${this.hrUrl}/categories`);
}
getHRCategories(): Observable<any> {
return this.http.get(`${this.hrUrl}/categories`);
}
getHRGroups(): Observable<any> {
return this.http.get(`${this.hrUrl}/groups`);
}
getHRGroups(): Observable<any> {
return this.http.get(`${this.hrUrl}/groups`);
}
// ========== CANDIDATE ENDPOINTS ==========
// ========== CANDIDATE ENDPOINTS ==========
getAvailableQuizzes(): Observable<any> {
return this.http.get(`${this.candidateUrl}/quizzes`);
}
getAvailableQuizzes(): Observable<any> {
return this.http.get(`${this.candidateUrl}/quizzes`);
}
getQuizForTaking(quizId: string): Observable<any> {
return this.http.get(`${this.candidateUrl}/quiz/${quizId}`);
}
getQuizForTaking(quizId: string): Observable<any> {
return this.http.get(`${this.candidateUrl}/quiz/${quizId}`);
}
submitQuiz(quizId: string, answers: any[], timeTaken: number): Observable<any> {
return this.http.post(`${this.candidateUrl}/quiz/${quizId}/submit`, { answers, timeTaken });
}
submitQuiz(quizId: string, answers: any[], timeTaken: number): Observable<any> {
return this.http.post(`${this.candidateUrl}/quiz/${quizId}/submit`, { answers, timeTaken });
}
getCandidateProfile(): Observable<any> {
return this.http.get(`${this.candidateUrl}/profile`);
}
getCandidateProfile(): Observable<any> {
return this.http.get(`${this.candidateUrl}/profile`);
}
getResultDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.candidateUrl}/results/${submissionId}`);
getResultDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.candidateUrl}/results/${submissionId}`);
}
}
}
......@@ -33,6 +33,21 @@ export class ThemeService {
}
private applyTheme(theme: ThemeMode): void {
// Existing (your custom theme)
document.documentElement.setAttribute('data-theme', theme);
// NEW (Material theme)
const body = document.body;
body.classList.remove('azure-theme', 'magenta-theme', 'cyan-theme', 'rose-theme');
// Map your themes → material themes
const themeMap: Record<ThemeMode, string> = {
light: 'azure-theme',
dark: 'magenta-theme',
blue: 'cyan-theme'
};
body.classList.add(themeMap[theme] || 'azure-theme');
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment