Reset data bị lỗi, thêm hotspot
This commit is contained in:
+143
-88
@@ -7,6 +7,7 @@ const crypto = require('crypto');
|
||||
const User = require('../models/User');
|
||||
const Asset = require('../models/Asset');
|
||||
const Scene = require('../models/Scene');
|
||||
const Hotspot = require('../models/Hotspot'); // Giả định bạn đã tạo model mới
|
||||
|
||||
const { protect, optionalAuth } = require('../middlewares/authMiddleware');
|
||||
const { verifyReferer, setNoCacheHeaders } = require('../middlewares/securityMiddleware');
|
||||
@@ -77,8 +78,8 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => {
|
||||
return res.status(400).json({ message: 'Please upload a panorama image' });
|
||||
}
|
||||
|
||||
const latitude = parseFloat(lat);
|
||||
const longitude = parseFloat(lng);
|
||||
const latitude = Number(lat);
|
||||
const longitude = Number(lng);
|
||||
|
||||
if (isNaN(latitude) || isNaN(longitude)) {
|
||||
// Cleanup uploaded file on validation error
|
||||
@@ -134,11 +135,14 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => {
|
||||
|
||||
// 7. Save Scene to DB
|
||||
const scene = new Scene({
|
||||
title,
|
||||
name: title,
|
||||
assetId: asset._id,
|
||||
lat: latitude,
|
||||
lng: longitude,
|
||||
owner: req.user._id,
|
||||
scene_url: processedFilePath, // Lưu đường dẫn ảnh trực tiếp
|
||||
gps: {
|
||||
lat: latitude,
|
||||
lng: longitude
|
||||
},
|
||||
createdBy: req.user._id,
|
||||
privacy: privacy || 'private',
|
||||
shareToken,
|
||||
sharedWith: parsedSharedWith
|
||||
@@ -176,7 +180,7 @@ router.get('/scenes', optionalAuth, async (req, res) => {
|
||||
{ privacy: 'public' },
|
||||
{ privacy: 'member' },
|
||||
{ privacy: 'shared' }, // shareToken will be required to fetch panorama, but coordinates show on map
|
||||
{ owner: req.user._id },
|
||||
{ createdBy: req.user._id },
|
||||
{ sharedWith: req.user._id }
|
||||
]
|
||||
};
|
||||
@@ -191,8 +195,8 @@ router.get('/scenes', optionalAuth, async (req, res) => {
|
||||
}
|
||||
|
||||
const scenes = await Scene.find(query)
|
||||
.populate('owner', 'username')
|
||||
.populate('assetId', 'coordinates createdAt');
|
||||
.populate('createdBy', 'username')
|
||||
.lean();
|
||||
|
||||
console.log(`[Data Load] Đã tìm thấy ${scenes.length} scenes. Gửi phản hồi về Frontend...`);
|
||||
res.json(scenes);
|
||||
@@ -210,7 +214,7 @@ router.get('/scenes', optionalAuth, async (req, res) => {
|
||||
router.get('/scenes/:id', optionalAuth, async (req, res) => {
|
||||
try {
|
||||
const scene = await Scene.findById(req.params.id)
|
||||
.populate('owner', 'username')
|
||||
.populate('createdBy', 'username')
|
||||
.populate('assetId');
|
||||
|
||||
if (!scene) {
|
||||
@@ -220,7 +224,7 @@ router.get('/scenes/:id', optionalAuth, async (req, res) => {
|
||||
const hasAccess =
|
||||
scene.privacy === 'public' ||
|
||||
(scene.privacy === 'member' && req.user) ||
|
||||
(req.user && scene.owner._id.toString() === req.user._id.toString()) ||
|
||||
(req.user && scene.createdBy._id.toString() === req.user._id.toString()) ||
|
||||
(req.user && scene.sharedWith.includes(req.user._id)) ||
|
||||
(scene.privacy === 'shared' && req.query.token === scene.shareToken);
|
||||
|
||||
@@ -235,83 +239,130 @@ router.get('/scenes/:id', optionalAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* @route POST /api/scenes/:id/hotspots
|
||||
* @desc Add a new hotspot to a scene
|
||||
* @access Private (Owner only)
|
||||
* @route GET /api/hotspots/:scene_id
|
||||
* @desc Lấy toàn bộ danh sách hotspot của scene hiện tại
|
||||
*/
|
||||
router.post('/scenes/:id/hotspots', protect, async (req, res) => {
|
||||
router.get('/hotspots/:scene_id', async (req, res) => {
|
||||
try {
|
||||
const { hotspotId, pitch, yaw, text, description, targetSceneId } = req.body;
|
||||
const scene = await Scene.findById(req.params.id);
|
||||
const hotspots = await Hotspot.find({ parent_scene_id: req.params.scene_id });
|
||||
res.json(hotspots);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
if (!scene) {
|
||||
return res.status(404).json({ message: 'Scene not found' });
|
||||
/**
|
||||
* @route POST /api/hotspots/create
|
||||
* @desc Tạo mới Hotspot + Tự động tạo liên kết ngược
|
||||
*/
|
||||
router.post('/hotspots/create', protect, async (req, res) => {
|
||||
try {
|
||||
const { parent_scene_id, target_scene_id, title, description, coordinates } = req.body;
|
||||
|
||||
const parentScene = await Scene.findById(parent_scene_id);
|
||||
// Phân quyền: Admin hoặc Người tạo Scene
|
||||
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' });
|
||||
}
|
||||
|
||||
// Chỉ chủ sở hữu mới có quyền chỉnh sửa hotspots
|
||||
if (scene.owner.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({ message: 'Access denied: Only the owner can add hotspots' });
|
||||
}
|
||||
|
||||
if (hotspotId) {
|
||||
// CẬP NHẬT HOTSPOT HIỆN CÓ
|
||||
const hs = scene.hotspots.id(hotspotId);
|
||||
if (!hs) return res.status(404).json({ message: 'Hotspot not found' });
|
||||
|
||||
hs.pitch = parseFloat(pitch) ?? hs.pitch;
|
||||
hs.yaw = parseFloat(yaw) ?? hs.yaw;
|
||||
hs.text = text ?? hs.text;
|
||||
hs.description = description ?? hs.description;
|
||||
hs.targetSceneId = targetSceneId ?? hs.targetSceneId;
|
||||
} else {
|
||||
// THÊM MỚI HOTSPOT
|
||||
const newHotspot = {
|
||||
pitch: parseFloat(pitch),
|
||||
yaw: parseFloat(yaw),
|
||||
text: text || '',
|
||||
description: description || '',
|
||||
targetSceneId: targetSceneId || undefined
|
||||
};
|
||||
|
||||
if (isNaN(newHotspot.pitch) || isNaN(newHotspot.yaw)) {
|
||||
return res.status(400).json({ message: 'Invalid coordinates' });
|
||||
}
|
||||
scene.hotspots.push(newHotspot);
|
||||
}
|
||||
|
||||
await scene.save();
|
||||
|
||||
// LOGIC "MẸ - CON": TỰ ĐỘNG TẠO ĐIỂM QUAY LẠI
|
||||
if (targetSceneId && targetSceneId !== 'null' && targetSceneId !== '' && typeof targetSceneId === 'string') {
|
||||
try {
|
||||
const targetScene = await Scene.findById(targetSceneId);
|
||||
if (targetScene) {
|
||||
const hasReverse = targetScene.hotspots.some(h =>
|
||||
h.targetSceneId && h.targetSceneId.toString() === scene._id.toString()
|
||||
);
|
||||
|
||||
if (!hasReverse) {
|
||||
const originYaw = parseFloat(yaw) || 0;
|
||||
const reverseYaw = originYaw > 0 ? originYaw - 180 : originYaw + 180;
|
||||
|
||||
targetScene.hotspots.push({
|
||||
pitch: 0,
|
||||
yaw: reverseYaw,
|
||||
text: `Quay lại: ${scene.title}`,
|
||||
targetSceneId: scene._id
|
||||
});
|
||||
await targetScene.save();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Lỗi tạo hotspot ngược:", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Hotspot added successfully',
|
||||
hotspots: scene.hotspots
|
||||
const hotspot = new Hotspot({
|
||||
parent_scene_id,
|
||||
target_scene_id,
|
||||
title,
|
||||
description,
|
||||
coordinates: {
|
||||
yaw: Number(coordinates.yaw),
|
||||
pitch: Number(coordinates.pitch)
|
||||
},
|
||||
is_auto_return: false
|
||||
});
|
||||
await hotspot.save();
|
||||
|
||||
if (target_scene_id) {
|
||||
const targetScene = await Scene.findById(target_scene_id);
|
||||
if (targetScene) {
|
||||
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({
|
||||
parent_scene_id: target_scene_id,
|
||||
target_scene_id: parent_scene_id,
|
||||
title: `Quay lại ${backLabel}`,
|
||||
coordinates: { yaw: reverseYaw, pitch: 0 },
|
||||
is_auto_return: true
|
||||
});
|
||||
await reverseHotspot.save();
|
||||
}
|
||||
}
|
||||
|
||||
res.status(201).json(hotspot);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route PUT /api/hotspots/update/:id
|
||||
* @desc Cập nhật hotspot
|
||||
*/
|
||||
router.put('/hotspots/update/:id', protect, async (req, res) => {
|
||||
try {
|
||||
const { title, description, coordinates } = req.body;
|
||||
const hotspot = await Hotspot.findById(req.params.id);
|
||||
if (!hotspot) return res.status(404).json({ message: 'Hotspot không tồn tại' });
|
||||
|
||||
const parentScene = await Scene.findById(hotspot.parent_scene_id);
|
||||
// Phân quyền Admin hoặc Owner
|
||||
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' });
|
||||
}
|
||||
|
||||
if (title) hotspot.title = title;
|
||||
if (description) hotspot.description = description;
|
||||
if (coordinates) {
|
||||
hotspot.coordinates = {
|
||||
yaw: Number(coordinates.yaw),
|
||||
pitch: Number(coordinates.pitch)
|
||||
};
|
||||
}
|
||||
|
||||
await hotspot.save();
|
||||
res.json(hotspot);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route DELETE /api/hotspots/delete/:id
|
||||
* @desc Xóa hotspot + Xóa luôn hotspot ngược tương ứng
|
||||
*/
|
||||
router.delete('/hotspots/delete/:id', protect, async (req, res) => {
|
||||
try {
|
||||
const hotspot = await Hotspot.findById(req.params.id);
|
||||
if (!hotspot) return res.status(404).json({ message: 'Hotspot không tồn tại' });
|
||||
|
||||
const parentScene = await Scene.findById(hotspot.parent_scene_id);
|
||||
// Phân quyền Admin hoặc Owner
|
||||
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' });
|
||||
}
|
||||
|
||||
if (hotspot.target_scene_id) {
|
||||
await Hotspot.deleteOne({
|
||||
parent_scene_id: hotspot.target_scene_id,
|
||||
target_scene_id: hotspot.parent_scene_id,
|
||||
is_auto_return: true
|
||||
});
|
||||
}
|
||||
|
||||
await Hotspot.findByIdAndDelete(req.params.id);
|
||||
res.json({ message: 'Hotspot deleted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
@@ -340,7 +391,7 @@ router.get('/assets/view/:assetId', verifyReferer, optionalAuth, async (req, res
|
||||
const hasAccess =
|
||||
scene.privacy === 'public' ||
|
||||
(scene.privacy === 'member' && req.user) ||
|
||||
(req.user && scene.owner.toString() === req.user._id.toString()) ||
|
||||
(req.user && scene.createdBy.toString() === req.user._id.toString()) ||
|
||||
(req.user && scene.sharedWith.includes(req.user._id)) ||
|
||||
(scene.privacy === 'shared' && req.query.token === scene.shareToken);
|
||||
|
||||
@@ -381,15 +432,17 @@ router.put('/scenes/:id', protect, uploadSinglePanorama, async (req, res) => {
|
||||
const { title, privacy, sharedWithUsers, lat, lng } = req.body;
|
||||
const scene = await Scene.findById(req.params.id);
|
||||
|
||||
if (!scene || scene.owner.toString() !== req.user._id.toString()) {
|
||||
// Phân quyền Admin hoặc Owner
|
||||
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' });
|
||||
}
|
||||
|
||||
// Update basic info
|
||||
scene.title = title || scene.title;
|
||||
scene.name = title || scene.name;
|
||||
scene.privacy = privacy || scene.privacy;
|
||||
scene.lat = lat ? parseFloat(lat) : scene.lat;
|
||||
scene.lng = lng ? parseFloat(lng) : scene.lng;
|
||||
if (lat) scene.gps.lat = Number(lat);
|
||||
if (lng) scene.gps.lng = Number(lng);
|
||||
|
||||
if (privacy === 'shared' && !scene.shareToken) {
|
||||
scene.shareToken = crypto.randomBytes(24).toString('hex');
|
||||
@@ -428,7 +481,9 @@ router.put('/scenes/:id', protect, uploadSinglePanorama, async (req, res) => {
|
||||
router.delete('/scenes/:id', protect, async (req, res) => {
|
||||
try {
|
||||
const scene = await Scene.findById(req.params.id);
|
||||
if (!scene || scene.owner.toString() !== req.user._id.toString()) {
|
||||
// Phân quyền Admin hoặc Owner
|
||||
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' });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user