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:
2026-06-10 17:13:56 +07:00
parent ec7a9186b6
commit 3f1b31b233
17 changed files with 326 additions and 139 deletions
+119
View File
@@ -0,0 +1,119 @@
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();
+42
View File
@@ -0,0 +1,42 @@
const mongoose = require('mongoose');
const connectDB = require('../config/db');
const Scene = require('../models/Scene');
/**
* Script sửa lỗi các Scene mồ côi (không có tourId)
* Logic: Nếu một Scene không có tourId, nó sẽ được gán tourId = _id (trở thành Root)
*/
const fixOrphans = async () => {
try {
await connectDB();
// Sử dụng logic quét rộng tương tự audit script để tìm tất cả các loại "rác" tourId
const allScenes = await Scene.find({});
const orphans = allScenes.filter(s =>
!s.tourId ||
s.tourId === "" ||
s.tourId === "null" ||
s.tourId === "undefined"
);
console.log(`Tìm thấy ${orphans.length} scene mồ côi. Đang xử lý...`);
for (const scene of orphans) {
// [FIX] Sử dụng updateOne trực tiếp trên collection để bypass Schema validation
// Đảm bảo dữ liệu CHẮC CHẮN được ghi xuống Database
await Scene.collection.updateOne(
{ _id: scene._id },
{ $set: { tourId: scene._id } }
);
console.log(`- [FIXED] Scene: ${scene.name || scene.title} (ID: ${scene._id}) -> Đã trở thành Tour Gốc`);
}
console.log('\n--- HOÀN TẤT SỬA LỖI DỮ LIỆU ---');
mongoose.connection.close();
} catch (err) {
console.error('Lỗi thực thi:', err);
process.exit(1);
}
};
fixOrphans();
+4 -2
View File
@@ -68,8 +68,10 @@ const migrateTourIds = async () => {
}
}
// Bước 3: Xử lý các cảnh mồ côi hoặc vòng lặp kín (tự trỏ về chính mình làm gốc)
const orphanScenes = await Scene.find({ tourId: { $exists: false } });
// Bước 3: Xử lý các cảnh mồ côi, lỗi tourId null/rỗng hoặc vòng lặp kín
const orphanScenes = await Scene.find({
$or: [{ tourId: { $exists: false } }, { tourId: null }, { tourId: "" }]
});
let orphanCount = 0;
for (const scene of orphanScenes) {
await Scene.updateOne({ _id: scene._id }, { $set: { tourId: scene._id } });