sửa lỗi privacy cho nhiều scene con, không cho phép ghi hoặc nhận sai

This commit is contained in:
2026-06-10 09:35:00 +07:00
parent 37d1b0095d
commit 02cd68f23c
12 changed files with 162 additions and 20 deletions
+47 -6
View File
@@ -13,7 +13,7 @@ const { checkQuota } = require('../middlewares/quotaMiddleware');
const { resizeTo8K } = require('../utils/imageHelper');
const { injectGPSCoordinates } = require('../utils/exifHelper');
const { imageQueue } = require('./imageQueue');
const { deleteSceneCascade } = require('../utils/sceneHelper');
const { deleteSceneCascade, propagateScenePrivacy } = require('../utils/sceneHelper');
const uploadDir = process.env.UPLOAD_DIR ? path.resolve(process.env.UPLOAD_DIR) : path.join(__dirname, '../uploads');
const tempDir = path.join(uploadDir, 'temp');
@@ -108,7 +108,11 @@ router.get('/:id', optionalAuth, async (req, res) => {
if (!hasAccess) return res.status(403).json({ message: 'Access denied' });
const isChildScene = await Hotspot.exists({ target_scene_id: scene._id });
// 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 });
} catch (error) {
res.status(500).json({ message: error.message });
@@ -125,6 +129,18 @@ router.put('/:id', protect, uploadSinglePanorama, async (req, res) => {
return res.status(403).json({ message: 'Not authorized' });
}
// [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 }
});
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ộ."
});
}
const oldPrivacy = scene.privacy;
scene.name = title || scene.name;
scene.description = description !== undefined ? description : scene.description;
@@ -132,10 +148,29 @@ router.put('/:id', protect, uploadSinglePanorama, async (req, res) => {
if (lat) scene.gps.lat = parseFloat(lat);
if (lng) scene.gps.lng = parseFloat(lng);
if (sharedWithUsers) try { scene.sharedWith = JSON.parse(sharedWithUsers); } catch (e) {}
if (sharedEmails) try { scene.sharedEmails = JSON.parse(sharedEmails); } catch (e) {}
// [BẢO MẬT] Chỉ duy trì shareToken ở chế độ 'shared'.
// Gán undefined để Mongoose xóa trường này khỏi DB khi save.
if (scene.privacy !== 'shared') {
scene.shareToken = undefined; // Mongoose sẽ xóa field này khỏi document
scene.shareTokenExpires = undefined; // Mướng sẽ xóa field này khỏi document
// Nếu không phải 'member', xóa luôn danh sách chia sẻ người dùng
if (scene.privacy !== 'member') {
scene.sharedWith = [];
scene.sharedEmails = [];
}
}
if (privacy === 'shared') {
if (scene.privacy !== 'private') {
// Cập nhật danh sách chia sẻ nếu không phải chế độ Private
if (sharedWithUsers) {
try { scene.sharedWith = JSON.parse(sharedWithUsers); } catch (e) {}
}
if (sharedEmails) {
try { scene.sharedEmails = JSON.parse(sharedEmails); } catch (e) {}
}
}
if (scene.privacy === 'shared') {
if (!scene.shareToken) scene.shareToken = crypto.randomBytes(24).toString('hex');
if (shareExpireDays && shareExpireDays !== 'never') {
const expires = new Date();
@@ -159,7 +194,13 @@ router.put('/:id', protect, uploadSinglePanorama, async (req, res) => {
}
await scene.save();
res.json({ message: 'Scene updated', scene });
// [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);
}
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 });
} catch (error) {
res.status(500).json({ message: error.message });
}