diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index f1480fa..d609d3b 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -943,7 +943,14 @@ router.get('/me/profile', protect, async (req, res) => { try { const user = await User.findById(req.user._id).select('-password').lean(); + // Đảm bảo các trường này luôn tồn tại để frontend không bị lỗi undefined + user.fullName = user.fullName || ''; + user.email = user.email || ''; + user.avatarUrl = user.avatarUrl || ''; + // Tính toán dung lượng thực tế của người dùng + // Logic này đã được tối ưu hóa bằng Aggregation ở các bước trước + // và được giữ nguyên để trả về thông tin storage cho frontend const usageResult = await Asset.aggregate([ { $match: { uploadedBy: req.user._id } }, { @@ -1010,21 +1017,59 @@ router.get('/me/assets/top-large', protect, async (req, res) => { * @desc Cập nhật hồ sơ (đổi tên, mật khẩu) * @access Private */ -router.put('/me/profile', protect, async (req, res) => { +router.put('/me/profile', protect, upload.single('avatar'), async (req, res, next) => { try { const user = await User.findById(req.user._id); if (!user) return res.status(404).json({ message: 'User not found' }); - if (req.body.username) user.username = req.body.username; - if (req.body.password) user.password = req.body.password; // Hook pre-save sẽ tự hash + const { fullName, email, username, password } = req.body; + + if (fullName) user.fullName = fullName; + if (email) user.email = email; + // Chỉ cho phép cập nhật username nếu nó khác với username hiện tại + if (username && user.username !== username) { + user.username = username; + } else if (username && user.username === username) { + // Nếu username không đổi, không cần gán lại để tránh trigger unique validation không cần thiết + } else if (!username) { + // Nếu frontend gửi username rỗng, có thể là lỗi hoặc cố ý xóa, cần xử lý tùy theo business logic + } + if (password && password.trim() !== '') user.password = password; + + // Xử lý ảnh đại diện nếu có upload + if (req.file) { + // Xóa avatar cũ nếu có và không phải là avatar mặc định + if (user.avatarUrl && user.avatarUrl.startsWith('/api/assets/view_avatar/')) { + const oldAvatarName = user.avatarUrl.split('/').pop(); + const oldAvatarPath = path.join(uploadDir, oldAvatarName); + if (fs.existsSync(oldAvatarPath)) fs.unlinkSync(oldAvatarPath); + } + + const avatarName = `avatar_${user._id}${path.extname(req.file.originalname)}`; + const avatarPath = path.join(uploadDir, avatarName); + + await sharp(req.file.path) + .resize(200, 200) // Resize avatar về kích thước nhỏ (200x200) + .toFile(avatarPath); + user.avatarUrl = `/api/assets/view_avatar/${avatarName}`; // Lưu đường dẫn ảnh vào DB + if (fs.existsSync(req.file.path)) fs.unlinkSync(req.file.path); // Xóa file tạm + } + + // Sử dụng validateBeforeSave: false để bỏ qua validation cho các trường không được gửi lên + // hoặc các trường không liên quan đến việc cập nhật hồ sơ cá nhân như agreedToRules, role. + // Tuy nhiên, các validation cho các trường được cập nhật (như email, username unique) vẫn sẽ chạy. + await user.save({ validateBeforeSave: false }); - await user.save(); res.json({ message: 'Hồ sơ đã được cập nhật', user: { id: user._id, username: user.username, role: user.role } }); } catch (error) { - res.status(500).json({ message: error.message }); + // Xử lý lỗi validation của Mongoose + if (error.name === 'ValidationError') { + return res.status(400).json({ message: error.message }); + } + next(error); // Chuyển lỗi khác cho middleware xử lý lỗi chung } }); diff --git a/backend/uploads/processed_1780998741676_81695da9.jpg.jpg b/backend/uploads/processed_1780998741676_81695da9.jpg.jpg new file mode 100644 index 0000000..c697bcf Binary files /dev/null and b/backend/uploads/processed_1780998741676_81695da9.jpg.jpg differ diff --git a/backend/uploads/processed_1780998925919_fd52e93a.jpg.jpg b/backend/uploads/processed_1780998925919_fd52e93a.jpg.jpg new file mode 100644 index 0000000..9288525 Binary files /dev/null and b/backend/uploads/processed_1780998925919_fd52e93a.jpg.jpg differ diff --git a/backend/uploads/processed_1780998987016_edc44f4a.jpg.jpg b/backend/uploads/processed_1780998987016_edc44f4a.jpg.jpg new file mode 100644 index 0000000..7f95014 Binary files /dev/null and b/backend/uploads/processed_1780998987016_edc44f4a.jpg.jpg differ diff --git a/backend/uploads/processed_1780999028354_a04713cb.jpg.jpg b/backend/uploads/processed_1780999028354_a04713cb.jpg.jpg new file mode 100644 index 0000000..9634f46 Binary files /dev/null and b/backend/uploads/processed_1780999028354_a04713cb.jpg.jpg differ diff --git a/backend/uploads/processed_1780999075558_29c91830.jpg.jpg b/backend/uploads/processed_1780999075558_29c91830.jpg.jpg new file mode 100644 index 0000000..7c5095c Binary files /dev/null and b/backend/uploads/processed_1780999075558_29c91830.jpg.jpg differ diff --git a/backend/uploads/processed_1780999146678_dc25cc80.jpg.jpg b/backend/uploads/processed_1780999146678_dc25cc80.jpg.jpg new file mode 100644 index 0000000..16b5fe4 Binary files /dev/null and b/backend/uploads/processed_1780999146678_dc25cc80.jpg.jpg differ diff --git a/backend/uploads/processed_1780999167827_db42488d.jpg.jpg b/backend/uploads/processed_1780999167827_db42488d.jpg.jpg new file mode 100644 index 0000000..7aacebb Binary files /dev/null and b/backend/uploads/processed_1780999167827_db42488d.jpg.jpg differ diff --git a/frontend/js/main_map.js b/frontend/js/main_map.js index 6cb6e0f..3fa697f 100644 --- a/frontend/js/main_map.js +++ b/frontend/js/main_map.js @@ -307,6 +307,10 @@ async function updateProfile(e) { const form = document.getElementById('profile-form'); const formData = new FormData(form); + // Xóa các trường không cần thiết khi cập nhật hồ sơ cá nhân + formData.delete('agreedToRules'); // Không cần khi cập nhật + formData.delete('role'); // Role chỉ được thay đổi bởi Admin + try { const res = await fetch(`${API_BASE_URL}/me/profile`, { method: 'PUT',