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
+22 -29
View File
@@ -3,8 +3,7 @@
* Для jumphost и home (входной роутер): mikrotikHost, mikrotikPort, mikrotikUser, encryptedMikrotikPassword
*/
const { GetObjectCommand, PutObjectCommand, HeadObjectCommand } = require('@aws-sdk/client-s3');
const { s3, BUCKET_NAME, streamToString } = require('../services/s3Service');
const { readS3TextObject, writeS3JsonObject, headMeta } = require('../services/s3Service');
const { sendError, sendOk, checkIfNoneMatch } = require('../middleware/errorHandler');
const { encrypt, decrypt } = require('../utils/encryption');
@@ -28,7 +27,7 @@ function validateServer(server, i) {
if (!Array.isArray(server.gateways) || server.gateways.length === 0) {
return `Server at index ${i} must have gateways for type ${normalizedType}`;
}
const primaries = server.gateways.filter(g => g && g.primary);
const primaries = server.gateways.filter((g) => g && g.primary);
if (primaries.length !== 1) {
return `Server at index ${i} must have exactly one primary gateway`;
}
@@ -45,24 +44,17 @@ function validateServer(server, i) {
async function readServersFromS3() {
try {
const data = await s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: S3_KEY }));
const body = await streamToString(data.Body);
const parsed = JSON.parse(body || '[]');
const data = await readS3TextObject(S3_KEY);
const parsed = JSON.parse(data.body || '[]');
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
if (e?.name === 'NoSuchKey' || e?.$metadata?.httpStatusCode === 404) return [];
if (e?.code === 'NoSuchKey') return [];
throw e;
}
}
async function writeServersToS3(servers) {
const body = JSON.stringify(servers, null, 2);
await s3.send(new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: S3_KEY,
Body: body,
ContentType: 'application/json',
}));
await writeS3JsonObject(S3_KEY, servers);
}
/**
@@ -70,15 +62,15 @@ async function writeServersToS3(servers) {
*/
async function getServers(req, res) {
try {
const head = await s3.send(new HeadObjectCommand({ Bucket: BUCKET_NAME, Key: S3_KEY })).catch(() => null);
const etag = head?.ETag || null;
const head = await headMeta(S3_KEY);
const etag = head?.etag || null;
if (etag) res.set('ETag', String(etag));
if (head?.LastModified) res.set('Last-Modified', new Date(head.LastModified).toUTCString());
if (typeof head?.ContentLength === 'number') res.set('Content-Length-Source', String(head.ContentLength));
if (head?.lastModified) res.set('Last-Modified', new Date(head.lastModified).toUTCString());
if (typeof head?.contentLength === 'number') res.set('Content-Length-Source', String(head.contentLength));
if (checkIfNoneMatch(req, res, etag)) return;
const servers = await readServersFromS3();
const sanitized = servers.map(s => {
const sanitized = servers.map((s) => {
const out = { ...s };
const hasMikrotikApi = s.type === 'jumphost' || s.type === 'home';
if (hasMikrotikApi && out.encryptedMikrotikPassword) {
@@ -91,11 +83,11 @@ async function getServers(req, res) {
});
res.json(sanitized);
} catch (error) {
if (error?.name === 'NoSuchKey' || error?.$metadata?.httpStatusCode === 404) {
if (error?.code === 'NoSuchKey') {
return res.json([]);
}
console.error(error);
return sendError(res, 500, 'Error reading servers', 'E_S3');
return sendError(res, 500, 'Error reading servers', 'E_STORAGE');
}
}
@@ -115,9 +107,10 @@ async function postServers(req, res) {
try {
const currentServers = await readServersFromS3();
const findCurrent = (srv) => currentServers.find(c =>
(srv.id && c.id === srv.id) || (srv.dns && c.dns === srv.dns) || (srv.ip && c.ip === srv.ip)
);
const findCurrent = (srv) =>
currentServers.find(
(c) => (srv.id && c.id === srv.id) || (srv.dns && c.dns === srv.dns) || (srv.ip && c.ip === srv.ip),
);
const toSave = items.map((srv) => {
const current = findCurrent(srv);
@@ -140,15 +133,15 @@ async function postServers(req, res) {
});
await writeServersToS3(toSave);
const head = await s3.send(new HeadObjectCommand({ Bucket: BUCKET_NAME, Key: S3_KEY })).catch(() => null);
const meta = await headMeta(S3_KEY);
return sendOk(res, {
etag: head?.ETag || null,
lastModified: head?.LastModified ? head.LastModified.toISOString() : null,
contentLength: typeof head?.ContentLength === 'number' ? head.ContentLength : null,
etag: meta?.etag || null,
lastModified: meta?.lastModified || null,
contentLength: typeof meta?.contentLength === 'number' ? meta.contentLength : null,
});
} catch (error) {
console.error(error);
return sendError(res, 500, error.message || 'Error saving servers', 'E_S3');
return sendError(res, 500, error.message || 'Error saving servers', 'E_STORAGE');
}
}