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
+100 -95
View File
@@ -15,9 +15,16 @@ const crypto = require('crypto');
// Импорт модулей
const { sendError, sendOk, errorHandler } = require('./middleware/errorHandler');
const { getLockStatus, acquireLock, releaseLock } = require('./middleware/lockManager');
const { createTextDataRoutes } = require('./routes/textDataRoutes');
const { createJsonDataRoutes } = require('./routes/jsonDataRoutes');
const { splitWhitespace } = require('./utils/helpers');
const { createFilterRulesRoutes } = require('./routes/filterRulesRoutes');
const evobgpClient = require('./services/evobgpClient');
const {
createEvobgpListGET,
createEvobgpDomainsNewPOST,
createEvobgpAsnsPOST,
createEvobgpIpRangesPOST,
createEvobgpLegacyDomainsGET,
} = require('./routes/evobgpReferenceRoutes');
const validators = require('./lib/validators');
const mikrotikValidator = require('./lib/mikrotik-validator');
@@ -231,99 +238,100 @@ const validateDomainsNew = ajv.compile(schemaDomainsNew);
const validateAsns = ajv.compile(schemaAsns);
const validateIpRanges = ajv.compile(schemaIpRanges);
// === Domains (старая версия) ===
const domainsOldRoutes = createTextDataRoutes({
s3Key: 'bgp_data/domains.txt',
mapLine: (line) => {
const parts = splitWhitespace(line);
return { domain: parts[0] || '', type: parts[1] || '' };
},
formatLine: (d) => `${String(d.domain || '').trim()} ${String(d.type || '').trim()}`.trim(),
validate: validateAsns,
cachePrefix: 'domains'
});
// === Domains (старая версия, поля domain+type — тот же источник, что ASNs) ===
const asnValidateItem = (asn, i) => {
const errors = [];
if (!validators.isValidASN(asn.domain)) {
errors.push(`Элемент ${i}: неверный ASN "${asn.domain}"`);
}
if (!validators.isValidCommunity(asn.type)) {
errors.push(`Элемент ${i}: неверный community "${asn.type}"`);
}
return errors;
};
const domainsOldRoutes = {
get: createEvobgpLegacyDomainsGET({ validate: validateAsns }),
post: createEvobgpAsnsPOST({ validate: validateAsns, validateItem: asnValidateItem }),
};
app.get('/api/domains', domainsOldRoutes.get);
app.post('/api/domains', domainsOldRoutes.post);
app.post('/api/domains', writeLimiter, domainsOldRoutes.post);
// === ASNs ===
const asnsRoutes = createTextDataRoutes({
s3Key: 'bgp_data/asns.txt',
mapLine: (line) => {
const parts = splitWhitespace(line);
return { domain: parts[0] || '', type: parts[1] || '' };
},
formatLine: (a) => `${String(a.domain || '').trim().toUpperCase()} ${String(a.type || '').trim()}`.trim(),
validate: validateAsns,
validateItem: (asn, i) => {
const errors = [];
if (!validators.isValidASN(asn.domain)) {
errors.push(`Элемент ${i}: неверный ASN "${asn.domain}"`);
}
if (!validators.isValidCommunity(asn.type)) {
errors.push(`Элемент ${i}: неверный community "${asn.type}"`);
}
return errors;
},
cachePrefix: 'asns'
});
// === ASNs (EvoBGP) ===
const asnsRoutes = {
get: createEvobgpListGET({
cachePrefix: 'asns',
validate: validateAsns,
fetchItems: async () => {
const rows = await evobgpClient.fetchAsnsData();
return rows.map((r) => ({ domain: r.asn, type: r.community }));
},
}),
post: createEvobgpAsnsPOST({ validate: validateAsns, validateItem: asnValidateItem }),
};
app.get('/api/asns', asnsRoutes.get);
app.post('/api/asns', writeLimiter, asnsRoutes.post);
// === Domains New ===
const domainsNewRoutes = createTextDataRoutes({
s3Key: 'bgp_data/domains_community.txt',
mapLine: (line) => {
const parts = splitWhitespace(line);
return { domain: parts[0] || '', community: parts[1] || '' };
},
formatLine: (d) => `${String(d.domain || '').trim().toLowerCase()} ${String(d.community || '').trim()}`.trim(),
validate: validateDomainsNew,
validateItem: (d, i) => {
const errors = [];
if (!validators.isValidDomain(d.domain) && !validators.isValidWildcardDomain(d.domain)) {
errors.push(`Элемент ${i}: неверный домен "${d.domain}"`);
}
if (!validators.isValidCommunity(d.community)) {
errors.push(`Элемент ${i}: неверный community "${d.community}"`);
}
if (!validators.isSafeXSSString(d.domain)) {
errors.push(`Элемент ${i}: домен содержит потенциально опасные символы`);
}
return errors;
},
cachePrefix: 'domains-new'
});
// === Domains New (EvoBGP) ===
const domainsNewRoutes = {
get: createEvobgpListGET({
cachePrefix: 'domains-new',
validate: validateDomainsNew,
fetchItems: async () => {
const rows = await evobgpClient.fetchDomainsData();
return rows.map(({ domain, community }) => ({ domain, community }));
},
}),
post: createEvobgpDomainsNewPOST({
validate: validateDomainsNew,
validateItem: (d, i) => {
const errors = [];
if (!validators.isValidDomain(d.domain) && !validators.isValidWildcardDomain(d.domain)) {
errors.push(`Элемент ${i}: неверный домен "${d.domain}"`);
}
if (!validators.isValidCommunity(d.community)) {
errors.push(`Элемент ${i}: неверный community "${d.community}"`);
}
if (!validators.isSafeXSSString(d.domain)) {
errors.push(`Элемент ${i}: домен содержит потенциально опасные символы`);
}
return errors;
},
}),
};
app.get('/api/domains-new', domainsNewRoutes.get);
app.post('/api/domains-new', writeLimiter, domainsNewRoutes.post);
// === IP Ranges ===
const ipRangesRoutes = createTextDataRoutes({
s3Key: 'bgp_data/ips.txt',
mapLine: (line) => {
const parts = splitWhitespace(line);
return { ipRange: parts[0] || '', community: parts[1] || '' };
},
formatLine: (ip) => `${String(ip.ipRange || '').trim()} ${String(ip.community || '').trim()}`.trim(),
validate: validateIpRanges,
validateItem: (ip, i) => {
const errors = [];
const ipStr = String(ip.ipRange || '').trim();
const isValidCIDR = validators.isValidCIDRv4(ipStr) || validators.isValidCIDRv6(ipStr);
const isValidIP = validators.isValidIPv4(ipStr) || validators.isValidIPv6(ipStr);
if (!isValidCIDR && !isValidIP) {
errors.push(`Элемент ${i}: неверный IP/CIDR "${ipStr}"`);
}
if (!validators.isValidCommunity(ip.community)) {
errors.push(`Элемент ${i}: неверный community "${ip.community}"`);
}
return errors;
},
cachePrefix: 'ip-ranges'
});
// === IP Ranges (EvoBGP) ===
const ipRangesRoutes = {
get: createEvobgpListGET({
cachePrefix: 'ip-ranges',
validate: validateIpRanges,
fetchItems: async () => {
const rows = await evobgpClient.fetchIpRangesData();
return rows.map(({ ipRange, community }) => ({ ipRange, community }));
},
}),
post: createEvobgpIpRangesPOST({
validate: validateIpRanges,
validateItem: (ip, i) => {
const errors = [];
const ipStr = String(ip.ipRange || '').trim();
const isValidCIDR = validators.isValidCIDRv4(ipStr) || validators.isValidCIDRv6(ipStr);
const isValidIP = validators.isValidIPv4(ipStr) || validators.isValidIPv6(ipStr);
if (!isValidCIDR && !isValidIP) {
errors.push(`Элемент ${i}: неверный IP/CIDR "${ipStr}"`);
}
if (!validators.isValidCommunity(ip.community)) {
errors.push(`Элемент ${i}: неверный community "${ip.community}"`);
}
return errors;
},
}),
};
app.get('/api/ip-ranges', ipRangesRoutes.get);
app.post('/api/ip-ranges', writeLimiter, ipRangesRoutes.post);
@@ -360,24 +368,21 @@ const billingRoutes = createJsonDataRoutes('servers-billing.json', (item, i) =>
app.get('/api/billing', billingRoutes.get);
app.post('/api/billing', billingRoutes.post);
// Filters
const filtersRoutes2 = createJsonDataRoutes('filters.json', (filter, i) => {
// Filters (SQL app_filter_rules, scope global)
const filterItemValidator = (filter, i) => {
if (!filter.community || !filter.gateway) {
return `Filter at index ${i} is missing required fields`;
}
return null;
});
};
const filtersRoutes2 = createFilterRulesRoutes('global', filterItemValidator);
app.get('/api/filters', filtersRoutes2.get);
app.post('/api/filters', filtersRoutes2.post);
// Simple Filters
const simpleFiltersRoutes = createJsonDataRoutes('filter-manager/simple-filters.json', (filter, i) => {
if (!filter.community || !filter.gateway) {
return `Filter at index ${i} is missing required fields`;
}
return null;
});
// Simple Filters (app_filter_rules, scope simple)
const simpleFiltersRoutes = createFilterRulesRoutes('simple', filterItemValidator);
app.get('/api/simple-filters', simpleFiltersRoutes.get);
app.post('/api/simple-filters', simpleFiltersRoutes.post);
@@ -508,7 +513,7 @@ app.get('/api/resources/stats', resourceStatsRoutes.getResourceStats);
// === ALERTS (агрегация проблем: серверы, ресурсы роутеров) ===
app.get('/api/alerts', alertsRoutes.getAlerts);
// === MIKROTIK BACKUPS (S3) ===
// === MIKROTIK BACKUPS (локальная ФС) ===
app.post('/api/mikrotik/backups', writeLimiter, mikrotikBackupRoutes.createBackup);
app.get('/api/mikrotik/backups', mikrotikBackupRoutes.listBackups);
app.get('/api/mikrotik/backups/item', mikrotikBackupRoutes.getBackup);