Tạo scripts backup dữ liệu

This commit is contained in:
2026-06-09 15:16:53 +07:00
parent 181b0ce033
commit fd1027203d
6 changed files with 229 additions and 0 deletions
Binary file not shown.
+78
View File
@@ -4,6 +4,7 @@ const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const sharp = require('sharp');
const AdmZip = require('adm-zip');
const User = require('../models/User');
const Asset = require('../models/Asset');
@@ -53,6 +54,83 @@ const upload = multer({
}
});
/**
* @route POST /api/admin/backup
* @desc Tạo bản sao lưu toàn bộ hệ thống (DB + Uploads)
* @access Private (Admin)
*/
router.post('/admin/backup', protect, async (req, res) => {
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') {
return res.status(403).json({ message: 'Forbidden' });
}
try {
const zip = new AdmZip();
// 1. Export Database
const dbData = {
users: await User.find().lean(),
assets: await Asset.find().lean(),
scenes: await Scene.find().lean(),
hotspots: await Hotspot.find().lean(),
settings: await Setting.find().lean()
};
zip.addFile("database.json", Buffer.from(JSON.stringify(dbData, null, 2), "utf8"));
// 2. Add Uploads folder
if (fs.existsSync(uploadDir)) {
zip.addLocalFolder(uploadDir, "uploads");
}
const buffer = zip.toBuffer();
res.set({
'Content-Type': 'application/zip',
'Content-Disposition': 'attachment; filename="backup_3dtour.zip"'
});
res.send(buffer);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
/**
* @route POST /api/admin/restore
* @desc Khôi phục hệ thống từ file backup.zip
* @access Private (Admin)
*/
router.post('/admin/restore', protect, upload.single('backupFile'), async (req, res) => {
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') {
if (req.file) fs.unlinkSync(req.file.path);
return res.status(403).json({ message: 'Forbidden' });
}
if (!req.file) return res.status(400).json({ message: 'Vui lòng upload file backup.zip' });
try {
const zip = new AdmZip(req.file.path);
const dbEntry = zip.getEntry("database.json");
if (!dbEntry) throw new Error("File backup không hợp lệ (thiếu database.json)");
const dbData = JSON.parse(dbEntry.getData().toString('utf8'));
// Khôi phục Database (Xóa cũ - Ghi mới)
await Promise.all([
User.deleteMany({}), Asset.deleteMany({}), Scene.deleteMany({}),
Hotspot.deleteMany({}), Setting.deleteMany({})
]);
await Promise.all([
User.insertMany(dbData.users), Asset.insertMany(dbData.assets),
Scene.insertMany(dbData.scenes), Hotspot.insertMany(dbData.hotspots),
Setting.insertMany(dbData.settings)
]);
// Khôi phục Files
zip.extractEntryTo("uploads/", uploadDir, false, true);
fs.unlinkSync(req.file.path);
res.json({ message: 'Khôi phục dữ liệu thành công' });
} catch (error) {
if (req.file && fs.existsSync(req.file.path)) fs.unlinkSync(req.file.path);
res.status(500).json({ message: error.message });
}
});
/**
* Wrapper for Multer middleware to catch "Request aborted" and other upload errors gracefully.
*/
+40
View File
@@ -0,0 +1,40 @@
const mongoose = require('mongoose');
const fs = require('fs');
const path = require('path');
const AdmZip = require('adm-zip');
const connectDB = require('../config/db');
const User = require('../models/User');
const Asset = require('../models/Asset');
const Scene = require('../models/Scene');
const Hotspot = require('../models/Hotspot');
const Setting = require('../models/Setting');
const backup = async () => {
await connectDB();
const zip = new AdmZip();
const uploadDir = path.join(__dirname, '../uploads');
const backupPath = path.join(__dirname, `../backups/backup_${Date.now()}.zip`);
if (!fs.existsSync(path.join(__dirname, '../backups'))) fs.mkdirSync(path.join(__dirname, '../backups'));
console.log('Exporting Database...');
const dbData = {
users: await User.find().lean(),
assets: await Asset.find().lean(),
scenes: await Scene.find().lean(),
hotspots: await Hotspot.find().lean(),
settings: await Setting.find().lean()
};
zip.addFile("database.json", Buffer.from(JSON.stringify(dbData, null, 2), "utf8"));
if (fs.existsSync(uploadDir)) {
console.log('Adding Uploads...');
zip.addLocalFolder(uploadDir, "uploads");
}
zip.writeZip(backupPath);
console.log(`Backup completed: ${backupPath}`);
mongoose.connection.close();
};
backup();
+42
View File
@@ -0,0 +1,42 @@
const mongoose = require('mongoose');
const fs = require('fs');
const path = require('path');
const AdmZip = require('adm-zip');
const connectDB = require('../config/db');
const User = require('../models/User');
const Asset = require('../models/Asset');
const Scene = require('../models/Scene');
const Hotspot = require('../models/Hotspot');
const Setting = require('../models/Setting');
const restore = async () => {
const zipPath = process.argv[2];
if (!zipPath) return console.error('Please provide zip file path: node restoreData.js <path>');
await connectDB();
const zip = new AdmZip(zipPath);
const dbEntry = zip.getEntry("database.json");
const uploadDir = path.join(__dirname, '../uploads');
console.log('Restoring Database...');
const dbData = JSON.parse(dbEntry.getData().toString('utf8'));
await Promise.all([
User.deleteMany({}), Asset.deleteMany({}), Scene.deleteMany({}),
Hotspot.deleteMany({}), Setting.deleteMany({})
]);
await Promise.all([
User.insertMany(dbData.users), Asset.insertMany(dbData.assets),
Scene.insertMany(dbData.scenes), Hotspot.insertMany(dbData.hotspots),
Setting.insertMany(dbData.settings)
]);
console.log('Restoring Files...');
zip.extractEntryTo("uploads/", uploadDir, false, true);
console.log('Restore completed successfully!');
mongoose.connection.close();
};
restore();