172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
/**
|
|
* Роуты для работы с communities (справочник BGP Community) — данные из EvoBGP API
|
|
*/
|
|
|
|
const evobgpClient = require('../services/evobgpClient');
|
|
const filterRulesStorage = require('../services/filterRulesStorage');
|
|
const { sendError, sendOk } = require('../middleware/errorHandler');
|
|
const validators = require('../lib/validators');
|
|
|
|
function requireEvobgp(res) {
|
|
if (!evobgpClient.isConfigured()) {
|
|
return sendError(
|
|
res,
|
|
503,
|
|
'EvoBGP не настроен: задайте EVOBGP_API_URL и EVOBGP_API_TOKEN',
|
|
'E_EVOBGP_NOT_CONFIGURED',
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// GET /api/communities
|
|
async function getCommunities(req, res) {
|
|
const err = requireEvobgp(res);
|
|
if (err) return err;
|
|
try {
|
|
const communities = await evobgpClient.listCommunities();
|
|
const normalized = communities
|
|
.filter((c) => c && typeof c.value === 'string' && c.value.trim().length > 0)
|
|
.map((c) => ({
|
|
value: String(c.value).trim(),
|
|
name: c.name ? String(c.name) : '',
|
|
description: c.description ? String(c.description) : '',
|
|
tags: Array.isArray(c.tags) ? c.tags.map(String) : [],
|
|
color: c.color ? String(c.color) : '',
|
|
icon: c.icon ? String(c.icon) : '',
|
|
}));
|
|
res.json(normalized);
|
|
} catch (e) {
|
|
console.error('getCommunities', e);
|
|
return sendError(res, 500, e.message || 'EvoBGP error', 'E_EVOBGP');
|
|
}
|
|
}
|
|
|
|
// POST /api/communities
|
|
async function postCommunities(req, res) {
|
|
const err = requireEvobgp(res);
|
|
if (err) return err;
|
|
|
|
const { communities } = req.body;
|
|
|
|
if (!Array.isArray(communities)) {
|
|
return sendError(res, 400, 'communities must be an array', 'E_BAD_REQUEST');
|
|
}
|
|
|
|
const seen = new Set();
|
|
const normalized = [];
|
|
const validationErrors = [];
|
|
|
|
for (let i = 0; i < communities.length; i++) {
|
|
const entry = communities[i] || {};
|
|
const value = typeof entry.value === 'string' ? entry.value.trim() : '';
|
|
|
|
if (!value) {
|
|
validationErrors.push(`Community at index ${i} is missing required field: value`);
|
|
continue;
|
|
}
|
|
|
|
if (!validators.isValidCommunity(value)) {
|
|
validationErrors.push(`Community at index ${i} has invalid value: ${value}`);
|
|
continue;
|
|
}
|
|
|
|
if (seen.has(value)) {
|
|
validationErrors.push(`Duplicate community value at index ${i}: ${value}`);
|
|
continue;
|
|
}
|
|
|
|
seen.add(value);
|
|
normalized.push({
|
|
value,
|
|
name: entry.name ? String(entry.name) : '',
|
|
description: entry.description ? String(entry.description) : '',
|
|
tags: Array.isArray(entry.tags) ? entry.tags.map(String) : [],
|
|
color: entry.color ? String(entry.color) : '',
|
|
icon: entry.icon ? String(entry.icon) : '',
|
|
category: entry.category ? String(entry.category) : '',
|
|
priority: typeof entry.priority === 'number' ? entry.priority : 0,
|
|
enabled: typeof entry.enabled === 'boolean' ? entry.enabled : true,
|
|
});
|
|
}
|
|
|
|
if (validationErrors.length > 0) {
|
|
return sendError(res, 400, 'Validation errors', 'E_VALIDATION', { errors: validationErrors });
|
|
}
|
|
|
|
try {
|
|
await evobgpClient.saveCommunitiesData(normalized);
|
|
return sendOk(res, {
|
|
etag: null,
|
|
lastModified: new Date().toISOString(),
|
|
contentLength: null,
|
|
});
|
|
} catch (error) {
|
|
console.error('Error writing communities to EvoBGP:', error);
|
|
return sendError(res, 500, error.message || 'EvoBGP write failed', 'E_EVOBGP');
|
|
}
|
|
}
|
|
|
|
function countCommunityInLines(rows, getCommunity) {
|
|
const usage = new Map();
|
|
for (const row of rows || []) {
|
|
const c = getCommunity(row);
|
|
if (c) usage.set(c, (usage.get(c) || 0) + 1);
|
|
}
|
|
return usage;
|
|
}
|
|
|
|
// GET /api/communities/stats
|
|
async function getCommunityStats(req, res) {
|
|
const err = requireEvobgp(res);
|
|
if (err) return err;
|
|
try {
|
|
const [domains, ipRanges, asns] = await Promise.allSettled([
|
|
evobgpClient.fetchDomainsData(),
|
|
evobgpClient.fetchIpRangesData(),
|
|
evobgpClient.fetchAsnsData(),
|
|
]);
|
|
|
|
const communityUsage = new Map();
|
|
|
|
if (domains.status === 'fulfilled') {
|
|
const m = countCommunityInLines(domains.value, (r) => r.community);
|
|
for (const [k, v] of m) communityUsage.set(k, (communityUsage.get(k) || 0) + v);
|
|
}
|
|
|
|
if (ipRanges.status === 'fulfilled') {
|
|
const m = countCommunityInLines(ipRanges.value, (r) => r.community);
|
|
for (const [k, v] of m) communityUsage.set(k, (communityUsage.get(k) || 0) + v);
|
|
}
|
|
|
|
if (asns.status === 'fulfilled') {
|
|
const m = countCommunityInLines(asns.value, (r) => r.community);
|
|
for (const [k, v] of m) communityUsage.set(k, (communityUsage.get(k) || 0) + v);
|
|
}
|
|
|
|
try {
|
|
const simpleFilters = filterRulesStorage.listRules('simple');
|
|
for (const f of simpleFilters) {
|
|
if (f.community) {
|
|
communityUsage.set(f.community, (communityUsage.get(f.community) || 0) + 1);
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
|
|
const stats = Array.from(communityUsage.entries())
|
|
.map(([community, count]) => ({ community, count }))
|
|
.sort((a, b) => b.count - a.count);
|
|
|
|
res.json({ stats, total: stats.reduce((sum, s) => sum + s.count, 0) });
|
|
} catch (error) {
|
|
console.error('Error getting community stats:', error);
|
|
return sendError(res, 500, 'Error getting community stats', 'E_EVOBGP');
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getCommunities,
|
|
postCommunities,
|
|
getCommunityStats,
|
|
};
|