const express = require('express'); const { success } = require('../response-helpers'); /** * Backups routes factory * @param {Object} deps - Explicit dependencies * @param {Object} deps.backupManager - Backup management module * @param {Function} deps.asyncHandler - Async route handler wrapper * @returns {express.Router} */ module.exports = function({ backupManager, asyncHandler }) { const router = express.Router(); // Get backup configuration router.get('/backups/config', asyncHandler(async (req, res) => { const config = backupManager.getConfig(); success(res, { config }); }, 'backups-config-get')); // Update backup configuration router.post('/backups/config', asyncHandler(async (req, res) => { backupManager.updateConfig(req.body); success(res, { message: 'Backup configuration updated' }); }, 'backups-config-update')); // Execute manual backup router.post('/backups/execute', asyncHandler(async (req, res) => { const backup = await backupManager.executeBackup('manual', req.body); success(res, { backup }); }, 'backups-execute')); // Get backup history router.get('/backups/history', asyncHandler(async (req, res) => { const limit = parseInt(req.query.limit) || 50; const history = backupManager.getHistory(limit); success(res, { history }); }, 'backups-history')); // Restore from backup router.post('/backups/restore/:backupId', asyncHandler(async (req, res) => { const result = await backupManager.restoreBackup(req.params.backupId, req.body); success(res, { result }); }, 'backups-restore')); // ==================== CLOUD DESTINATIONS ==================== // Test a destination (write+read+delete probe) router.post('/backups/test-destination', asyncHandler(async (req, res) => { const destination = req.body; if (!destination || !destination.type) { const { ValidationError } = require('../errors'); throw new ValidationError('destination.type is required'); } const result = await backupManager.testDestination(destination); success(res, result); }, 'backups-test-destination')); // Get cloud credentials (masked) for a provider // Provider: dropbox | webdav | sftp router.get('/backups/credentials/:provider', asyncHandler(async (req, res) => { const credentialManager = require('../credential-manager'); const provider = req.params.provider; if (!['dropbox', 'webdav', 'sftp'].includes(provider)) { const { ValidationError } = require('../errors'); throw new ValidationError('Invalid provider'); } const mask = (val) => val ? '***' : null; let creds = {}; if (provider === 'dropbox') { const token = await credentialManager.retrieve('backup.dropbox.token'); creds = { token: mask(token) }; } else if (provider === 'webdav') { creds = { url: (await credentialManager.retrieve('backup.webdav.url')) || null, username: (await credentialManager.retrieve('backup.webdav.username')) || null, password: mask(await credentialManager.retrieve('backup.webdav.password')) }; } else if (provider === 'sftp') { creds = { host: (await credentialManager.retrieve('backup.sftp.host')) || null, port: (await credentialManager.retrieve('backup.sftp.port')) || '22', username: (await credentialManager.retrieve('backup.sftp.username')) || null, password: mask(await credentialManager.retrieve('backup.sftp.password')), privateKey: mask(await credentialManager.retrieve('backup.sftp.privateKey')) }; } success(res, { provider, credentials: creds }); }, 'backups-credentials-get')); // Save cloud credentials for a provider router.post('/backups/credentials/:provider', asyncHandler(async (req, res) => { const credentialManager = require('../credential-manager'); const { ValidationError } = require('../errors'); const provider = req.params.provider; if (!['dropbox', 'webdav', 'sftp'].includes(provider)) { throw new ValidationError('Invalid provider'); } const body = req.body || {}; const storeIfPresent = async (key, val) => { if (val !== undefined && val !== null && val !== '' && val !== '***') { await credentialManager.store(key, String(val)); } }; if (provider === 'dropbox') { if (!body.token || body.token === '***') { const existing = await credentialManager.retrieve('backup.dropbox.token'); if (!existing) { throw new ValidationError('Dropbox token required'); } } else { await credentialManager.store('backup.dropbox.token', body.token); } } else if (provider === 'webdav') { await storeIfPresent('backup.webdav.url', body.url); await storeIfPresent('backup.webdav.username', body.username); await storeIfPresent('backup.webdav.password', body.password); } else if (provider === 'sftp') { await storeIfPresent('backup.sftp.host', body.host); await storeIfPresent('backup.sftp.port', body.port); await storeIfPresent('backup.sftp.username', body.username); await storeIfPresent('backup.sftp.password', body.password); await storeIfPresent('backup.sftp.privateKey', body.privateKey); } success(res, { message: `${provider} credentials saved` }); }, 'backups-credentials-set')); // Delete cloud credentials for a provider router.delete('/backups/credentials/:provider', asyncHandler(async (req, res) => { const credentialManager = require('../credential-manager'); const { ValidationError } = require('../errors'); const provider = req.params.provider; if (!['dropbox', 'webdav', 'sftp'].includes(provider)) { throw new ValidationError('Invalid provider'); } const keys = { dropbox: ['backup.dropbox.token'], webdav: ['backup.webdav.url', 'backup.webdav.username', 'backup.webdav.password'], sftp: ['backup.sftp.host', 'backup.sftp.port', 'backup.sftp.username', 'backup.sftp.password', 'backup.sftp.privateKey'] }; for (const k of keys[provider]) { try { await credentialManager.delete(k); } catch (_) {} } success(res, { message: `${provider} credentials deleted` }); }, 'backups-credentials-delete')); return router; };