add changes
This commit is contained in:
288
backend/controllers/auth.controller.js
Normal file
288
backend/controllers/auth.controller.js
Normal file
@@ -0,0 +1,288 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { User, GuestSession, QuizSession, sequelize } = require('../models');
|
||||
const config = require('../config/config');
|
||||
|
||||
/**
|
||||
* @desc Register a new user
|
||||
* @route POST /api/auth/register
|
||||
* @access Public
|
||||
*/
|
||||
exports.register = async (req, res) => {
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
const { username, email, password, guestSessionId } = req.body;
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
[sequelize.Sequelize.Op.or]: [
|
||||
{ email: email.toLowerCase() },
|
||||
{ username: username.toLowerCase() }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
await transaction.rollback();
|
||||
if (existingUser.email === email.toLowerCase()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Email already registered'
|
||||
});
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Username already taken'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create new user (password will be hashed by beforeCreate hook)
|
||||
const user = await User.create({
|
||||
id: uuidv4(),
|
||||
username: username.toLowerCase(),
|
||||
email: email.toLowerCase(),
|
||||
password: password,
|
||||
role: 'user',
|
||||
is_active: true
|
||||
}, { transaction });
|
||||
|
||||
// Handle guest session migration if provided
|
||||
let migratedData = null;
|
||||
if (guestSessionId) {
|
||||
try {
|
||||
const guestSession = await GuestSession.findOne({
|
||||
where: { guest_id: guestSessionId }
|
||||
});
|
||||
|
||||
if (guestSession && !guestSession.is_converted) {
|
||||
// Migrate quiz sessions from guest to user
|
||||
const migratedSessions = await QuizSession.update(
|
||||
{
|
||||
user_id: user.id,
|
||||
guest_session_id: null
|
||||
},
|
||||
{
|
||||
where: { guest_session_id: guestSession.id },
|
||||
transaction
|
||||
}
|
||||
);
|
||||
|
||||
// Mark guest session as converted
|
||||
await guestSession.update({
|
||||
is_converted: true,
|
||||
converted_user_id: user.id,
|
||||
converted_at: new Date()
|
||||
}, { transaction });
|
||||
|
||||
// Recalculate user stats from migrated sessions
|
||||
const quizSessions = await QuizSession.findAll({
|
||||
where: {
|
||||
user_id: user.id,
|
||||
status: 'completed'
|
||||
},
|
||||
transaction
|
||||
});
|
||||
|
||||
let totalQuizzes = quizSessions.length;
|
||||
let quizzesPassed = 0;
|
||||
let totalQuestionsAnswered = 0;
|
||||
let correctAnswers = 0;
|
||||
|
||||
quizSessions.forEach(session => {
|
||||
if (session.is_passed) quizzesPassed++;
|
||||
totalQuestionsAnswered += session.questions_answered || 0;
|
||||
correctAnswers += session.correct_answers || 0;
|
||||
});
|
||||
|
||||
// Update user stats
|
||||
await user.update({
|
||||
total_quizzes: totalQuizzes,
|
||||
quizzes_passed: quizzesPassed,
|
||||
total_questions_answered: totalQuestionsAnswered,
|
||||
correct_answers: correctAnswers
|
||||
}, { transaction });
|
||||
|
||||
migratedData = {
|
||||
quizzes: migratedSessions[0],
|
||||
stats: {
|
||||
totalQuizzes,
|
||||
quizzesPassed,
|
||||
accuracy: totalQuestionsAnswered > 0
|
||||
? ((correctAnswers / totalQuestionsAnswered) * 100).toFixed(2)
|
||||
: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (guestError) {
|
||||
// Log error but don't fail registration
|
||||
console.error('Guest migration error:', guestError.message);
|
||||
// Continue with registration even if migration fails
|
||||
}
|
||||
}
|
||||
|
||||
// Commit transaction before generating JWT
|
||||
await transaction.commit();
|
||||
|
||||
// Generate JWT token (after commit to avoid rollback issues)
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
role: user.role
|
||||
},
|
||||
config.jwt.secret,
|
||||
{ expiresIn: config.jwt.expire }
|
||||
);
|
||||
|
||||
// Return user data (exclude password)
|
||||
const userData = user.toSafeJSON();
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'User registered successfully',
|
||||
data: {
|
||||
user: userData,
|
||||
token,
|
||||
migratedData
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// Only rollback if transaction is still active
|
||||
if (!transaction.finished) {
|
||||
await transaction.rollback();
|
||||
}
|
||||
console.error('Registration error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error registering user',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Login user
|
||||
* @route POST /api/auth/login
|
||||
* @access Public
|
||||
*/
|
||||
exports.login = async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user by email
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email.toLowerCase(),
|
||||
is_active: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isPasswordValid = await user.comparePassword(password);
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last_login
|
||||
await user.update({ last_login: new Date() });
|
||||
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
role: user.role
|
||||
},
|
||||
config.jwt.secret,
|
||||
{ expiresIn: config.jwt.expire }
|
||||
);
|
||||
|
||||
// Return user data (exclude password)
|
||||
const userData = user.toSafeJSON();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
data: {
|
||||
user: userData,
|
||||
token
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error logging in',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Logout user (client-side token removal)
|
||||
* @route POST /api/auth/logout
|
||||
* @access Public
|
||||
*/
|
||||
exports.logout = async (req, res) => {
|
||||
// Since we're using JWT (stateless), logout is handled client-side
|
||||
// by removing the token from storage
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Logout successful. Please remove token from client storage.'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Verify JWT token and return user info
|
||||
* @route GET /api/auth/verify
|
||||
* @access Private
|
||||
*/
|
||||
exports.verifyToken = async (req, res) => {
|
||||
try {
|
||||
// User is already attached to req by verifyToken middleware
|
||||
const user = await User.findByPk(req.user.userId);
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found or inactive'
|
||||
});
|
||||
}
|
||||
|
||||
// Return user data (exclude password)
|
||||
const userData = user.toSafeJSON();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Token valid',
|
||||
data: {
|
||||
user: userData
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Token verification error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error verifying token',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user