Sửa lỗi quản lí users, chỉnh sửa users
This commit is contained in:
+18
-2
@@ -2,6 +2,18 @@ const mongoose = require('mongoose');
|
|||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
const userSchema = new mongoose.Schema({
|
const userSchema = new mongoose.Schema({
|
||||||
|
fullName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
trim: true
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
trim: true,
|
||||||
|
lowercase: true
|
||||||
|
},
|
||||||
username: {
|
username: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -14,8 +26,12 @@ const userSchema = new mongoose.Schema({
|
|||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ['Chủ sở hữu', 'Thành viên'],
|
enum: ['admin', 'moderator', 'editor', 'user'],
|
||||||
default: 'Thành viên'
|
default: 'user'
|
||||||
|
},
|
||||||
|
agreedToRules: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
timestamps: true
|
timestamps: true
|
||||||
|
|||||||
@@ -569,7 +569,7 @@ router.delete('/scenes/:id', protect, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Kiểm tra quyền: Người tạo hoặc Admin
|
// Kiểm tra quyền: Người tạo hoặc Admin
|
||||||
const isAdmin = req.user.role === 'Chủ sở hữu' || req.user.role === 'admin';
|
const isAdmin = req.user.role === 'admin';
|
||||||
const isOwner = rootScene.createdBy.toString() === req.user._id.toString();
|
const isOwner = rootScene.createdBy.toString() === req.user._id.toString();
|
||||||
|
|
||||||
if (!isAdmin && !isOwner) {
|
if (!isAdmin && !isOwner) {
|
||||||
@@ -689,7 +689,7 @@ router.get('/me/scenes', protect, async (req, res) => {
|
|||||||
router.get('/me/assets', protect, async (req, res) => {
|
router.get('/me/assets', protect, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Sử dụng Aggregation để lấy Asset kèm thông tin Scene và Parent Scene
|
// Sử dụng Aggregation để lấy Asset kèm thông tin Scene và Parent Scene
|
||||||
const query = req.user.role === 'Chủ sở hữu' ? {} : { uploadedBy: req.user._id };
|
const query = (req.user.role === 'admin' || req.user.role === 'Chủ sở hữu') ? {} : { uploadedBy: req.user._id };
|
||||||
|
|
||||||
const assets = await Asset.aggregate([
|
const assets = await Asset.aggregate([
|
||||||
{ $match: query },
|
{ $match: query },
|
||||||
@@ -744,7 +744,7 @@ router.delete('/assets/:id', protect, async (req, res) => {
|
|||||||
|
|
||||||
// Kiểm tra quyền: Người upload hoặc Admin (Chủ sở hữu)
|
// Kiểm tra quyền: Người upload hoặc Admin (Chủ sở hữu)
|
||||||
const isOwner = asset.uploadedBy && asset.uploadedBy.toString() === req.user._id.toString();
|
const isOwner = asset.uploadedBy && asset.uploadedBy.toString() === req.user._id.toString();
|
||||||
const isAdmin = req.user.role === 'Chủ sở hữu' || req.user.role === 'admin';
|
const isAdmin = req.user.role === 'admin' || req.user.role === 'Chủ sở hữu';
|
||||||
|
|
||||||
if (!isOwner && !isAdmin) {
|
if (!isOwner && !isAdmin) {
|
||||||
return res.status(403).json({ message: 'Bạn không có quyền xóa tập tin này' });
|
return res.status(403).json({ message: 'Bạn không có quyền xóa tập tin này' });
|
||||||
@@ -783,17 +783,67 @@ router.delete('/assets/:id', protect, async (req, res) => {
|
|||||||
* @access Private (Admin)
|
* @access Private (Admin)
|
||||||
*/
|
*/
|
||||||
router.get('/admin/users', protect, async (req, res) => {
|
router.get('/admin/users', protect, async (req, res) => {
|
||||||
if (req.user.role !== 'Chủ sở hữu') {
|
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') {
|
||||||
return res.status(403).json({ message: 'Bạn không có quyền truy cập quản trị' });
|
return res.status(403).json({ message: 'Bạn không có quyền truy cập quản trị' });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const users = await User.find({}).select('-password').sort({ createdAt: -1 });
|
const users = await User.find({}).sort({ createdAt: -1 });
|
||||||
res.json(users);
|
res.json(users);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: error.message });
|
res.status(500).json({ message: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route PUT /api/admin/users/:id
|
||||||
|
* @desc Admin cập nhật thông tin người dùng (Quyền, Mật khẩu, Email, Họ tên)
|
||||||
|
*/
|
||||||
|
router.put('/admin/users/:id', protect, async (req, res) => {
|
||||||
|
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') return res.status(403).json({ message: 'Forbidden' });
|
||||||
|
try {
|
||||||
|
const { fullName, email, role, password } = req.body;
|
||||||
|
const user = await User.findById(req.params.id);
|
||||||
|
if (!user) return res.status(404).json({ message: 'Người dùng không tồn tại' });
|
||||||
|
|
||||||
|
// Cập nhật thông tin cơ bản
|
||||||
|
if (fullName) user.fullName = fullName;
|
||||||
|
if (email) user.email = email;
|
||||||
|
if (role) user.role = role;
|
||||||
|
|
||||||
|
// Nếu có nhập mật khẩu mới thì cập nhật (Middleware pre-save sẽ tự hash)
|
||||||
|
if (password && password.trim() !== '') {
|
||||||
|
user.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
res.json({ message: 'Cập nhật người dùng thành công' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route DELETE /api/admin/users/:id
|
||||||
|
* @desc Admin xóa vĩnh viễn người dùng
|
||||||
|
*/
|
||||||
|
router.delete('/admin/users/:id', protect, async (req, res) => {
|
||||||
|
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') return res.status(403).json({ message: 'Forbidden' });
|
||||||
|
try {
|
||||||
|
const user = await User.findById(req.params.id);
|
||||||
|
if (!user) return res.status(404).json({ message: 'Người dùng không tồn tại' });
|
||||||
|
|
||||||
|
if (user.role === 'admin' && user._id.toString() === req.user._id.toString()) {
|
||||||
|
return res.status(400).json({ message: 'Bạn không thể tự xóa chính mình' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lưu ý: Trong thực tế bạn có thể muốn xóa cả các Scene của user này
|
||||||
|
await User.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ message: 'Đã xóa người dùng vĩnh viễn' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route GET/PUT /api/system/settings
|
* @route GET/PUT /api/system/settings
|
||||||
* @desc Quản lý thiết lập hệ thống
|
* @desc Quản lý thiết lập hệ thống
|
||||||
@@ -810,7 +860,7 @@ router.get('/system/settings', optionalAuth, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.put('/system/settings', protect, async (req, res) => {
|
router.put('/system/settings', protect, async (req, res) => {
|
||||||
if (req.user.role !== 'Chủ sở hữu') return res.status(403).json({ message: 'Forbidden' });
|
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') return res.status(403).json({ message: 'Forbidden' });
|
||||||
try {
|
try {
|
||||||
const settings = await Setting.findOneAndUpdate({}, req.body, { new: true, upsert: true });
|
const settings = await Setting.findOneAndUpdate({}, req.body, { new: true, upsert: true });
|
||||||
res.json(settings);
|
res.json(settings);
|
||||||
@@ -826,6 +876,10 @@ router.put('/system/settings', protect, async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
router.post('/maintenance/reset-all', protect, async (req, res) => {
|
router.post('/maintenance/reset-all', protect, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') {
|
||||||
|
return res.status(403).json({ message: 'Chỉ Admin tối cao mới có quyền thực hiện thao tác này' });
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Xóa toàn bộ dữ liệu trong Database
|
// 1. Xóa toàn bộ dữ liệu trong Database
|
||||||
await Scene.deleteMany({});
|
await Scene.deleteMany({});
|
||||||
await Asset.deleteMany({});
|
await Asset.deleteMany({});
|
||||||
|
|||||||
@@ -11,26 +11,35 @@ const router = express.Router();
|
|||||||
*/
|
*/
|
||||||
router.post('/register', async (req, res) => {
|
router.post('/register', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { username, password, role } = req.body;
|
const { fullName, email, username, password, agreedToRules } = req.body;
|
||||||
|
|
||||||
// Check if user already exists
|
// Kiểm tra thông tin bắt buộc
|
||||||
const userExists = await User.findOne({ username });
|
if (!fullName || !email || !username || !password || agreedToRules === undefined) {
|
||||||
|
return res.status(400).json({ message: 'Vui lòng cung cấp đầy đủ thông tin đăng ký' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra xem username hoặc email đã tồn tại chưa
|
||||||
|
const userExists = await User.findOne({ $or: [{ username }, { email }] });
|
||||||
if (userExists) {
|
if (userExists) {
|
||||||
return res.status(400).json({ message: 'User already exists' });
|
const field = userExists.username === username ? 'Tên đăng nhập' : 'Email';
|
||||||
|
return res.status(400).json({ message: `${field} đã được sử dụng` });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is the very first user registering
|
// Check if this is the very first user registering
|
||||||
const userCount = await User.countDocuments();
|
const userCount = await User.countDocuments();
|
||||||
let finalRole = 'Thành viên';
|
let finalRole = 'user';
|
||||||
|
|
||||||
if (userCount === 0) {
|
if (userCount === 0) {
|
||||||
// First user to register in the system gets the supreme admin role
|
// First user to register in the system gets the supreme admin role
|
||||||
finalRole = 'Chủ sở hữu';
|
finalRole = 'admin';
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = new User({
|
const user = new User({
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
agreedToRules,
|
||||||
role: finalRole
|
role: finalRole
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ html, body {
|
|||||||
z-index: 1600; /* Above top-bar */
|
z-index: 1600; /* Above top-bar */
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
min-width: 180px; /* Đảm bảo không quá nhỏ */
|
min-width: 180px; /* Đảm bảo không quá nhỏ */
|
||||||
|
max-height: 550px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-dropdown.show {
|
#user-dropdown.show {
|
||||||
@@ -210,6 +212,54 @@ html, body {
|
|||||||
/* Nếu đây là tiêu đề "Welcome", nó sẽ bị ẩn bởi quy tắc bên dưới */
|
/* Nếu đây là tiêu đề "Welcome", nó sẽ bị ẩn bởi quy tắc bên dưới */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-tabs {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 10px 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-tab-btn {
|
||||||
|
flex: 1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #888;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-tab-btn.active {
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 2px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-error {
|
||||||
|
color: #ff4d4d;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0 20px 10px 20px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-submit-btn {
|
||||||
|
margin: 0 20px 15px;
|
||||||
|
width: calc(100% - 40px) !important;
|
||||||
|
background: #007bff !important;
|
||||||
|
color: white !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 5px 20px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
#user-dropdown input {
|
#user-dropdown input {
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px);
|
||||||
margin: 0 20px 10px 20px;
|
margin: 0 20px 10px 20px;
|
||||||
@@ -837,6 +887,36 @@ html, body {
|
|||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Admin User Management Table --- */
|
||||||
|
.admin-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-table th, .admin-table td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-table th {
|
||||||
|
color: #888;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-table input, .admin-table select {
|
||||||
|
background: rgba(255, 255, 255, 0.05) !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
padding: 5px !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Privacy Settings Enhancements --- */
|
/* --- Privacy Settings Enhancements --- */
|
||||||
.privacy-settings-btn {
|
.privacy-settings-btn {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
|||||||
+25
-7
@@ -30,13 +30,31 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="user-dropdown" class="dropdown-content">
|
<div id="user-dropdown" class="dropdown-content">
|
||||||
<div id="auth-guest">
|
<div id="auth-guest">
|
||||||
<h3>Login / Register</h3>
|
<div class="auth-tabs">
|
||||||
<input type="text" id="username-input" placeholder="Username" onkeydown="if(event.key === 'Enter') handleLogin()">
|
<button id="tab-login-btn" class="auth-tab-btn active" onclick="switchAuthMode('login')">Đăng nhập</button>
|
||||||
<input type="password" id="password-input" placeholder="Password" onkeydown="if(event.key === 'Enter') handleLogin()">
|
<button id="tab-register-btn" class="auth-tab-btn" onclick="switchAuthMode('register')">Đăng ký</button>
|
||||||
<p id="login-error-msg" style="color: #ff4d4d; font-size: 12px; margin: 0 20px 10px 20px; display: none;"></p>
|
</div>
|
||||||
<div class="btn-group">
|
|
||||||
<button onclick="handleLogin()">Login</button>
|
<!-- Login Form -->
|
||||||
<button onclick="handleRegister()">Register</button>
|
<div id="login-section">
|
||||||
|
<input type="text" id="username-input" placeholder="Tên đăng nhập" onkeydown="if(event.key === 'Enter') handleLogin()">
|
||||||
|
<input type="password" id="password-input" placeholder="Mật khẩu" onkeydown="if(event.key === 'Enter') handleLogin()">
|
||||||
|
<p id="login-error-msg" class="auth-error"></p>
|
||||||
|
<button onclick="handleLogin()" class="auth-submit-btn">Đăng nhập</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Register Form -->
|
||||||
|
<div id="register-section" style="display: none;">
|
||||||
|
<input type="text" id="reg-fullname" placeholder="Họ và tên đầy đủ">
|
||||||
|
<input type="email" id="reg-email" placeholder="Địa chỉ Email">
|
||||||
|
<input type="text" id="reg-username" placeholder="Tên đăng nhập">
|
||||||
|
<input type="password" id="reg-password" placeholder="Mật khẩu">
|
||||||
|
<input type="password" id="reg-confirm" placeholder="Nhắc lại mật khẩu">
|
||||||
|
<label class="rules-checkbox">
|
||||||
|
<input type="checkbox" id="reg-agree"> Đồng ý với <a href="#" style="color: #007bff;">quy định của trang</a>
|
||||||
|
</label>
|
||||||
|
<p id="reg-error-msg" class="auth-error"></p>
|
||||||
|
<button onclick="handleRegister()" class="auth-submit-btn">Đăng ký thành viên</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="auth-logged-in" style="display: none;">
|
<div id="auth-logged-in" style="display: none;">
|
||||||
|
|||||||
+177
-13
@@ -277,13 +277,13 @@ function checkAuthStatus() {
|
|||||||
const avatarInitials = document.getElementById('avatar-initials');
|
const avatarInitials = document.getElementById('avatar-initials');
|
||||||
|
|
||||||
if (token && username) {
|
if (token && username) {
|
||||||
authGuest.style.display = 'none'; // Hide login form
|
if (authGuest) authGuest.style.display = 'none'; // Hide login form
|
||||||
authLoggedIn.style.display = 'block'; // Show welcome message and buttons
|
authLoggedIn.style.display = 'block'; // Show welcome message and buttons
|
||||||
avatarInitials.innerText = username.charAt(0).toUpperCase();
|
avatarInitials.innerText = username.charAt(0).toUpperCase();
|
||||||
|
|
||||||
// Chỉ hiển thị các NÚT BẤM menu admin trong sidebar
|
// Hiển thị các nút dành cho admin (Chủ sở hữu/Admin tối cao)
|
||||||
const adminButtons = document.querySelectorAll('.dashboard-tabs .admin-only');
|
const adminButtons = document.querySelectorAll('.dashboard-tabs .admin-only');
|
||||||
if (role === 'Chủ sở hữu' || role === 'admin') {
|
if (role === 'admin' || role === 'Chủ sở hữu') {
|
||||||
adminButtons.forEach(btn => btn.style.display = 'block');
|
adminButtons.forEach(btn => btn.style.display = 'block');
|
||||||
} else {
|
} else {
|
||||||
adminButtons.forEach(btn => btn.style.display = 'none');
|
adminButtons.forEach(btn => btn.style.display = 'none');
|
||||||
@@ -344,15 +344,66 @@ async function handleLogin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chuyển đổi giữa chế độ Đăng nhập và Đăng ký trong dropdown
|
||||||
|
*/
|
||||||
|
window.switchAuthMode = function(mode) {
|
||||||
|
const loginSection = document.getElementById('login-section');
|
||||||
|
const registerSection = document.getElementById('register-section');
|
||||||
|
const loginBtn = document.getElementById('tab-login-btn');
|
||||||
|
const registerBtn = document.getElementById('tab-register-btn');
|
||||||
|
|
||||||
|
if (mode === 'login') {
|
||||||
|
loginSection.style.display = 'block';
|
||||||
|
registerSection.style.display = 'none';
|
||||||
|
loginBtn.classList.add('active');
|
||||||
|
registerBtn.classList.remove('active');
|
||||||
|
} else {
|
||||||
|
loginSection.style.display = 'none';
|
||||||
|
registerSection.style.display = 'block';
|
||||||
|
loginBtn.classList.remove('active');
|
||||||
|
registerBtn.classList.add('active');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles user registration
|
* Handles user registration
|
||||||
*/
|
*/
|
||||||
async function handleRegister() {
|
async function handleRegister() {
|
||||||
const username = document.getElementById('username-input').value.trim();
|
const errorMsg = document.getElementById('reg-error-msg');
|
||||||
const password = document.getElementById('password-input').value.trim();
|
if (errorMsg) errorMsg.style.display = 'none';
|
||||||
|
|
||||||
if (!username || !password) {
|
const fullName = document.getElementById('reg-fullname').value.trim();
|
||||||
alert('Please fill in both fields');
|
const email = document.getElementById('reg-email').value.trim();
|
||||||
|
const username = document.getElementById('reg-username').value.trim();
|
||||||
|
const password = document.getElementById('reg-password').value;
|
||||||
|
const confirm = document.getElementById('reg-confirm').value;
|
||||||
|
const agree = document.getElementById('reg-agree').checked;
|
||||||
|
|
||||||
|
// Kiểm tra các trường trống
|
||||||
|
if (!fullName || !email || !username || !password || !confirm) {
|
||||||
|
if (errorMsg) {
|
||||||
|
errorMsg.innerText = 'Vui lòng điền đầy đủ thông tin';
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra mật khẩu khớp nhau
|
||||||
|
if (password !== confirm) {
|
||||||
|
if (errorMsg) {
|
||||||
|
errorMsg.innerText = 'Mật khẩu xác nhận không khớp';
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra đồng ý quy định
|
||||||
|
if (!agree) {
|
||||||
|
if (errorMsg) {
|
||||||
|
errorMsg.innerText = 'Bạn phải đồng ý với quy định của trang';
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,15 +411,26 @@ async function handleRegister() {
|
|||||||
const response = await fetch(`${API_BASE_URL}/auth/register`, {
|
const response = await fetch(`${API_BASE_URL}/auth/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ username, password, role: 'Thành viên' })
|
body: JSON.stringify({
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
agreedToRules: agree,
|
||||||
|
role: 'Thành viên'
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!response.ok) throw new Error(data.message || 'Registration failed');
|
if (!response.ok) throw new Error(data.message || 'Đăng ký thất bại');
|
||||||
|
|
||||||
showNotification('Registration successful! You can now log in.', 'success');
|
showNotification('Đăng ký thành công! Bạn có thể đăng nhập ngay bây giờ.', 'success');
|
||||||
|
switchAuthMode('login'); // Tự động chuyển về tab đăng nhập sau khi thành công
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showNotification(error.message, 'error');
|
if (errorMsg) {
|
||||||
|
errorMsg.innerText = error.message;
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,7 +716,7 @@ async function loadScenes() {
|
|||||||
const ownerId = scene.createdBy?._id || scene.createdBy || scene.owner?._id || scene.owner;
|
const ownerId = scene.createdBy?._id || scene.createdBy || scene.owner?._id || scene.owner;
|
||||||
|
|
||||||
// Phân quyền: Admin hoặc Chủ sở hữu Scene
|
// Phân quyền: Admin hoặc Chủ sở hữu Scene
|
||||||
const isAdmin = userRole === 'Chủ sở hữu' || userRole === 'admin';
|
const isAdmin = userRole === 'admin' || userRole === 'Chủ sở hữu';
|
||||||
if (isAdmin || (currentUserId && ownerId && ownerId.toString() === currentUserId.toString())) {
|
if (isAdmin || (currentUserId && ownerId && ownerId.toString() === currentUserId.toString())) {
|
||||||
handleEditDeleteScene(scene);
|
handleEditDeleteScene(scene);
|
||||||
} else {
|
} else {
|
||||||
@@ -1312,6 +1374,105 @@ async function loadMyScenes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tải danh sách người dùng dành cho Admin tối cao
|
||||||
|
*/
|
||||||
|
async function loadAdminUsers() {
|
||||||
|
const token = localStorage.getItem('jwt');
|
||||||
|
const container = document.getElementById('admin-users-list');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = '<p>Đang tải danh sách người dùng...</p>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/admin/users`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
const users = await res.json();
|
||||||
|
if (!res.ok) throw new Error(users.message);
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<table class="admin-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Họ tên</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Quyền hạn</th>
|
||||||
|
<th>Reset Password</th>
|
||||||
|
<th>Thao tác</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
users.forEach(user => {
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td><input type="text" id="adm-fn-${user._id}" value="${user.fullName || ''}"></td>
|
||||||
|
<td><strong>${user.username}</strong></td>
|
||||||
|
<td><input type="email" id="adm-em-${user._id}" value="${user.email || ''}"></td>
|
||||||
|
<td>
|
||||||
|
<select id="adm-role-${user._id}">
|
||||||
|
<option value="user" ${user.role === 'user' ? 'selected' : ''}>User</option>
|
||||||
|
<option value="editor" ${user.role === 'editor' ? 'selected' : ''}>Editor</option>
|
||||||
|
<option value="moderator" ${user.role === 'moderator' ? 'selected' : ''}>Moderator</option>
|
||||||
|
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td><input type="password" id="adm-pw-${user._id}" placeholder="Mật khẩu mới"></td>
|
||||||
|
<td>
|
||||||
|
<button class="edit-btn-small" onclick="updateUserByAdmin('${user._id}')">Lưu</button>
|
||||||
|
<button class="delete-btn-small" onclick="deleteUserByAdmin('${user._id}')">Xóa</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += '</tbody></table>';
|
||||||
|
container.innerHTML = html;
|
||||||
|
} catch (e) {
|
||||||
|
container.innerHTML = `<p style="color:red">Lỗi: ${e.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.updateUserByAdmin = async function(userId) {
|
||||||
|
const token = localStorage.getItem('jwt');
|
||||||
|
const payload = {
|
||||||
|
fullName: document.getElementById(`adm-fn-${userId}`).value,
|
||||||
|
email: document.getElementById(`adm-em-${userId}`).value,
|
||||||
|
role: document.getElementById(`adm-role-${userId}`).value,
|
||||||
|
password: document.getElementById(`adm-pw-${userId}`).value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/admin/users/${userId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.message);
|
||||||
|
showNotification(data.message, 'success');
|
||||||
|
loadAdminUsers();
|
||||||
|
} catch (e) { showNotification(e.message, 'error'); }
|
||||||
|
};
|
||||||
|
|
||||||
|
window.deleteUserByAdmin = async function(userId) {
|
||||||
|
if (!confirm('Xóa vĩnh viễn người dùng này?')) return;
|
||||||
|
const token = localStorage.getItem('jwt');
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/admin/users/${userId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.message);
|
||||||
|
showNotification(data.message, 'success');
|
||||||
|
loadAdminUsers();
|
||||||
|
} catch (e) { showNotification(e.message, 'error'); }
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tải và hiển thị kho ảnh/media của người dùng
|
* Tải và hiển thị kho ảnh/media của người dùng
|
||||||
*/
|
*/
|
||||||
@@ -1373,7 +1534,7 @@ async function loadMyAssets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add delete button and its event listener
|
// Add delete button and its event listener
|
||||||
if (asset.uploadedBy === currentUserId || (isTrash && userRole === 'Chủ sở hữu')) {
|
if (asset.uploadedBy === currentUserId || (isTrash && (userRole === 'admin' || userRole === 'Chủ sở hữu'))) {
|
||||||
const deleteButton = document.createElement('button');
|
const deleteButton = document.createElement('button');
|
||||||
deleteButton.className = 'delete-btn-small';
|
deleteButton.className = 'delete-btn-small';
|
||||||
deleteButton.innerText = 'Xóa';
|
deleteButton.innerText = 'Xóa';
|
||||||
@@ -1764,6 +1925,9 @@ function openDashboardTab(tabName) {
|
|||||||
if (tabName === 'media-library') {
|
if (tabName === 'media-library') {
|
||||||
loadMyAssets();
|
loadMyAssets();
|
||||||
}
|
}
|
||||||
|
if (tabName === 'user-management') {
|
||||||
|
loadAdminUsers();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Đánh dấu nút tab được chọn là active
|
// Đánh dấu nút tab được chọn là active
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ function applyViewerSecurity() {
|
|||||||
const currentUserId = localStorage.getItem('userId');
|
const currentUserId = localStorage.getItem('userId');
|
||||||
|
|
||||||
// Phân quyền: Admin (Chủ sở hữu) hoặc Người tạo ra Scene này
|
// Phân quyền: Admin (Chủ sở hữu) hoặc Người tạo ra Scene này
|
||||||
const isAdmin = userRole === 'Chủ sở hữu' || userRole === 'admin';
|
const isAdmin = userRole === 'admin' || userRole === 'Chủ sở hữu';
|
||||||
const isAuthorized = isAdmin ||
|
const isAuthorized = isAdmin ||
|
||||||
(currentUserId && currentSceneOwnerId && currentUserId.toString() === currentSceneOwnerId.toString());
|
(currentUserId && currentSceneOwnerId && currentUserId.toString() === currentSceneOwnerId.toString());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user