Sửa lỗi quản lí users, chỉnh sửa users

This commit is contained in:
2026-06-08 21:30:03 +07:00
parent 6dfc811292
commit fadfb6ba09
7 changed files with 376 additions and 35 deletions
+18 -2
View File
@@ -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
+60 -6
View File
@@ -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({});
+15 -6
View File
@@ -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
}); });
+80
View File
@@ -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
View File
@@ -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
View File
@@ -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 Đăng 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 hiển thị kho ảnh/media của người dùng * Tải 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
+1 -1
View File
@@ -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());