Sửa lỗi quyền chia sẻ và hiển thị lên map ở các liên kết chéo
This commit is contained in:
@@ -34,9 +34,12 @@ const uploadSinglePanorama = (req, res, next) => {
|
||||
// @route POST /api/scenes
|
||||
router.post('/', protect, uploadSinglePanorama, checkQuota, async (req, res) => {
|
||||
try {
|
||||
const { title, lat, lng, privacy, sharedWithUsers } = req.body;
|
||||
const { title, lat, lng, privacy, sharedWithUsers, tourId } = req.body;
|
||||
if (!req.file) return res.status(400).json({ message: 'Please upload a panorama image' });
|
||||
|
||||
// Xử lý tourId từ FormData an toàn
|
||||
const cleanedTourId = (tourId && tourId !== 'null' && tourId !== '') ? tourId : undefined;
|
||||
|
||||
const latitude = Number(lat) || 0;
|
||||
const longitude = Number(lng) || 0;
|
||||
const tempFilePath = req.file.path;
|
||||
@@ -64,8 +67,11 @@ router.post('/', protect, uploadSinglePanorama, checkQuota, async (req, res) =>
|
||||
privacy: privacy || 'private',
|
||||
shareToken,
|
||||
sharedWith: parsedSharedWith,
|
||||
status: 'processing'
|
||||
status: 'processing',
|
||||
tourId: cleanedTourId
|
||||
});
|
||||
// Mặc định mỗi cảnh mới khi tạo ra là cảnh gốc của chính nó
|
||||
if (!scene.tourId) scene.tourId = scene._id;
|
||||
await scene.save();
|
||||
|
||||
await imageQueue.add('process-panorama', {
|
||||
@@ -101,19 +107,32 @@ router.get('/:id', optionalAuth, async (req, res) => {
|
||||
|
||||
const isTokenValid = scene.shareToken && (!scene.shareTokenExpires || new Date() < scene.shareTokenExpires);
|
||||
const userEmail = req.user ? req.user.email : null;
|
||||
const hasAccess = scene.privacy === 'public' ||
|
||||
let hasAccess = scene.privacy === 'public' ||
|
||||
(scene.privacy === 'member' && req.user && (scene.sharedWith.includes(req.user._id) || (userEmail && scene.sharedEmails.includes(userEmail)))) ||
|
||||
(req.user && scene.createdBy._id.toString() === req.user._id.toString()) ||
|
||||
(scene.privacy === 'shared' && req.query.token === scene.shareToken && isTokenValid);
|
||||
|
||||
// [BRIDGE ACCESS LOGIC]
|
||||
// Nếu chưa có quyền, kiểm tra xem người dùng có đến từ một cảnh hợp lệ thuộc Tour khác không
|
||||
if (!hasAccess && req.query.token) {
|
||||
// Tìm tất cả các cảnh (parent) có hotspot trỏ đến cảnh hiện tại
|
||||
const potentialParents = await Hotspot.find({ target_scene_id: scene._id }).distinct('parent_scene_id');
|
||||
if (potentialParents.length > 0) {
|
||||
// Kiểm tra xem có cảnh cha nào sở hữu shareToken này và còn hạn không
|
||||
const authorizedParentExists = await Scene.exists({
|
||||
_id: { $in: potentialParents },
|
||||
shareToken: req.query.token,
|
||||
$or: [{ shareTokenExpires: null }, { shareTokenExpires: { $gt: new Date() } }]
|
||||
});
|
||||
if (authorizedParentExists) hasAccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAccess) return res.status(403).json({ message: 'Access denied' });
|
||||
|
||||
// Một cảnh chỉ được coi là cảnh con nếu có hotspot đi tới (không phải link quay lại) trỏ đến nó
|
||||
const isChildScene = await Hotspot.exists({
|
||||
target_scene_id: scene._id,
|
||||
is_auto_return: { $ne: true }
|
||||
});
|
||||
res.json({ ...scene.toObject(), isChildScene: !!isChildScene });
|
||||
// [BẢO MẬT] Một cảnh là cảnh con nếu nó thuộc về một tour và tourId khác với ID chính nó
|
||||
const isChild = scene.tourId && scene.tourId.toString() !== scene._id.toString();
|
||||
res.json({ ...scene.toObject(), isChildScene: !!isChild });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
@@ -130,11 +149,8 @@ router.put('/:id', protect, uploadSinglePanorama, async (req, res) => {
|
||||
}
|
||||
|
||||
// [BẢO MẬT] Kiểm tra nếu là cảnh con thì chặn thay đổi Privacy
|
||||
// Chỉ chặn nếu cảnh này là đích đến của một luồng điều hướng đi xuôi
|
||||
const isChild = await Hotspot.exists({
|
||||
target_scene_id: req.params.id,
|
||||
is_auto_return: { $ne: true }
|
||||
});
|
||||
// Dựa vào tourId để xác định quan hệ cha-con chính xác, tránh bị nhầm bởi liên kết chéo (cross-link)
|
||||
const isChild = scene.tourId && scene.tourId.toString() !== scene._id.toString();
|
||||
if (isChild && privacy && privacy !== scene.privacy) {
|
||||
return res.status(403).json({
|
||||
message: "Cảnh này thuộc một tour. Vui lòng thay đổi quyền riêng tư tại Cảnh gốc để đồng bộ."
|
||||
@@ -193,11 +209,13 @@ router.put('/:id', protect, uploadSinglePanorama, async (req, res) => {
|
||||
await fs.promises.unlink(req.file.path).catch(() => {});
|
||||
}
|
||||
|
||||
// [FIX] Đảm bảo root scene luôn có tourId để logic lan truyền hoạt động (cho cả dữ liệu cũ)
|
||||
if (!scene.tourId) scene.tourId = scene._id;
|
||||
await scene.save();
|
||||
|
||||
// [CASCADING] Lan truyền Privacy xuống các cảnh con nếu đây là cảnh cha
|
||||
if (!isChild) {
|
||||
await propagateScenePrivacy(scene._id, scene);
|
||||
await propagateScenePrivacy(scene.tourId || scene._id, scene);
|
||||
}
|
||||
|
||||
res.json({ message: 'Cập nhật thành công và đã đồng bộ quyền riêng tư cho các cảnh liên quan.', scene });
|
||||
|
||||
Reference in New Issue
Block a user