Backend: SQLite storage, EvoBGP integration, filters in SQL

Made-with: Cursor
This commit is contained in:
2026-04-20 01:17:00 +07:00
parent 7f3eaea40a
commit 1c6e6ab24a
27 changed files with 2406 additions and 2542 deletions
+18 -19
View File
@@ -1,8 +1,8 @@
/**
* Роуты для бэкапов конфигурации MikroTik
* Хранят сырый текст конфигурации (например, вывод /export) в S3.
* Хранят сырый текст конфигурации (например, вывод /export) на локальной ФС.
*
* Ключи в S3: backups/mikrotik/{serverId}/{timestamp}-{rand}.rsc
* Логические ключи: backups/mikrotik/{serverId}/{timestamp}-{rand}.rsc
*
* Задачи:
* - создать бэкап (ручной или из планировщика)
@@ -13,7 +13,7 @@
const crypto = require('crypto');
const { sendError, sendOk } = require('../middleware/errorHandler');
const { writeS3TextObject, readS3TextObject, listS3Objects } = require('../services/s3Service');
const { writeBackupFile, readBackupFile, listBackupObjects } = require('../services/backupFsService');
const { readServersFromS3 } = require('./serversRoutes');
const { createRosClient, fetchExportViaFile } = require('../services/mikrotikApplyService');
const { encrypt, decrypt } = require('../utils/encryption');
@@ -61,7 +61,7 @@ async function saveBackupForServer(serverId, config, { source, comment } = {}) {
let content = `${headerLines.join('\n')}\n${String(config || '').replace(/^\uFEFF/, '')}`;
// Шифруем содержимое на S3, если задан ENCRYPTION_KEY
// Шифруем содержимое файла, если задан ENCRYPTION_KEY
if (process.env.ENCRYPTION_KEY) {
try {
content = ENCRYPTED_PREFIX + encrypt(content);
@@ -71,15 +71,15 @@ async function saveBackupForServer(serverId, config, { source, comment } = {}) {
}
}
const meta = await writeS3TextObject(key, content, 'text/plain');
return { key, createdAt: now.toISOString(), s3: meta };
const meta = await writeBackupFile(key, content, 'text/plain');
return { key, createdAt: now.toISOString(), storage: meta };
}
/**
* POST /api/mikrotik/backups
* Body: { serverId: string, config: string, source?: string, comment?: string }
*
* Сохраняет текст конфигурации (обычно вывод /export) в S3.
* Сохраняет текст конфигурации (обычно вывод /export) в каталог бэкапов на диске.
* Автоматический бэкап можно организовать внешним планировщиком, дергающим этот эндпоинт.
*/
async function createBackup(req, res) {
@@ -93,14 +93,14 @@ async function createBackup(req, res) {
return sendError(res, 400, 'config (MikroTik export text) is required', 'E_BAD_REQUEST');
}
const { key, createdAt, s3 } = await saveBackupForServer(serverId, config, { source, comment });
const { key, createdAt, storage } = await saveBackupForServer(serverId, config, { source, comment });
return res.json({
ok: true,
key,
serverId,
createdAt,
s3,
storage,
});
} catch (error) {
console.error('createBackup error:', error);
@@ -120,12 +120,11 @@ async function listBackups(req, res) {
return sendError(res, 400, 'serverId is required', 'E_BAD_REQUEST');
}
const prefix = `${BACKUP_PREFIX}/${String(serverId).replace(/[^a-zA-Z0-9._-]/g, '_')}/`;
const objects = await listS3Objects(prefix, { maxKeys: 200 });
const safeId = String(serverId).replace(/[^a-zA-Z0-9._-]/g, '_');
const objects = await listBackupObjects(safeId, 200);
const items = objects
.filter(o => o.key && o.key.startsWith(prefix))
.map(o => ({
.map((o) => ({
key: o.key,
serverId,
createdAt: o.lastModified,
@@ -156,7 +155,7 @@ async function getBackup(req, res) {
return sendError(res, 400, 'Invalid backup key', 'E_BAD_REQUEST');
}
const data = await readS3TextObject(key);
const data = await readBackupFile(key);
const config = decryptBackupContent(data.body || '');
return res.json({
@@ -191,8 +190,8 @@ async function diffBackups(req, res) {
}
const [a, b] = await Promise.all([
readS3TextObject(keyA),
readS3TextObject(keyB),
readBackupFile(keyA),
readBackupFile(keyB),
]);
const bodyA = decryptBackupContent(a.body || '');
@@ -265,7 +264,7 @@ async function getMikrotikClientForServerId(serverId) {
* POST /api/mikrotik/backups/run
* Body: { serverId: string }
*
* Немедленно делает /rest/export compact на указанном MikroTik и сохраняет результат в S3.
* Немедленно делает /rest/export compact на указанном MikroTik и сохраняет результат на диск.
*/
async function runBackupNow(req, res) {
try {
@@ -293,7 +292,7 @@ async function runBackupNow(req, res) {
return sendError(res, 502, 'Empty export result from MikroTik', 'E_EMPTY_EXPORT');
}
const { key, createdAt, s3 } = await saveBackupForServer(serverId, config, {
const { key, createdAt, storage } = await saveBackupForServer(serverId, config, {
source: 'manual',
comment: 'Manual backup from UI (/api/mikrotik/backups/run)',
});
@@ -303,7 +302,7 @@ async function runBackupNow(req, res) {
key,
serverId,
createdAt,
s3,
storage,
});
} catch (error) {
console.error('runBackupNow error:', error?.message, error?.stack, error?.response?.data);