Spaces:
Sleeping
Sleeping
| 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 <backup-name> 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(); | |
| }); |