Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m51s
178 lines
6.0 KiB
JavaScript
178 lines
6.0 KiB
JavaScript
/**
|
|
* Роуты для работы с communities (справочник BGP Community)
|
|
*/
|
|
|
|
const { s3, BUCKET_NAME, writeS3JsonObject, invalidateCacheForKey } = require('../services/s3Service');
|
|
const { sendError, sendOk } = require('../middleware/errorHandler');
|
|
const { GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
|
|
const { streamToString } = require('../services/s3Service');
|
|
const validators = require('../lib/validators');
|
|
const { getS3JsonWithHeaders } = require('../utils/s3Helpers');
|
|
|
|
const S3_KEY = 'bgp_data/communities.json';
|
|
|
|
// GET /api/communities
|
|
async function getCommunities(req, res) {
|
|
await getS3JsonWithHeaders(S3_KEY, req, res, {
|
|
transform: (communities) => {
|
|
// Нормализация
|
|
return 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) : ''
|
|
}));
|
|
},
|
|
defaultValue: []
|
|
});
|
|
}
|
|
|
|
// POST /api/communities
|
|
async function postCommunities(req, res) {
|
|
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 s3.send(new PutObjectCommand({
|
|
Bucket: BUCKET_NAME,
|
|
Key: S3_KEY,
|
|
Body: JSON.stringify(normalized, null, 2),
|
|
ContentType: 'application/json',
|
|
}));
|
|
invalidateCacheForKey(S3_KEY);
|
|
const { headMeta } = require('../services/s3Service');
|
|
const meta = await headMeta(S3_KEY);
|
|
return sendOk(res, meta);
|
|
} catch (error) {
|
|
console.error('Error writing communities to S3:', error);
|
|
return sendError(res, 500, 'Error writing communities to S3', 'E_S3');
|
|
}
|
|
}
|
|
|
|
// GET /api/communities/stats
|
|
async function getCommunityStats(req, res) {
|
|
try {
|
|
const [domainsRes, ipRangesRes, asnsRes, filtersRes] = await Promise.allSettled([
|
|
s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: 'bgp_data/domains_community.txt' })),
|
|
s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: 'bgp_data/ips.txt' })),
|
|
s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: 'bgp_data/asns.txt' })),
|
|
s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: 'filter-manager/simple-filters.json' })),
|
|
]);
|
|
|
|
const communityUsage = new Map();
|
|
|
|
// Подсчет использования в доменах
|
|
if (domainsRes.status === 'fulfilled') {
|
|
const text = await streamToString(domainsRes.value.Body);
|
|
text.split('\n').filter(Boolean).forEach(line => {
|
|
const parts = line.trim().split(/\s+/);
|
|
if (parts[1]) {
|
|
communityUsage.set(parts[1], (communityUsage.get(parts[1]) || 0) + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Подсчет в IP ranges
|
|
if (ipRangesRes.status === 'fulfilled') {
|
|
const text = await streamToString(ipRangesRes.value.Body);
|
|
text.split('\n').filter(Boolean).forEach(line => {
|
|
const parts = line.trim().split(/\s+/);
|
|
if (parts[1]) {
|
|
communityUsage.set(parts[1], (communityUsage.get(parts[1]) || 0) + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Подсчет в ASNs
|
|
if (asnsRes.status === 'fulfilled') {
|
|
const text = await streamToString(asnsRes.value.Body);
|
|
text.split('\n').filter(Boolean).forEach(line => {
|
|
const parts = line.trim().split(/\s+/);
|
|
if (parts[1]) {
|
|
communityUsage.set(parts[1], (communityUsage.get(parts[1]) || 0) + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Подсчет в фильтрах
|
|
if (filtersRes.status === 'fulfilled') {
|
|
try {
|
|
const text = await streamToString(filtersRes.value.Body);
|
|
const filters = JSON.parse(text);
|
|
if (Array.isArray(filters)) {
|
|
filters.forEach(f => {
|
|
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_S3');
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getCommunities,
|
|
postCommunities,
|
|
getCommunityStats,
|
|
};
|
|
|