Sửa lỗi đăng nhập vào admin mà không reload được page do lỗi tạo scene trước đó, sử dụng lệnh resetDB.js để khởi tạo lại, xóa các scene trước và ảnh đã upload

This commit is contained in:
2026-06-08 10:48:54 +07:00
parent 81de520071
commit c495efad36
25 changed files with 290 additions and 47 deletions
+90 -2
View File
@@ -43,12 +43,33 @@ const upload = multer({
}
});
/**
* Wrapper for Multer middleware to catch "Request aborted" and other upload errors gracefully.
*/
const uploadSinglePanorama = (req, res, next) => {
const multerUpload = upload.single('panorama');
multerUpload(req, res, (err) => {
if (err) {
// Bắt lỗi khi client ngắt kết nối đột ngột
if (err.message === 'Request aborted') {
console.warn(`[Multer Warning]: Upload aborted by client at ${req.method} ${req.originalUrl}`);
return res.status(499).json({ message: 'Client aborted the request' });
}
if (err instanceof multer.MulterError) {
return res.status(400).json({ message: `Multer error: ${err.message}` });
}
return res.status(400).json({ message: err.message });
}
next();
});
};
/**
* @route POST /api/scenes
* @desc Create a new 3D scene (with 360 photo, 8K resize, EXIF injection)
* @access Private (Registered Users)
*/
router.post('/scenes', protect, upload.single('panorama'), async (req, res) => {
router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => {
try {
const { title, lat, lng, privacy, sharedWithUsers } = req.body;
@@ -145,6 +166,7 @@ router.post('/scenes', protect, upload.single('panorama'), async (req, res) => {
*/
router.get('/scenes', optionalAuth, async (req, res) => {
try {
console.log(`[Data Load] Bắt đầu truy vấn scenes cho: ${req.user ? req.user._id : 'Khách'}`);
let query = {};
if (req.user) {
@@ -172,8 +194,10 @@ router.get('/scenes', optionalAuth, async (req, res) => {
.populate('owner', 'username')
.populate('assetId', 'coordinates createdAt');
console.log(`[Data Load] Đã tìm thấy ${scenes.length} scenes. Gửi phản hồi về Frontend...`);
res.json(scenes);
} catch (error) {
console.error(`[Data Load Error]: ${error.stack}`);
res.status(500).json({ message: error.message });
}
});
@@ -257,6 +281,33 @@ router.post('/scenes/:id/hotspots', protect, async (req, res) => {
await scene.save();
// LOGIC "MẸ - CON": TỰ ĐỘNG TẠO ĐIỂM QUAY LẠI
if (targetSceneId && targetSceneId !== 'null' && targetSceneId !== '' && typeof targetSceneId === 'string') {
try {
const targetScene = await Scene.findById(targetSceneId);
if (targetScene) {
const hasReverse = targetScene.hotspots.some(h =>
h.targetSceneId && h.targetSceneId.toString() === scene._id.toString()
);
if (!hasReverse) {
const originYaw = parseFloat(yaw) || 0;
const reverseYaw = originYaw > 0 ? originYaw - 180 : originYaw + 180;
targetScene.hotspots.push({
pitch: 0,
yaw: reverseYaw,
text: `Quay lại: ${scene.title}`,
targetSceneId: scene._id
});
await targetScene.save();
}
}
} catch (err) {
console.error("Lỗi tạo hotspot ngược:", err.message);
}
}
res.status(201).json({
message: 'Hotspot added successfully',
hotspots: scene.hotspots
@@ -325,7 +376,7 @@ router.get('/assets/view/:assetId', verifyReferer, optionalAuth, async (req, res
* @desc Update an existing scene
* @access Private (Owner only)
*/
router.put('/scenes/:id', protect, upload.single('panorama'), async (req, res) => {
router.put('/scenes/:id', protect, uploadSinglePanorama, async (req, res) => {
try {
const { title, privacy, sharedWithUsers, lat, lng } = req.body;
const scene = await Scene.findById(req.params.id);
@@ -396,4 +447,41 @@ router.delete('/scenes/:id', protect, async (req, res) => {
}
});
/**
* @route POST /api/maintenance/reset-all
* @desc Wipe all scenes, assets, and physical files (DANGEROUS: For dev reset only)
* @access Private (Owner only)
*/
router.post('/maintenance/reset-all', protect, async (req, res) => {
try {
// 1. Xóa toàn bộ dữ liệu trong Database
await Scene.deleteMany({});
await Asset.deleteMany({});
// Lưu ý: Không xóa Users trừ khi bạn muốn reset cả tài khoản
// 2. Dọn dẹp thư mục uploads (trừ các file .gitkeep hoặc thư mục temp)
const files = fs.readdirSync(uploadDir);
for (const file of files) {
const fullPath = path.join(uploadDir, file);
if (fs.lstatSync(fullPath).isFile()) {
fs.unlinkSync(fullPath);
}
}
// 3. Dọn dẹp thư mục temp
const tempFiles = fs.readdirSync(tempDir);
for (const file of tempFiles) {
const fullPath = path.join(tempDir, file);
if (fs.lstatSync(fullPath).isFile()) {
fs.unlinkSync(fullPath);
}
}
console.warn(`[Maintenance]: Toàn bộ dữ liệu tour đã bị xóa bởi ${req.user.username}`);
res.json({ message: 'Dữ liệu đã được xóa sạch. Hãy clear localStorage ở trình duyệt để bắt đầu lại.' });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
module.exports = router;
+53
View File
@@ -0,0 +1,53 @@
const mongoose = require('mongoose');
const fs = require('fs');
const path = require('path');
const connectDB = require('../config/db');
const Scene = require('../models/Scene');
const Asset = require('../models/Asset');
const reset = async () => {
try {
console.log('--- Bắt đầu quá trình Reset Dữ liệu ---');
// 1. Kết nối Database
await connectDB();
// 2. Xóa bản ghi trong Database
console.log('1. Đang xóa dữ liệu trong MongoDB...');
const deletedScenes = await Scene.deleteMany({});
const deletedAssets = await Asset.deleteMany({});
console.log(`- Đã xóa ${deletedScenes.deletedCount} scenes.`);
console.log(`- Đã xóa ${deletedAssets.deletedCount} assets.`);
// 3. Dọn dẹp tệp tin vật lý
const uploadDir = path.join(__dirname, '../uploads');
const tempDir = path.join(uploadDir, 'temp');
console.log('2. Đang dọn dẹp thư mục uploads...');
const directories = [uploadDir, tempDir];
directories.forEach(dir => {
if (fs.existsSync(dir)) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
// Chỉ xóa file, không xóa thư mục (như temp) và tránh xóa file ẩn/cấu hình
if (fs.lstatSync(fullPath).isFile() && !file.startsWith('.')) {
fs.unlinkSync(fullPath);
}
}
}
});
console.log('--- Hoàn tất reset hệ thống! ---');
// Đóng kết nối
mongoose.connection.close();
process.exit(0);
} catch (err) {
console.error('Lỗi nghiêm trọng trong quá trình reset:', err);
mongoose.connection.close();
process.exit(1);
}
};
reset();
+10
View File
@@ -43,6 +43,16 @@ app.use(cors(corsOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Request Logger Middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl} - ${res.statusCode} (${duration}ms)`);
});
next();
});
// API Routes
app.use('/api/auth', authRoutes);
app.use('/api', apiRoutes);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

+9 -3
View File
@@ -32,11 +32,11 @@ const degToDmsRational = (deg) => {
const absolute = Math.abs(deg);
const d = Math.floor(absolute);
const m = Math.floor((absolute - d) * 60);
const s = Math.round((absolute - d - m / 60) * 3600 * 100) / 100;
const s = Math.round((absolute - d - m / 60) * 3600 * 100);
return [
[d, 1],
[m, 1],
[Math.round(s * 100), 100]
[s, 100]
];
};
@@ -77,7 +77,13 @@ const injectGPSCoordinates = async (filePath, lat, lng) => {
exifObj["GPS"][piexif.GPSIFD.GPSLongitudeRef] = lngRef;
exifObj["GPS"][piexif.GPSIFD.GPSLongitude] = degToDmsRational(lng);
const exifBytes = piexif.dump(exifObj);
// Chỉ đóng gói các IFD cần thiết để tránh lỗi 'pack' từ dữ liệu rác
const exifBytes = piexif.dump({
"0th": exifObj["0th"] || {},
"Exif": exifObj["Exif"] || {},
"GPS": exifObj["GPS"] || {}
});
const newJpegBinary = piexif.insert(exifBytes, jpegBinary);
fs.writeFileSync(filePath, Buffer.from(newJpegBinary, 'binary'));