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:
2026-06-10 10:59:34 +07:00
parent 02cd68f23c
commit 6378bcae5d
8 changed files with 135 additions and 63 deletions
+37 -45
View File
@@ -12,6 +12,11 @@ const { logActivity } = require('./logger');
* @returns {Promise<{deletedCount: number}>} Số lượng scene đã xóa
*/
const deleteSceneCascade = async (rootSceneId, performer = 'System') => {
// 0. Xác định tourId của scene gốc để thiết lập biên giới xóa
const rootScene = await Scene.findById(rootSceneId);
if (!rootScene) return { deletedCount: 0 };
const tourId = rootScene.tourId ? rootScene.tourId.toString() : null;
// BƯỚC SỬA LỖI QUAN TRỌNG: Xóa toàn bộ "điều hướng" (Hotspots) trỏ ĐẾN scene này.
// Đây chính là lệnh "xóa điều hướng" để cô lập scene con khỏi scene cha ngay lập tức.
// Nó đảm bảo các scene cha không còn bất kỳ liên kết nào dẫn đến luồng xóa này.
@@ -27,14 +32,23 @@ const deleteSceneCascade = async (rootSceneId, performer = 'System') => {
// Chỉ tìm các hotspots xuất phát từ scene hiện tại trỏ đến các scene con.
// QUAN TRỌNG: Phải loại bỏ các liên kết "Quay lại" (is_auto_return: true)
// để tránh việc thuật toán đi ngược lên cảnh cha.
// Populate target_scene_id để kiểm tra tourId
const childHotspots = await Hotspot.find({
parent_scene_id: parentId,
is_auto_return: { $ne: true }
});
}).populate('target_scene_id', 'tourId');
for (const hs of childHotspots) {
if (hs.target_scene_id) {
const targetIdStr = hs.target_scene_id.toString();
if (!visited.has(targetIdStr)) {
if (hs.target_scene_id && typeof hs.target_scene_id === 'object') {
const targetScene = hs.target_scene_id;
const targetIdStr = targetScene._id.toString();
const targetTourId = targetScene.tourId ? targetScene.tourId.toString() : null;
// [BẢO MẬT] Ngăn chặn xóa dây chuyền sang Tour khác.
// Một cảnh chỉ được xóa nếu:
// 1. Nó có cùng tourId với cảnh gốc đang bị xóa.
// 2. tourId của cả hai phải tồn tại (không null) để tránh xóa nhầm dữ liệu cũ chưa migrate.
if (tourId && targetTourId && targetTourId === tourId && !visited.has(targetIdStr)) {
visited.add(targetIdStr);
scenesToDelete.push(targetIdStr);
queue.push(targetIdStr);
@@ -79,61 +93,35 @@ const deleteSceneCascade = async (rootSceneId, performer = 'System') => {
};
/**
* Lan truyền thiết lập quyền riêng tư từ Scene cha xuống TOÀN BỘ các Scene con trong Tour (Đệ quy/BFS).
* Lan truyền thiết lập quyền riêng tư cho toàn bộ Tour dựa trên tourId.
* Đảm bảo tính nhất quán của toàn bộ Tour khi thay đổi quyền truy cập.
* @param {string} parentSceneId - ID của Scene cha vừa được cập nhật
* @param {Object} privacyData - Dữ liệu quyền riêng tư từ Scene cha (privacy, tokens, v.v.)
* @param {string} tourId - ID định danh ca Tour (thường là ID của cảnh gốc)
* @param {Object} privacyData - Dữ liệu quyền riêng tư mới
*/
const propagateScenePrivacy = async (parentSceneId, privacyData) => {
const propagateScenePrivacy = async (tourId, privacyData) => {
if (!tourId) return;
const { privacy, shareToken, shareTokenExpires, sharedWith, sharedEmails } = privacyData;
// 1. Tìm tất cả các scene con ở mọi cấp độ bằng thuật toán BFS
let queue = [parentSceneId.toString()];
let allChildIds = [];
const visited = new Set(queue);
while (queue.length > 0) {
const currentId = queue.shift();
// Tìm các hotspots xuất phát từ scene hiện tại (bỏ qua link quay lại để tránh vòng lặp)
const hotspots = await Hotspot.find({
parent_scene_id: currentId,
is_auto_return: { $ne: true }
}).select('target_scene_id');
for (const hs of hotspots) {
if (hs.target_scene_id) {
const targetIdStr = hs.target_scene_id.toString();
if (!visited.has(targetIdStr)) {
visited.add(targetIdStr);
allChildIds.push(targetIdStr);
queue.push(targetIdStr);
}
}
}
}
if (allChildIds.length === 0) return;
// 2. Chuẩn bị dữ liệu cập nhật đồng bộ cho toàn bộ chuỗi
const updateFields = { privacy };
const updateQuery = { $set: updateFields };
const updateFields = { privacy, tourId }; // Luôn đồng bộ tourId
const unsets = {};
if (privacy === 'shared') {
// Chỉ gán nếu có giá trị, nếu không thì xóa hẳn để tránh lỗi Duplicate Key Null
if (shareToken) {
updateFields.shareToken = shareToken;
} else {
if (!updateQuery.$unset) updateQuery.$unset = {};
updateQuery.$unset.shareToken = 1;
unsets.shareToken = 1;
}
updateFields.shareTokenExpires = shareTokenExpires || undefined;
updateFields.sharedWith = sharedWith || [];
updateFields.sharedEmails = sharedEmails || [];
} else {
// [BẢO MẬT] Xóa hoàn toàn token cho mọi chế độ không phải 'shared' để tránh lỗi Duplicate Key Null
if (!updateQuery.$unset) updateQuery.$unset = {};
updateQuery.$unset.shareToken = 1;
updateQuery.$unset.shareTokenExpires = 1;
unsets.shareToken = 1;
unsets.shareTokenExpires = 1;
// Nếu là private hoặc public, xóa luôn danh sách thành viên được chia sẻ
if (privacy !== 'member') {
updateFields.sharedWith = [];
@@ -141,13 +129,17 @@ const propagateScenePrivacy = async (parentSceneId, privacyData) => {
}
}
// 3. Cập nhật hàng loạt cho tất cả các scene con được tìm thấy
// Xây dựng Query cuối cùng
const updateQuery = { $set: updateFields };
if (Object.keys(unsets).length > 0) updateQuery.$unset = unsets;
// 3. Cập nhật hàng loạt dựa trên tourId (Nhanh và chính xác tuyệt đối)
await Scene.updateMany(
{ _id: { $in: allChildIds } },
{ tourId: tourId },
updateQuery
);
await logActivity('PROPAGATE_PRIVACY_DEEP', { parentSceneId, childCount: allChildIds.length, privacy }, 'System');
await logActivity('PROPAGATE_PRIVACY_BY_TOUR', { tourId, privacy }, 'System');
};
module.exports = { deleteSceneCascade, propagateScenePrivacy };