const fs = require('fs'); const Scene = require('../models/Scene'); const Asset = require('../models/Asset'); const Hotspot = require('../models/Hotspot'); const { logActivity } = require('./logger'); /** * Xóa dây chuyền một Scene và tất cả các Scene con liên quan (BFS). * Tuân thủ logic: Xóa cha thì xóa con, xóa con không xóa cha. * @param {string} rootSceneId - ID của Scene gốc cần xóa * @param {string} performer - Tên người thực hiện thao tác * @returns {Promise<{deletedCount: number}>} Số lượng scene đã xóa */ const deleteSceneCascade = async (rootSceneId, performer = 'System') => { // 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. await Hotspot.deleteMany({ target_scene_id: rootSceneId }); // 1. Thuật toán BFS để tìm tất cả các scene con (Xóa theo chiều xuôi) let queue = [rootSceneId.toString()]; let scenesToDelete = [rootSceneId.toString()]; const visited = new Set(scenesToDelete); while (queue.length > 0) { const parentId = queue.shift(); // 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. const childHotspots = await Hotspot.find({ parent_scene_id: parentId, is_auto_return: { $ne: true } }); for (const hs of childHotspots) { if (hs.target_scene_id) { const targetIdStr = hs.target_scene_id.toString(); if (!visited.has(targetIdStr)) { visited.add(targetIdStr); scenesToDelete.push(targetIdStr); queue.push(targetIdStr); } } } } // 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); const assets = await Asset.find({ _id: { $in: assetIds } }); // 3. Xóa tệp tin vật lý trên đĩa (Bất đồng bộ) await Promise.all(assets.map(async asset => { if (asset.filePath) await fs.promises.unlink(asset.filePath).catch(() => {}); })); // 4. Dọn dẹp Database // Xử lý triệt để Dangling Hotspots: // - parent_scene_id in scenesToDelete: Xóa các điểm điều hướng nằm TRONG các scene bị xóa. // - target_scene_id in scenesToDelete: Xóa các điểm điều hướng từ CÁC SCENE KHÁC (cha hoặc hàng xóm) // đang trỏ đến các scene bị xóa. Điều này giúp ngăn chặn lỗi "Broken Link" trong toàn hệ thống. const hotspotCleanup = await Hotspot.deleteMany({ $or: [ { parent_scene_id: { $in: scenesToDelete } }, { target_scene_id: { $in: scenesToDelete } } ] }); const assetCleanup = await Asset.deleteMany({ _id: { $in: assetIds } }); const sceneCleanup = await Scene.deleteMany({ _id: { $in: scenesToDelete } }); // 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', { rootSceneId, deletedScenesCount: scenesToDelete.length, cleanedHotspotsCount: hotspotCleanup.deletedCount }, performer); return { deletedCount: scenesToDelete.length }; }; module.exports = { deleteSceneCascade };