const mongoose = require('mongoose'); const fs = require('fs').promises; const path = require('path'); const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); // Atlas MongoDB connection string const MONGODB_URI = 'mongodb+srv://nothingyu:wSg3lbO1PkHiRMq9@sandbox.ecysggv.mongodb.net/test?retryWrites=true&w=majority&appName=sandbox'; // Connect to MongoDB Atlas const connectDB = async () => { try { await mongoose.connect(MONGODB_URI); console.log('āœ… Connected to MongoDB Atlas'); } catch (error) { console.error('āŒ MongoDB connection error:', error); process.exit(1); } }; // Comprehensive Backup System (Data + Code) const comprehensiveBackup = { // Create comprehensive backup async createComprehensiveBackup() { try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupName = `comprehensive-backup-${timestamp}`; console.log(`šŸš€ Creating comprehensive backup: ${backupName}`); // Create backup directory const backupDir = path.join(__dirname, 'backups', backupName); await fs.mkdir(backupDir, { recursive: true }); // 1. BACKUP DATABASE DATA console.log('šŸ“Š Backing up database data...'); const dbBackup = await this.backupDatabase(backupDir); // 2. BACKUP CODE FILES console.log('šŸ’» Backing up code files...'); const codeBackup = await this.backupCodeFiles(backupDir); // 3. BACKUP CONFIGURATION console.log('āš™ļø Backing up configuration...'); const configBackup = await this.backupConfiguration(backupDir); // 4. CREATE BACKUP MANIFEST console.log('šŸ“‹ Creating backup manifest...'); const manifest = { backupName, timestamp: new Date(), type: 'comprehensive', data: { database: dbBackup, code: codeBackup, configuration: configBackup }, totalSize: await this.calculateBackupSize(backupDir), version: '1.0' }; await fs.writeFile(path.join(backupDir, 'manifest.json'), JSON.stringify(manifest, null, 2)); // 5. SAVE BACKUP RECORD TO DATABASE const backupRecord = { backupName, timestamp: new Date(), type: 'comprehensive', location: backupDir, size: manifest.totalSize, status: 'created' }; await mongoose.connection.db.collection('backups').insertOne(backupRecord); console.log(`āœ… Comprehensive backup created: ${backupName}`); console.log(`šŸ“Š Database records: ${dbBackup.totalRecords}`); console.log(`šŸ’» Code files: ${codeBackup.fileCount}`); console.log(`šŸ’¾ Total size: ${(manifest.totalSize / 1024 / 1024).toFixed(2)} MB`); return backupName; } catch (error) { console.error('āŒ Error creating comprehensive backup:', error); throw error; } }, // Backup database data async backupDatabase(backupDir) { const collections = ['subtitles', 'sourcetexts', 'submissions', 'users', 'backups']; const dbData = {}; let totalRecords = 0; for (const collection of collections) { try { const data = await mongoose.connection.db.collection(collection).find({}).toArray(); dbData[collection] = data; totalRecords += data.length; console.log(` šŸ“¦ Exported ${data.length} records from ${collection}`); } catch (error) { console.warn(` āš ļø Could not export ${collection}:`, error.message); } } const dbBackupPath = path.join(backupDir, 'database.json'); await fs.writeFile(dbBackupPath, JSON.stringify(dbData, null, 2)); return { totalRecords, collections: Object.keys(dbData), filePath: dbBackupPath }; }, // Backup code files async backupCodeFiles(backupDir) { const codeDir = path.join(backupDir, 'code'); await fs.mkdir(codeDir, { recursive: true }); // Define important code directories and files const codePaths = [ // Backend code { src: path.join(__dirname), dest: 'backend' }, // Frontend code (relative to backend) { src: path.join(__dirname, '../frontend'), dest: 'frontend' }, // Root configuration { src: path.join(__dirname, '../../'), dest: 'root' } ]; let fileCount = 0; for (const codePath of codePaths) { try { if (await this.pathExists(codePath.src)) { await this.copyDirectory(codePath.src, path.join(codeDir, codePath.dest)); const count = await this.countFiles(codePath.src); fileCount += count; console.log(` šŸ’» Copied ${count} files from ${codePath.dest}`); } } catch (error) { console.warn(` āš ļø Could not backup ${codePath.dest}:`, error.message); } } return { fileCount, directories: codePaths.map(p => p.dest), location: codeDir }; }, // Backup configuration async backupConfiguration(backupDir) { const configDir = path.join(backupDir, 'config'); await fs.mkdir(configDir, { recursive: true }); const configFiles = [ 'package.json', 'package-lock.json', 'Dockerfile', 'docker-compose.yml', 'nginx.conf', '.gitignore' ]; let configCount = 0; for (const configFile of configFiles) { try { const srcPath = path.join(__dirname, configFile); if (await this.pathExists(srcPath)) { const destPath = path.join(configDir, configFile); await fs.copyFile(srcPath, destPath); configCount++; console.log(` āš™ļø Copied ${configFile}`); } } catch (error) { console.warn(` āš ļø Could not copy ${configFile}:`, error.message); } } return { fileCount: configCount, files: configFiles, location: configDir }; }, // Helper functions async pathExists(path) { try { await fs.access(path); return true; } catch { return false; } }, async copyDirectory(src, dest) { await fs.mkdir(dest, { recursive: true }); const entries = await fs.readdir(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { await this.copyDirectory(srcPath, destPath); } else { await fs.copyFile(srcPath, destPath); } } }, async countFiles(dir) { let count = 0; const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { count += await this.countFiles(fullPath); } else { count++; } } return count; }, async calculateBackupSize(backupDir) { const { stdout } = await execAsync(`du -sb "${backupDir}" | cut -f1`); return parseInt(stdout.trim()); }, // List comprehensive backups async listComprehensiveBackups() { try { console.log('šŸ“‹ Available comprehensive backups:'); const backupCollection = mongoose.connection.db.collection('backups'); const backups = await backupCollection.find({ type: 'comprehensive' }).sort({ timestamp: -1 }).toArray(); if (backups.length === 0) { console.log(' No comprehensive backups found'); } else { backups.forEach(backup => { const date = new Date(backup.timestamp).toLocaleString(); const size = (backup.size / 1024 / 1024).toFixed(2); console.log(` šŸ“¦ ${backup.backupName} (${size} MB, ${date})`); }); } } catch (error) { console.error('āŒ Error listing backups:', error); } }, // Restore from comprehensive backup async restoreFromBackup(backupName) { try { console.log(`šŸ”„ Restoring from comprehensive backup: ${backupName}`); const backupDir = path.join(__dirname, 'backups', backupName); const manifestPath = path.join(backupDir, 'manifest.json'); if (!await this.pathExists(manifestPath)) { throw new Error('Backup manifest not found'); } const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8')); console.log('šŸ“‹ Backup manifest:', manifest); // Restore database console.log('šŸ“Š Restoring database...'); await this.restoreDatabase(backupDir); // Restore code (optional - user confirmation) console.log('šŸ’» Code restoration available'); console.log('āš ļø Code restoration will overwrite existing files'); console.log(' Run: node restore-code.js to restore code'); console.log(`āœ… Database restoration completed: ${backupName}`); } catch (error) { console.error('āŒ Error restoring from backup:', error); } }, // Restore database only async restoreDatabase(backupDir) { const dbBackupPath = path.join(backupDir, 'database.json'); const dbData = JSON.parse(await fs.readFile(dbBackupPath, 'utf8')); for (const [collection, data] of Object.entries(dbData)) { try { // Clear existing data await mongoose.connection.db.collection(collection).deleteMany({}); // Insert backup data if (data.length > 0) { await mongoose.connection.db.collection(collection).insertMany(data); } console.log(` āœ… Restored ${data.length} records to ${collection}`); } catch (error) { console.error(` āŒ Error restoring ${collection}:`, error.message); } } } }; // Main function const main = async () => { try { console.log('šŸš€ Starting comprehensive backup system...'); // Create comprehensive backup const backupName = await comprehensiveBackup.createComprehensiveBackup(); // List backups await comprehensiveBackup.listComprehensiveBackups(); console.log('\nšŸŽ‰ Comprehensive backup system ready!'); console.log('\nšŸ“‹ Available functions:'); console.log(' - createComprehensiveBackup(): Backup data + code'); console.log(' - listComprehensiveBackups(): List all backups'); console.log(' - restoreFromBackup(name): Restore database'); console.log(' - restoreCode(name): Restore code files (separate script)'); console.log('\nā° To set up automated comprehensive backups:'); console.log(' 1. Add to crontab: 0 2 * * * cd /path/to/backend && node comprehensive-backup.js'); console.log(' 2. Or run manually: node comprehensive-backup.js'); } catch (error) { console.error('āŒ Error in comprehensive backup system:', error); } finally { await mongoose.disconnect(); console.log('šŸ”Œ Disconnected from MongoDB'); } }; // Run the system connectDB().then(() => { main(); });