Files
dashcaddy/dashcaddy-api/routes/backups.js
T
SamiandClaude Opus 4.7 d36705bd90 feat: 1.5.0 prep — API v1 cutover, LICENSE, CHANGELOG, CI
- Remove legacy /api/ mount; all routes now under /api/v1/ only
- Update path matchers (CSRF excludes, public routes, audit log, rate limits)
- Move standalone routes (/api/network/ips, /api/docs, /api/docs/spec) to v1
- Update openapi.yaml (110 paths), CA pages, and 4 lingering frontend files
- Add LICENSE (proprietary EULA), CHANGELOG.md (Keep a Changelog format)
- Add .gitea/workflows/ci.yml (test+lint and security audit jobs)
- Fix 9 pre-existing no-empty lint errors so CI starts green
- Drop ad-hoc scratch reports and *.bak files from repo root

All 739 jest tests pass. Lint is clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:38:45 -07:00

157 lines
6.2 KiB
JavaScript

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 (_) { /* ignore */ }
}
success(res, { message: `${provider} credentials deleted` });
}, 'backups-credentials-delete'));
return router;
};