- 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>
157 lines
6.2 KiB
JavaScript
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;
|
|
};
|