/** * Роуты для работы с 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, };