add a lot of changes
This commit is contained in:
1180
backend/controllers/quiz.controller.js
Normal file
1180
backend/controllers/quiz.controller.js
Normal file
File diff suppressed because it is too large
Load Diff
699
backend/controllers/user.controller.js
Normal file
699
backend/controllers/user.controller.js
Normal file
@@ -0,0 +1,699 @@
|
||||
const { User, QuizSession, Category, sequelize } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* Get user dashboard with stats, recent sessions, and category performance
|
||||
* GET /api/users/:userId/dashboard
|
||||
*/
|
||||
exports.getUserDashboard = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const requestUserId = req.user.userId;
|
||||
|
||||
// Validate UUID format first
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
if (!uuidRegex.test(userId)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid user ID format'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user exists first (before authorization)
|
||||
const user = await User.findByPk(userId, {
|
||||
attributes: [
|
||||
'id', 'username', 'email', 'role',
|
||||
'totalQuizzes', 'quizzesPassed', 'totalQuestionsAnswered', 'correctAnswers',
|
||||
'currentStreak', 'longestStreak', 'lastQuizDate',
|
||||
'profileImage', 'createdAt'
|
||||
]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Authorization: Users can only access their own dashboard
|
||||
if (userId !== requestUserId) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: 'You do not have permission to access this dashboard'
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate overall accuracy
|
||||
const overallAccuracy = user.totalQuestionsAnswered > 0
|
||||
? Math.round((user.correctAnswers / user.totalQuestionsAnswered) * 100)
|
||||
: 0;
|
||||
|
||||
// Calculate pass rate
|
||||
const passRate = user.totalQuizzes > 0
|
||||
? Math.round((user.quizzesPassed / user.totalQuizzes) * 100)
|
||||
: 0;
|
||||
|
||||
// Get recent quiz sessions (last 10 completed)
|
||||
const recentSessions = await QuizSession.findAll({
|
||||
where: {
|
||||
userId,
|
||||
status: {
|
||||
[Op.in]: ['completed', 'timeout']
|
||||
}
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Category,
|
||||
as: 'category',
|
||||
attributes: ['id', 'name', 'slug', 'icon', 'color']
|
||||
}
|
||||
],
|
||||
attributes: [
|
||||
'id', 'categoryId', 'quizType', 'difficulty', 'status',
|
||||
'score', 'totalPoints', 'isPassed',
|
||||
'questionsAnswered', 'correctAnswers', 'timeSpent',
|
||||
'startedAt', 'completedAt'
|
||||
],
|
||||
order: [['completedAt', 'DESC']],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
// Format recent sessions
|
||||
const formattedRecentSessions = recentSessions.map(session => {
|
||||
const earned = parseFloat(session.score) || 0;
|
||||
const total = parseFloat(session.totalPoints) || 0;
|
||||
const percentage = total > 0 ? Math.round((earned / total) * 100) : 0;
|
||||
|
||||
return {
|
||||
id: session.id,
|
||||
category: {
|
||||
id: session.category.id,
|
||||
name: session.category.name,
|
||||
slug: session.category.slug,
|
||||
icon: session.category.icon,
|
||||
color: session.category.color
|
||||
},
|
||||
quizType: session.quizType,
|
||||
difficulty: session.difficulty,
|
||||
status: session.status,
|
||||
score: {
|
||||
earned,
|
||||
total,
|
||||
percentage
|
||||
},
|
||||
isPassed: session.isPassed,
|
||||
questionsAnswered: session.questionsAnswered,
|
||||
correctAnswers: session.correctAnswers,
|
||||
accuracy: session.questionsAnswered > 0
|
||||
? Math.round((session.correctAnswers / session.questionsAnswered) * 100)
|
||||
: 0,
|
||||
timeSpent: session.timeSpent,
|
||||
completedAt: session.completedAt
|
||||
};
|
||||
});
|
||||
|
||||
// Get category-wise performance
|
||||
const categoryPerformance = await sequelize.query(`
|
||||
SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
c.slug,
|
||||
c.icon,
|
||||
c.color,
|
||||
COUNT(qs.id) as quizzes_taken,
|
||||
SUM(CASE WHEN qs.is_passed = 1 THEN 1 ELSE 0 END) as quizzes_passed,
|
||||
ROUND(AVG((qs.score / NULLIF(qs.total_points, 0)) * 100), 0) as average_score,
|
||||
SUM(qs.questions_answered) as total_questions,
|
||||
SUM(qs.correct_answers) as correct_answers,
|
||||
ROUND(
|
||||
(SUM(qs.correct_answers) / NULLIF(SUM(qs.questions_answered), 0)) * 100,
|
||||
0
|
||||
) as accuracy,
|
||||
MAX(qs.completed_at) as last_attempt
|
||||
FROM categories c
|
||||
INNER JOIN quiz_sessions qs ON c.id = qs.category_id
|
||||
WHERE qs.user_id = :userId
|
||||
AND qs.status IN ('completed', 'timeout')
|
||||
GROUP BY c.id, c.name, c.slug, c.icon, c.color
|
||||
ORDER BY quizzes_taken DESC, accuracy DESC
|
||||
`, {
|
||||
replacements: { userId },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
// Format category performance
|
||||
const formattedCategoryPerformance = categoryPerformance.map(cat => ({
|
||||
category: {
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
slug: cat.slug,
|
||||
icon: cat.icon,
|
||||
color: cat.color
|
||||
},
|
||||
stats: {
|
||||
quizzesTaken: parseInt(cat.quizzes_taken) || 0,
|
||||
quizzesPassed: parseInt(cat.quizzes_passed) || 0,
|
||||
passRate: cat.quizzes_taken > 0
|
||||
? Math.round((cat.quizzes_passed / cat.quizzes_taken) * 100)
|
||||
: 0,
|
||||
averageScore: parseInt(cat.average_score) || 0,
|
||||
totalQuestions: parseInt(cat.total_questions) || 0,
|
||||
correctAnswers: parseInt(cat.correct_answers) || 0,
|
||||
accuracy: parseInt(cat.accuracy) || 0
|
||||
},
|
||||
lastAttempt: cat.last_attempt
|
||||
}));
|
||||
|
||||
// Get activity summary (last 30 days)
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
const recentActivity = await QuizSession.findAll({
|
||||
where: {
|
||||
userId,
|
||||
status: {
|
||||
[Op.in]: ['completed', 'timeout']
|
||||
},
|
||||
completedAt: {
|
||||
[Op.gte]: thirtyDaysAgo
|
||||
}
|
||||
},
|
||||
attributes: [
|
||||
[sequelize.fn('DATE', sequelize.col('completed_at')), 'date'],
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'quizzes_completed']
|
||||
],
|
||||
group: [sequelize.fn('DATE', sequelize.col('completed_at'))],
|
||||
order: [[sequelize.fn('DATE', sequelize.col('completed_at')), 'DESC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// Calculate streak status
|
||||
const today = new Date().toDateString();
|
||||
const lastActive = user.lastQuizDate?.toDateString();
|
||||
const isActiveToday = lastActive === today;
|
||||
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const wasActiveYesterday = lastActive === yesterday.toDateString();
|
||||
|
||||
let streakStatus = 'inactive';
|
||||
if (isActiveToday) {
|
||||
streakStatus = 'active';
|
||||
} else if (wasActiveYesterday) {
|
||||
streakStatus = 'at-risk'; // User needs to complete a quiz today to maintain streak
|
||||
}
|
||||
|
||||
// Build response
|
||||
const response = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
profileImage: user.profileImage,
|
||||
memberSince: user.createdAt
|
||||
},
|
||||
stats: {
|
||||
totalQuizzes: user.totalQuizzes,
|
||||
quizzesPassed: user.quizzesPassed,
|
||||
passRate,
|
||||
totalQuestionsAnswered: user.totalQuestionsAnswered,
|
||||
correctAnswers: user.correctAnswers,
|
||||
overallAccuracy,
|
||||
currentStreak: user.currentStreak,
|
||||
longestStreak: user.longestStreak,
|
||||
streakStatus,
|
||||
lastActiveDate: user.lastQuizDate
|
||||
},
|
||||
recentSessions: formattedRecentSessions,
|
||||
categoryPerformance: formattedCategoryPerformance,
|
||||
recentActivity: recentActivity.map(activity => ({
|
||||
date: activity.date,
|
||||
quizzesCompleted: parseInt(activity.quizzes_completed) || 0
|
||||
}))
|
||||
},
|
||||
message: 'User dashboard retrieved successfully'
|
||||
};
|
||||
|
||||
return res.status(200).json(response);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting user dashboard:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'An error occurred while retrieving user dashboard',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get user quiz history with pagination, filtering, and sorting
|
||||
* GET /api/users/:userId/history
|
||||
*/
|
||||
exports.getQuizHistory = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const requestUserId = req.user.userId;
|
||||
|
||||
// Query parameters
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = Math.min(parseInt(req.query.limit) || 10, 50); // Max 50 per page
|
||||
const categoryId = req.query.category;
|
||||
const startDate = req.query.startDate;
|
||||
const endDate = req.query.endDate;
|
||||
const sortBy = req.query.sortBy || 'date'; // 'date' or 'score'
|
||||
const sortOrder = req.query.sortOrder || 'desc'; // 'asc' or 'desc'
|
||||
const status = req.query.status; // 'completed', 'timeout', 'abandoned'
|
||||
|
||||
// Validate UUID format
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
if (!uuidRegex.test(userId)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid user ID format'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
const user = await User.findByPk(userId, {
|
||||
attributes: ['id', 'username']
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Authorization: Users can only access their own history
|
||||
if (userId !== requestUserId) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: 'You do not have permission to access this quiz history'
|
||||
});
|
||||
}
|
||||
|
||||
// Build where clause
|
||||
const whereClause = {
|
||||
userId,
|
||||
status: {
|
||||
[Op.in]: ['completed', 'timeout', 'abandoned']
|
||||
}
|
||||
};
|
||||
|
||||
// Filter by category
|
||||
if (categoryId) {
|
||||
if (!uuidRegex.test(categoryId)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid category ID format'
|
||||
});
|
||||
}
|
||||
whereClause.categoryId = categoryId;
|
||||
}
|
||||
|
||||
// Filter by status
|
||||
if (status && ['completed', 'timeout', 'abandoned'].includes(status)) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
// Filter by date range
|
||||
if (startDate || endDate) {
|
||||
whereClause.completedAt = {};
|
||||
|
||||
if (startDate) {
|
||||
const start = new Date(startDate);
|
||||
if (isNaN(start.getTime())) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid start date format'
|
||||
});
|
||||
}
|
||||
whereClause.completedAt[Op.gte] = start;
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
const end = new Date(endDate);
|
||||
if (isNaN(end.getTime())) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid end date format'
|
||||
});
|
||||
}
|
||||
// Set to end of day
|
||||
end.setHours(23, 59, 59, 999);
|
||||
whereClause.completedAt[Op.lte] = end;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine sort field
|
||||
let orderField;
|
||||
if (sortBy === 'score') {
|
||||
orderField = 'score';
|
||||
} else {
|
||||
orderField = 'completedAt';
|
||||
}
|
||||
|
||||
const orderDirection = sortOrder.toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
|
||||
|
||||
// Calculate offset
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Get total count for pagination
|
||||
const totalCount = await QuizSession.count({ where: whereClause });
|
||||
const totalPages = Math.ceil(totalCount / limit);
|
||||
|
||||
// Get quiz sessions
|
||||
const sessions = await QuizSession.findAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Category,
|
||||
as: 'category',
|
||||
attributes: ['id', 'name', 'slug', 'icon', 'color']
|
||||
}
|
||||
],
|
||||
attributes: [
|
||||
'id',
|
||||
'categoryId',
|
||||
'quizType',
|
||||
'difficulty',
|
||||
'status',
|
||||
'score',
|
||||
'totalPoints',
|
||||
'isPassed',
|
||||
'questionsAnswered',
|
||||
'totalQuestions',
|
||||
'correctAnswers',
|
||||
'timeSpent',
|
||||
'timeLimit',
|
||||
'startedAt',
|
||||
'completedAt',
|
||||
'createdAt'
|
||||
],
|
||||
order: [[orderField, orderDirection]],
|
||||
limit,
|
||||
offset
|
||||
});
|
||||
|
||||
// Format sessions
|
||||
const formattedSessions = sessions.map(session => {
|
||||
const earned = parseFloat(session.score) || 0;
|
||||
const total = parseFloat(session.totalPoints) || 0;
|
||||
const percentage = total > 0 ? Math.round((earned / total) * 100) : 0;
|
||||
|
||||
return {
|
||||
id: session.id,
|
||||
category: session.category ? {
|
||||
id: session.category.id,
|
||||
name: session.category.name,
|
||||
slug: session.category.slug,
|
||||
icon: session.category.icon,
|
||||
color: session.category.color
|
||||
} : null,
|
||||
quizType: session.quizType,
|
||||
difficulty: session.difficulty,
|
||||
status: session.status,
|
||||
score: {
|
||||
earned,
|
||||
total,
|
||||
percentage
|
||||
},
|
||||
isPassed: session.isPassed,
|
||||
questions: {
|
||||
answered: session.questionsAnswered,
|
||||
total: session.totalQuestions,
|
||||
correct: session.correctAnswers,
|
||||
accuracy: session.questionsAnswered > 0
|
||||
? Math.round((session.correctAnswers / session.questionsAnswered) * 100)
|
||||
: 0
|
||||
},
|
||||
time: {
|
||||
spent: session.timeSpent,
|
||||
limit: session.timeLimit,
|
||||
percentage: session.timeLimit > 0
|
||||
? Math.round((session.timeSpent / session.timeLimit) * 100)
|
||||
: 0
|
||||
},
|
||||
startedAt: session.startedAt,
|
||||
completedAt: session.completedAt,
|
||||
createdAt: session.createdAt
|
||||
};
|
||||
});
|
||||
|
||||
// Build response
|
||||
const response = {
|
||||
success: true,
|
||||
data: {
|
||||
sessions: formattedSessions,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalItems: totalCount,
|
||||
itemsPerPage: limit,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPreviousPage: page > 1
|
||||
},
|
||||
filters: {
|
||||
category: categoryId || null,
|
||||
status: status || null,
|
||||
startDate: startDate || null,
|
||||
endDate: endDate || null
|
||||
},
|
||||
sorting: {
|
||||
sortBy,
|
||||
sortOrder
|
||||
}
|
||||
},
|
||||
message: 'Quiz history retrieved successfully'
|
||||
};
|
||||
|
||||
return res.status(200).json(response);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting quiz history:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'An error occurred while retrieving quiz history',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
* PUT /api/users/:userId
|
||||
*/
|
||||
exports.updateUserProfile = async (req, res) => {
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const requestUserId = req.user.userId;
|
||||
const { username, email, currentPassword, newPassword, profileImage } = req.body;
|
||||
|
||||
// Validate userId format
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
if (!uuidRegex.test(userId)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid user ID format'
|
||||
});
|
||||
}
|
||||
|
||||
// Find user
|
||||
const user = await User.findByPk(userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Authorization check - users can only update their own profile
|
||||
if (userId !== requestUserId) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: 'You are not authorized to update this profile'
|
||||
});
|
||||
}
|
||||
|
||||
// Track what fields are being updated
|
||||
const updates = {};
|
||||
const changedFields = [];
|
||||
|
||||
// Update username if provided
|
||||
if (username !== undefined && username !== user.username) {
|
||||
// Validate username
|
||||
if (typeof username !== 'string' || username.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Username cannot be empty'
|
||||
});
|
||||
}
|
||||
|
||||
if (username.length < 3 || username.length > 50) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Username must be between 3 and 50 characters'
|
||||
});
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9]+$/.test(username)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Username must contain only letters and numbers'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if username already exists
|
||||
const existingUser = await User.findOne({ where: { username } });
|
||||
if (existingUser && existingUser.id !== userId) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: 'Username already exists'
|
||||
});
|
||||
}
|
||||
|
||||
updates.username = username;
|
||||
changedFields.push('username');
|
||||
}
|
||||
|
||||
// Update email if provided
|
||||
if (email !== undefined && email !== user.email) {
|
||||
// Validate email format
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid email format'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
const existingUser = await User.findOne({ where: { email } });
|
||||
if (existingUser && existingUser.id !== userId) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: 'Email already exists'
|
||||
});
|
||||
}
|
||||
|
||||
updates.email = email;
|
||||
changedFields.push('email');
|
||||
}
|
||||
|
||||
// Update password if provided
|
||||
if (newPassword !== undefined) {
|
||||
// Verify current password is provided
|
||||
if (!currentPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Current password is required to change password'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate new password length first (before checking current password)
|
||||
if (newPassword.length < 6) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'New password must be at least 6 characters'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password is correct
|
||||
const isPasswordValid = await bcrypt.compare(currentPassword, user.password);
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
// Set new password (will be hashed by beforeUpdate hook in model)
|
||||
updates.password = newPassword;
|
||||
changedFields.push('password');
|
||||
}
|
||||
|
||||
// Update profile image if provided
|
||||
if (profileImage !== undefined && profileImage !== user.profileImage) {
|
||||
// Allow null or empty string to remove profile image
|
||||
if (profileImage === null || profileImage === '') {
|
||||
updates.profileImage = null;
|
||||
changedFields.push('profileImage');
|
||||
} else if (typeof profileImage === 'string') {
|
||||
// Basic URL validation (can be enhanced)
|
||||
if (profileImage.length > 255) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Profile image URL is too long (max 255 characters)'
|
||||
});
|
||||
}
|
||||
updates.profileImage = profileImage;
|
||||
changedFields.push('profileImage');
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Profile image must be a string URL'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any fields were provided for update
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No fields provided for update'
|
||||
});
|
||||
}
|
||||
|
||||
// Update user
|
||||
await user.update(updates);
|
||||
|
||||
// Fetch updated user (exclude password)
|
||||
const updatedUser = await User.findByPk(userId, {
|
||||
attributes: { exclude: ['password'] }
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
user: updatedUser,
|
||||
changedFields
|
||||
},
|
||||
message: 'Profile updated successfully'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating user profile:', error);
|
||||
|
||||
// Handle Sequelize validation errors
|
||||
if (error.name === 'SequelizeValidationError') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: error.errors[0]?.message || 'Validation error'
|
||||
});
|
||||
}
|
||||
|
||||
// Handle unique constraint errors
|
||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
const field = error.errors[0]?.path;
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: `${field} already exists`
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'An error occurred while updating profile',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user