Thay đổi ARCHITEC.md cập nhật các thông tin để chuẩn bị refactor lại dự án
This commit is contained in:
@@ -16,51 +16,21 @@ const deleteSceneCascade = async (rootSceneId, performer = 'System') => {
|
||||
const rootScene = await Scene.findById(rootSceneId);
|
||||
if (!rootScene) return { deletedCount: 0 };
|
||||
const tourId = rootScene.tourId ? rootScene.tourId.toString() : null;
|
||||
const tourIdStr = tourId || rootSceneId.toString();
|
||||
|
||||
// [Discovery BFS] Tìm kiếm các cảnh dựa trên liên kết thực tế để xử lý dữ liệu lỗi (Broken Root)
|
||||
let queue = [rootSceneId.toString()];
|
||||
let scenesToDelete = [rootSceneId.toString()];
|
||||
const visited = new Set(scenesToDelete);
|
||||
// [BIÊN GIỚI TOUR] Xác định danh sách cần xóa
|
||||
const isRoot = tourId && tourId === rootSceneId.toString();
|
||||
let scenesToDelete = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const parentId = queue.shift();
|
||||
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 && 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;
|
||||
|
||||
// [Biên giới Tour] Chỉ xóa nếu:
|
||||
// 1. Cùng tourId
|
||||
// 2. Hoặc là Broken Root (tourId tự trỏ về chính nó nhưng lại được liên kết ở đây)
|
||||
// 3. Hoặc là Orphan (không có tourId)
|
||||
const isSameTour = targetTourId === tourIdStr;
|
||||
const isBrokenRoot = targetTourId === targetIdStr;
|
||||
const isOrphan = !targetTourId;
|
||||
|
||||
if (!visited.has(targetIdStr) && (isSameTour || isBrokenRoot || isOrphan)) {
|
||||
visited.add(targetIdStr);
|
||||
scenesToDelete.push(targetIdStr);
|
||||
queue.push(targetIdStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isRoot) {
|
||||
// Nếu xóa gốc: Xóa mọi thứ thuộc tourId này (Bao gồm con đẻ, loại trừ liên kết)
|
||||
const tourScenes = await Scene.find({ tourId: rootScene.tourId }).select('_id');
|
||||
scenesToDelete = tourScenes.map(s => s._id.toString());
|
||||
} else {
|
||||
// Nếu xóa cảnh con lẻ: Chỉ xóa đúng nó
|
||||
scenesToDelete = [rootSceneId.toString()];
|
||||
}
|
||||
|
||||
// 1. Dọn dẹp Hotspots (Cả link đi và link trỏ ĐẾN các scene sắp xóa)
|
||||
const hotspotCleanup = await Hotspot.deleteMany({
|
||||
$or: [
|
||||
{ parent_scene_id: { $in: scenesToDelete } },
|
||||
{ target_scene_id: { $in: scenesToDelete } }
|
||||
]
|
||||
});
|
||||
|
||||
// 1. Thu thập Asset ID
|
||||
// 2. Thu thập tất cả Asset ID liên quan
|
||||
const scenes = await Scene.find({ _id: { $in: scenesToDelete } });
|
||||
const assetIds = scenes.map(s => s.assetId).filter(id => id);
|
||||
@@ -71,20 +41,22 @@ const deleteSceneCascade = async (rootSceneId, performer = 'System') => {
|
||||
if (asset.filePath) await fs.promises.unlink(asset.filePath).catch(() => {});
|
||||
}));
|
||||
|
||||
// 3. Xóa bản ghi trong Database
|
||||
// 4. Dọn dẹp Hotspots (Cả link đi từ cảnh bị xóa và link từ các tour khác trỏ ĐẾN cảnh này)
|
||||
const hotspotCleanup = await Hotspot.deleteMany({
|
||||
$or: [
|
||||
{ parent_scene_id: { $in: scenesToDelete } },
|
||||
{ target_scene_id: { $in: scenesToDelete } }
|
||||
]
|
||||
});
|
||||
|
||||
// 5. Xóa bản ghi trong Database
|
||||
const assetCleanup = await Asset.deleteMany({ _id: { $in: assetIds } });
|
||||
const sceneCleanup = await Scene.deleteMany({ _id: { $in: scenesToDelete } });
|
||||
|
||||
// Chuẩn bị nội dung thông báo cho log
|
||||
const tourName = rootScene.name || rootScene.title || 'Chưa đặt tên';
|
||||
const childCount = scenesToDelete.length > 0 ? scenesToDelete.length - 1 : 0;
|
||||
|
||||
// Xác định xem đây là xóa cả Tour hay xóa lẻ
|
||||
const isRootAction = tourIdStr === rootSceneId.toString();
|
||||
|
||||
// Ghi log hoạt động xóa chi tiết để dễ dàng truy vết và kiểm tra tính toàn vẹn
|
||||
await logActivity('CASCADE_DELETE_SCENE', {
|
||||
message: isRootAction ? `Xóa trọn bộ Tour [${tourName}] và ${childCount} cảnh con` : `Xóa cảnh lẻ [${tourName}] khỏi Tour`,
|
||||
message: isRoot ? `Xóa trọn bộ Tour [${tourName}] và ${childCount} cảnh con` : `Xóa cảnh lẻ [${tourName}] khỏi Tour`,
|
||||
deletedScenesCount: scenesToDelete.length,
|
||||
cleanedHotspotsCount: hotspotCleanup.deletedCount
|
||||
}, performer ? performer.toString() : 'System');
|
||||
@@ -104,53 +76,22 @@ const propagateScenePrivacy = async (rootSceneId, privacyData, performer = 'Syst
|
||||
if (!rootScene) return;
|
||||
|
||||
const tourId = rootScene.tourId || rootScene._id;
|
||||
const tourIdStr = tourId.toString();
|
||||
|
||||
// 1. Tìm tất cả cảnh con cần cập nhật bằng BFS để đảm bảo tính "tự chữa lành" cho tourId
|
||||
let queue = [rootSceneId.toString()];
|
||||
let scenesToUpdate = [rootSceneId.toString()];
|
||||
const visited = new Set(scenesToUpdate);
|
||||
|
||||
while (queue.length > 0) {
|
||||
const parentId = queue.shift();
|
||||
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 && 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;
|
||||
|
||||
// Chấp nhận cập nhật nếu là cùng tour hoặc là broken root (tự kế thừa lại tourId đúng)
|
||||
const isBrokenRoot = targetTourId === targetIdStr;
|
||||
const isSameTour = targetTourId === tourIdStr;
|
||||
|
||||
if (!visited.has(targetIdStr) && (isSameTour || isBrokenRoot || !targetTourId)) {
|
||||
visited.add(targetIdStr);
|
||||
scenesToUpdate.push(targetIdStr);
|
||||
queue.push(targetIdStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { privacy, shareToken, shareTokenExpires, sharedWith, sharedEmails } = privacyData;
|
||||
|
||||
// 2. Chuẩn bị dữ liệu cập nhật (Luôn đồng bộ tourId để sửa lỗi dữ liệu)
|
||||
const updateFields = { privacy, tourId };
|
||||
// 2. Chuẩn bị dữ liệu cập nhật (Chỉ cập nhật Privacy, giữ nguyên tourId)
|
||||
const updateFields = { privacy };
|
||||
const unsets = {};
|
||||
if (privacy === 'shared') { if (shareToken) updateFields.shareToken = shareToken; else unsets.shareToken = 1; updateFields.shareTokenExpires = shareTokenExpires || undefined; updateFields.sharedWith = []; updateFields.sharedEmails = []; } else { unsets.shareToken = 1; unsets.shareTokenExpires = 1; if (privacy !== 'member') { updateFields.sharedWith = []; updateFields.sharedEmails = []; } }
|
||||
|
||||
const updateQuery = { $set: updateFields };
|
||||
if (Object.keys(unsets).length > 0) updateQuery.$unset = unsets;
|
||||
|
||||
// 3. Cập nhật dựa trên danh sách ID đã tìm được qua BFS
|
||||
await Scene.updateMany({ _id: { $in: scenesToUpdate } }, updateQuery);
|
||||
// [BẢO MẬT TUYỆT ĐỐI] Chỉ cập nhật cho các cảnh mang đúng tourId này (Con đẻ).
|
||||
// Các cảnh liên kết chéo mang tourId khác nên sẽ được bảo vệ an toàn.
|
||||
const result = await Scene.updateMany({ tourId: tourId }, updateQuery);
|
||||
|
||||
await logActivity('PROPAGATE_PRIVACY_BY_TOUR', { tourId, privacy }, performer ? performer.toString() : 'System');
|
||||
await logActivity('PROPAGATE_PRIVACY_BY_TOUR', { tourId, privacy, affectedCount: result.modifiedCount }, performer ? performer.toString() : 'System');
|
||||
};
|
||||
|
||||
module.exports = { deleteSceneCascade, propagateScenePrivacy };
|
||||
Reference in New Issue
Block a user