125 lines
6.3 KiB
JavaScript
125 lines
6.3 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const AdmZip = require('adm-zip');
|
|
const multer = require('multer');
|
|
|
|
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 { protect } = require('../middlewares/authMiddleware');
|
|
const { logActivity } = require('../utils/logger');
|
|
|
|
const uploadDir = process.env.UPLOAD_DIR ? path.resolve(process.env.UPLOAD_DIR) : path.join(__dirname, '../uploads');
|
|
const tempDir = path.join(uploadDir, 'temp');
|
|
const upload = multer({ dest: tempDir });
|
|
|
|
// Helper: Dọn dẹp dữ liệu mồ côi
|
|
const runOrphanedCleanup = async (performer = 'System') => {
|
|
const validUserIds = await User.distinct('_id');
|
|
const orphanedScenes = await Scene.find({ createdBy: { $nin: validUserIds } });
|
|
const orphanedSceneIds = orphanedScenes.map(s => s._id);
|
|
|
|
if (orphanedSceneIds.length > 0) {
|
|
await Hotspot.deleteMany({ $or: [{ parent_scene_id: { $in: orphanedSceneIds } }, { target_scene_id: { $in: orphanedSceneIds } }] });
|
|
await Scene.deleteMany({ _id: { $in: orphanedSceneIds } });
|
|
}
|
|
|
|
const usedAssetIds = await Scene.distinct('assetId');
|
|
const safeDate = new Date(Date.now() - 2 * 3600 * 1000);
|
|
const orphanedAssets = await Asset.find({ $or: [{ uploadedBy: { $nin: validUserIds } }, { $and: [{ _id: { $nin: usedAssetIds } }, { createdAt: { $lt: safeDate } }] }] });
|
|
|
|
for (const asset of orphanedAssets) {
|
|
if (asset.filePath) await fs.promises.unlink(asset.filePath).catch(() => {});
|
|
await Asset.findByIdAndDelete(asset._id);
|
|
}
|
|
await logActivity('SYSTEM_ORPHAN_CLEANUP', { scenesDeleted: orphanedSceneIds.length, assetsDeleted: orphanedAssets.length }, performer);
|
|
return { scenesDeleted: orphanedSceneIds.length, assetsDeleted: orphanedAssets.length };
|
|
};
|
|
|
|
// @route POST /api/admin/backup
|
|
router.post('/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();
|
|
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)) zip.addLocalFolder(uploadDir, "uploads");
|
|
res.set({ 'Content-Type': 'application/zip', 'Content-Disposition': 'attachment; filename="backup.zip"' });
|
|
res.send(zip.toBuffer());
|
|
} catch (error) { res.status(500).json({ message: error.message }); }
|
|
});
|
|
|
|
// @route GET /api/admin/maintenance/stray-files
|
|
router.get('/maintenance/stray-files', 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 entries = await fs.promises.readdir(uploadDir, { withFileTypes: true });
|
|
const assets = await Asset.find().select('filePath').lean();
|
|
const dbFileNames = new Set(assets.map(a => path.basename(a.filePath)));
|
|
const strayFiles = entries.filter(e => e.isFile() && !e.name.startsWith('.') && !dbFileNames.has(e.name)).map(e => e.name);
|
|
res.json({ count: strayFiles.length, files: strayFiles });
|
|
} catch (error) { res.status(500).json({ message: error.message }); }
|
|
});
|
|
|
|
// @route POST /api/admin/maintenance/cleanup
|
|
router.post('/maintenance/cleanup', 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 report = await runOrphanedCleanup(req.user.username);
|
|
res.json({ message: 'Cleanup completed', report });
|
|
} catch (error) { res.status(500).json({ message: error.message }); }
|
|
});
|
|
|
|
// @route GET /api/admin/users
|
|
router.get('/users', 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 page = parseInt(req.query.page) || 1;
|
|
const limit = parseInt(req.query.limit) || 10;
|
|
const skip = (page - 1) * limit;
|
|
const users = await User.find().sort({ createdAt: -1 }).skip(skip).limit(limit).select('-password');
|
|
const total = await User.countDocuments();
|
|
res.json({ users, totalPages: Math.ceil(total / limit), currentPage: page });
|
|
} catch (error) { res.status(500).json({ message: error.message }); }
|
|
});
|
|
|
|
// @route PUT /api/admin/users/:id
|
|
router.put('/users/:id', 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 { fullName, email, role, password } = req.body;
|
|
const user = await User.findById(req.params.id);
|
|
if (!user) return res.status(404).json({ message: 'User not found' });
|
|
if (fullName) user.fullName = fullName;
|
|
if (email) user.email = email;
|
|
if (role && user.role !== 'admin') user.role = role;
|
|
if (password) user.password = password;
|
|
await user.save();
|
|
res.json({ message: 'User updated' });
|
|
} catch (error) { res.status(500).json({ message: error.message }); }
|
|
});
|
|
|
|
// @route DELETE /api/admin/users/:id
|
|
router.delete('/users/:id', 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 user = await User.findById(req.params.id);
|
|
if (!user || user.role === 'admin') return res.status(400).json({ message: 'Invalid request' });
|
|
await User.findByIdAndDelete(req.params.id);
|
|
await logActivity('USER_PERMANENT_DELETE', { userId: user._id, username: user.username }, req.user.username);
|
|
await runOrphanedCleanup(req.user.username);
|
|
res.json({ message: 'User deleted' });
|
|
} catch (error) { res.status(500).json({ message: error.message }); }
|
|
});
|
|
|
|
module.exports = router; |