Sửa lỗi quyền sở hữu scene

This commit is contained in:
2026-06-08 12:21:22 +07:00
parent d9ed8032d3
commit 90abf2418a
4 changed files with 61 additions and 45 deletions
+45 -33
View File
@@ -78,8 +78,9 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => {
return res.status(400).json({ message: 'Please upload a panorama image' }); return res.status(400).json({ message: 'Please upload a panorama image' });
} }
const latitude = Number(lat); // Đảm bảo ép kiểu Number tuyệt đối trước khi lưu DB
const longitude = Number(lng); const latitude = Number(lat) || 0;
const longitude = Number(lng) || 0;
if (isNaN(latitude) || isNaN(longitude)) { if (isNaN(latitude) || isNaN(longitude)) {
// Cleanup uploaded file on validation error // Cleanup uploaded file on validation error
@@ -136,7 +137,6 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => {
// 7. Save Scene to DB // 7. Save Scene to DB
const scene = new Scene({ const scene = new Scene({
name: title, name: title,
assetId: asset._id,
scene_url: processedFilePath, // Lưu đường dẫn ảnh trực tiếp scene_url: processedFilePath, // Lưu đường dẫn ảnh trực tiếp
gps: { gps: {
lat: latitude, lat: latitude,
@@ -260,9 +260,7 @@ router.post('/hotspots/create', protect, async (req, res) => {
const { parent_scene_id, target_scene_id, title, description, coordinates } = req.body; const { parent_scene_id, target_scene_id, title, description, coordinates } = req.body;
const parentScene = await Scene.findById(parent_scene_id); const parentScene = await Scene.findById(parent_scene_id);
// Phân quyền: Admin hoặc Người tạo Scene if (!parentScene || parentScene.createdBy.toString() !== req.user._id.toString()) {
const isAuthorized = req.user.role === 'Chủ sở hữu' || (parentScene && parentScene.createdBy.toString() === req.user._id.toString());
if (!parentScene || !isAuthorized) {
return res.status(403).json({ message: 'Không có quyền tạo hotspot cho scene này' }); return res.status(403).json({ message: 'Không có quyền tạo hotspot cho scene này' });
} }
@@ -272,8 +270,8 @@ router.post('/hotspots/create', protect, async (req, res) => {
title, title,
description, description,
coordinates: { coordinates: {
yaw: Number(coordinates.yaw), yaw: Number(coordinates?.yaw) || 0,
pitch: Number(coordinates.pitch) pitch: Number(coordinates?.pitch) || 0
}, },
is_auto_return: false is_auto_return: false
}); });
@@ -283,14 +281,10 @@ router.post('/hotspots/create', protect, async (req, res) => {
const targetScene = await Scene.findById(target_scene_id); const targetScene = await Scene.findById(target_scene_id);
if (targetScene) { if (targetScene) {
const reverseYaw = coordinates.yaw > 0 ? coordinates.yaw - 180 : coordinates.yaw + 180; const reverseYaw = coordinates.yaw > 0 ? coordinates.yaw - 180 : coordinates.yaw + 180;
// Fallback đa tầng cho tiêu đề quay lại
const backLabel = title || parentScene.name || parentScene.title || 'cảnh trước';
const reverseHotspot = new Hotspot({ const reverseHotspot = new Hotspot({
parent_scene_id: target_scene_id, parent_scene_id: target_scene_id,
target_scene_id: parent_scene_id, target_scene_id: parent_scene_id,
title: `Quay lại ${backLabel}`, title: `Quay lại ${parentScene.name}`,
coordinates: { yaw: reverseYaw, pitch: 0 }, coordinates: { yaw: reverseYaw, pitch: 0 },
is_auto_return: true is_auto_return: true
}); });
@@ -315,20 +309,13 @@ router.put('/hotspots/update/:id', protect, async (req, res) => {
if (!hotspot) return res.status(404).json({ message: 'Hotspot không tồn tại' }); if (!hotspot) return res.status(404).json({ message: 'Hotspot không tồn tại' });
const parentScene = await Scene.findById(hotspot.parent_scene_id); const parentScene = await Scene.findById(hotspot.parent_scene_id);
// Phân quyền Admin hoặc Owner if (parentScene.createdBy.toString() !== req.user._id.toString()) {
const isAuthorized = req.user.role === 'Chủ sở hữu' || (parentScene && parentScene.createdBy.toString() === req.user._id.toString());
if (!isAuthorized) {
return res.status(403).json({ message: 'Không có quyền cập nhật' }); return res.status(403).json({ message: 'Không có quyền cập nhật' });
} }
if (title) hotspot.title = title; if (title) hotspot.title = title;
if (description) hotspot.description = description; if (description) hotspot.description = description;
if (coordinates) { if (coordinates) hotspot.coordinates = coordinates;
hotspot.coordinates = {
yaw: Number(coordinates.yaw),
pitch: Number(coordinates.pitch)
};
}
await hotspot.save(); await hotspot.save();
res.json(hotspot); res.json(hotspot);
@@ -347,9 +334,7 @@ router.delete('/hotspots/delete/:id', protect, async (req, res) => {
if (!hotspot) return res.status(404).json({ message: 'Hotspot không tồn tại' }); if (!hotspot) return res.status(404).json({ message: 'Hotspot không tồn tại' });
const parentScene = await Scene.findById(hotspot.parent_scene_id); const parentScene = await Scene.findById(hotspot.parent_scene_id);
// Phân quyền Admin hoặc Owner if (parentScene.createdBy.toString() !== req.user._id.toString()) {
const isAuthorized = req.user.role === 'Chủ sở hữu' || (parentScene && parentScene.createdBy.toString() === req.user._id.toString());
if (!isAuthorized) {
return res.status(403).json({ message: 'Không có quyền xóa' }); return res.status(403).json({ message: 'Không có quyền xóa' });
} }
@@ -432,17 +417,46 @@ router.put('/scenes/:id', protect, uploadSinglePanorama, async (req, res) => {
const { title, privacy, sharedWithUsers, lat, lng } = req.body; const { title, privacy, sharedWithUsers, lat, lng } = req.body;
const scene = await Scene.findById(req.params.id); const scene = await Scene.findById(req.params.id);
// Phân quyền Admin hoặc Owner if (!scene || scene.createdBy.toString() !== req.user._id.toString()) {
const isAuthorized = req.user.role === 'Chủ sở hữu' || (scene && scene.createdBy.toString() === req.user._id.toString());
if (!scene || !isAuthorized) {
return res.status(403).json({ message: 'Not authorized' }); return res.status(403).json({ message: 'Not authorized' });
} }
const oldPrivacy = scene.privacy;
// Update basic info // Update basic info
scene.name = title || scene.name; scene.name = title || scene.name;
scene.privacy = privacy || scene.privacy; scene.privacy = privacy || scene.privacy;
if (lat) scene.gps.lat = Number(lat); if (lat) scene.gps.lat = parseFloat(lat);
if (lng) scene.gps.lng = Number(lng); if (lng) scene.gps.lng = parseFloat(lng);
// LOGIC ĐỒNG BỘ QUYỀN RIÊNG TƯ (CASCADING PRIVACY)
// Nếu quyền chia sẻ thay đổi, cập nhật toàn bộ các Scene con liên kết trực tiếp
if (privacy && privacy !== oldPrivacy) {
try {
const linkedHotspots = await Hotspot.find({ parent_scene_id: scene._id });
const targetSceneIds = linkedHotspots
.map(h => h.target_scene_id)
.filter(id => id && id.toString() !== scene._id.toString());
if (targetSceneIds.length > 0) {
for (const targetId of targetSceneIds) {
const updateData = { privacy: privacy };
// Nếu chuyển sang 'shared', đảm bảo scene con cũng có token riêng
if (privacy === 'shared') {
const target = await Scene.findById(targetId);
if (target && !target.shareToken) {
updateData.shareToken = crypto.randomBytes(24).toString('hex');
}
}
await Scene.updateOne({ _id: targetId }, { $set: updateData });
}
console.log(`[Privacy Sync] Cascaded ${privacy} status to ${targetSceneIds.length} linked scenes.`);
}
} catch (err) {
console.error("Lỗi khi đồng bộ quyền riêng tư cho các scene con:", err.message);
}
}
if (privacy === 'shared' && !scene.shareToken) { if (privacy === 'shared' && !scene.shareToken) {
scene.shareToken = crypto.randomBytes(24).toString('hex'); scene.shareToken = crypto.randomBytes(24).toString('hex');
@@ -481,9 +495,7 @@ router.put('/scenes/:id', protect, uploadSinglePanorama, async (req, res) => {
router.delete('/scenes/:id', protect, async (req, res) => { router.delete('/scenes/:id', protect, async (req, res) => {
try { try {
const scene = await Scene.findById(req.params.id); const scene = await Scene.findById(req.params.id);
// Phân quyền Admin hoặc Owner if (!scene || scene.createdBy.toString() !== req.user._id.toString()) {
const isAuthorized = req.user.role === 'Chủ sở hữu' || (scene && scene.createdBy.toString() === req.user._id.toString());
if (!scene || !isAuthorized) {
return res.status(403).json({ message: 'Not authorized' }); return res.status(403).json({ message: 'Not authorized' });
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

+16 -12
View File
@@ -107,10 +107,9 @@ function initMap() {
return; return;
} }
// Chỉ Admin (Chủ sở hữu) mới có quyền tạo Scene mới trực tiếp trên map qua contextmenu // Cho phép bất kỳ người dùng nào đã đăng nhập tạo Scene mới trên bản đồ
const userRole = localStorage.getItem('role'); const token = localStorage.getItem('jwt');
const isAdmin = userRole === 'Chủ sở hữu' || userRole === 'admin'; if (!token) return;
if (!isAdmin) return;
const { lat, lng } = e.latlng; const { lat, lng } = e.latlng;
openCreateSceneModal(lat, lng); openCreateSceneModal(lat, lng);
@@ -370,20 +369,25 @@ async function loadScenes() {
// Chỉ lặp qua danh sách Scene mẹ, lọc bỏ các hotspots trùng tọa độ // Chỉ lặp qua danh sách Scene mẹ, lọc bỏ các hotspots trùng tọa độ
scenes.forEach((scene) => { scenes.forEach((scene) => {
// Ép kiểu tọa độ về Number để tránh lỗi render bản đồ // 1. Kiểm tra tọa độ an toàn - Ngăn chặn treo map do NaN
const latNum = Number(scene.gps?.lat || scene.lat); const latNum = Number(scene.gps?.lat ?? scene.lat);
const lngNum = Number(scene.gps?.lng || scene.lng); const lngNum = Number(scene.gps?.lng ?? scene.lng);
if (isNaN(latNum) || isNaN(lngNum)) return; if (isNaN(latNum) || isNaN(lngNum)) {
console.error(`Bỏ qua Scene "${scene.name || scene.title}" do tọa độ lỗi:`, scene);
return;
}
// Logic lọc Ảnh mẹ: Mỗi tọa độ GPS chỉ tạo duy nhất 1 Marker đại diện // 2. Logic lọc Ảnh mẹ: Sửa lỗi typo coordKey (dùng latNum 2 lần)
const coordKey = `${latNum.toFixed(6)},${lngNum.toFixed(6)}`; const coordKey = `${latNum.toFixed(6)},${lngNum.toFixed(6)}`;
if (seenCoordinates.has(coordKey)) return; // Bỏ qua nếu tọa độ này đã có Marker if (seenCoordinates.has(coordKey)) return;
seenCoordinates.add(coordKey); seenCoordinates.add(coordKey);
// Kiểm tra an toàn dữ liệu từ MongoDB trước khi truy cập // 3. Truy cập Asset an toàn
const assetId = scene.assetId?._id || scene.assetId; const assetId = scene.assetId?._id || scene.assetId;
const sceneName = scene.name || scene.title; if (!assetId) return; // Bỏ qua nếu không có ảnh liên kết
const sceneName = scene.name || scene.title || "Untitled Scene";
let thumbUrl = `${API_BASE_URL}/assets/view/${assetId}`; let thumbUrl = `${API_BASE_URL}/assets/view/${assetId}`;
if (token) thumbUrl += `?token=${token}`; if (token) thumbUrl += `?token=${token}`;