Sửa lỗi quyền sở hữu scene
This commit is contained in:
+45
-33
@@ -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
@@ -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}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user