const mongoose = require('mongoose'); const connectDB = require('../config/db'); const User = require('../models/User'); const Scene = require('../models/Scene'); /** * Script rà soát tính toàn vẹn của tourId và quyền sở hữu. * Mục tiêu: * 1. Phát hiện các Scene không có tourId (Mồ côi). * 2. Phát hiện các Scene trỏ tourId vào một cảnh không tồn tại (Link hỏng). * 3. Phát hiện rủi ro Scenario 2: Scene của người A nhưng tourId lại trỏ vào Tour của người B. */ const auditTourIds = async () => { try { console.log('--- BẮT ĐẦU RÀ SOÁT TOUR ID ---'); await connectDB(); // Lấy tất cả scene và populate thông tin người tạo const scenes = await Scene.find().populate('createdBy', 'username'); console.log(`Đang kiểm tra ${scenes.length} bản ghi...\n`); const report = { orphan: [], // Không có tourId brokenLink: [], // tourId trỏ vào hư vô mismatchOwner: [], // Chủ sở hữu không khớp (Rủi ro Scenario 2) validRoots: 0, validChildren: 0 }; for (const scene of scenes) { const sId = scene._id.toString(); // 1. Kiểm tra tồn tại tourId // [ROBUST CHECK] Kiểm tra cả giá trị null, undefined và chuỗi rác const tourIdRaw = scene.tourId; if (!tourIdRaw || tourIdRaw === "" || tourIdRaw === "null" || tourIdRaw === "undefined") { report.orphan.push({ id: sId, name: scene.name || scene.title, value: JSON.stringify(tourIdRaw) // In ra giá trị thực tế để debug }); continue; } const tId = scene.tourId.toString(); // Trường hợp là Root (Cảnh gốc của chính nó) if (sId === tId) { report.validRoots++; continue; } // Trường hợp là Child -> Kiểm tra quan hệ với cha const rootScene = await Scene.findById(scene.tourId).populate('createdBy', 'username'); if (!rootScene) { report.brokenLink.push({ id: sId, name: scene.name || scene.title, target: tId }); continue; } // 2. KIỂM TRA QUAN TRỌNG: Đồng nhất chủ sở hữu (Scenario 2) // Nếu scene con có chủ sở hữu khác với scene gốc mà nó đang trỏ tourId vào, // nghĩa là quyền riêng tư của nó đang bị điều khiển bởi một người khác. const sceneOwner = scene.createdBy?._id?.toString() || scene.createdBy?.toString(); const rootOwner = rootScene.createdBy?._id?.toString() || rootScene.createdBy?.toString(); if (sceneOwner !== rootOwner) { report.mismatchOwner.push({ childId: sId, childName: scene.name || scene.title, childOwner: scene.createdBy?.username || 'N/A', parentId: tId, parentName: rootScene.name || rootScene.title, parentOwner: rootScene.createdBy?.username || 'N/A' }); } else { report.validChildren++; } } // --- XUẤT BÁO CÁO --- console.log('=== KẾT QUẢ RÀ SOÁT ==='); console.log(`- Scene gốc hợp lệ: ${report.validRoots}`); console.log(`- Scene con hợp lệ: ${report.validChildren}`); console.log('-----------------------'); if (report.orphan.length > 0) { console.error(`[!] LỖI: ${report.orphan.length} Scene mồ côi (thiếu tourId):`); report.orphan.forEach(x => console.log(` - ID: ${x.id} | Tên: ${x.name}`)); } if (report.brokenLink.length > 0) { console.error(`[!] LỖI: ${report.brokenLink.length} Scene trỏ vào Tour không tồn tại:`); report.brokenLink.forEach(x => console.log(` - ID: ${x.id} | Tên: ${x.name} -> Target: ${x.target}`)); } if (report.mismatchOwner.length > 0) { console.warn(`[!] CẢNH BÁO: ${report.mismatchOwner.length} Scene bị "lây nhiễm" tourId (Nguy cơ Scenario 2):`); report.mismatchOwner.forEach(x => { console.log(` - Cảnh [${x.childName}] (ID: ${x.childId}) của user [${x.childOwner}]`); console.log(` đang bị điều khiển bởi Tour [${x.parentName}] của user [${x.parentOwner}]`); console.log(` => GIẢI PHÁP: Cần cập nhật tourId của cảnh này về chính nó.\n`); }); } if (report.orphan.length === 0 && report.brokenLink.length === 0 && report.mismatchOwner.length === 0) { console.log('[✓] Database sạch sẽ. Không phát hiện lỗi tourId hay xâm lấn quyền sở hữu.'); } console.log('\n--- HOÀN TẤT ---'); mongoose.connection.close(); process.exit(0); } catch (err) { console.error('Lỗi thực thi script:', err); process.exit(1); } }; auditTourIds();