require('dotenv').config(); const express = require('express'); const AWS = require('aws-sdk'); const cors = require('cors'); const path = require('path'); const app = express(); const port = 3001; app.use(cors()); app.use(express.json()); // Serve static files from the React app app.use(express.static(path.join(__dirname, 'public'))); // Configure AWS S3 const s3 = new AWS.S3({ endpoint: 'https://storage.yandexcloud.net', region: process.env.AWS_REGION, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, } }); const BUCKET_NAME = process.env.S3_BUCKET_NAME; const FILE_KEY = 'domains.txt'; // Get domains from S3 app.get('/api/domains', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: FILE_KEY, }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); const domains = fileContent.split('\n').filter(line => line).map(line => { // Trim the line to remove any leading/trailing whitespace, including \r // And split by any whitespace to be more robust const parts = line.trim().split(/\s+/); const domain = parts[0] || ''; const type = parts[1] || ''; return { domain, type }; }); res.json(domains); } catch (error) { if (error.code === 'NoSuchKey') { res.json([]); // Return empty array if file does not exist } else { console.error(error); res.status(500).send('Error reading from S3'); } } }); // Update domains in S3 app.post('/api/domains', async (req, res) => { const { domains } = req.body; const fileContent = domains.map(d => `${d.domain} ${d.type}`).join('\n'); const params = { Bucket: BUCKET_NAME, Key: FILE_KEY, Body: fileContent, ContentType: 'text/plain', }; try { await s3.putObject(params).promise(); res.send('File updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing to S3'); } }); // --- ASNs Routes --- // Get ASNs from S3 app.get('/api/asns', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'asns.txt', }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); const asns = fileContent.split('\n').filter(line => line).map(line => { const parts = line.trim().split(/\s+/); const domain = parts[0] || ''; // Keep name 'domain' for consistency in component const type = parts[1] || ''; return { domain, type }; }); res.json(asns); } catch (error) { if (error.code === 'NoSuchKey') { res.json([]); } else { console.error(error); res.status(500).send('Error reading from S3'); } } }); // Update ASNs in S3 app.post('/api/asns', async (req, res) => { const { domains: asns } = req.body; // Keep name 'domains' for consistency const fileContent = asns.map(a => `${a.domain} ${a.type}`).join('\n'); const params = { Bucket: BUCKET_NAME, Key: 'asns.txt', Body: fileContent, ContentType: 'text/plain', }; try { await s3.putObject(params).promise(); res.send('File updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing to S3'); } }); // --- Servers Routes (JSON format) --- // Get servers from S3 app.get('/api/servers', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'servers.json', }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); let servers = []; try { servers = JSON.parse(fileContent); // Ensure it's an array if (!Array.isArray(servers)) { servers = []; } } catch (parseError) { console.error('Error parsing servers.json:', parseError); servers = []; } res.json(servers); } catch (error) { if (error.code === 'NoSuchKey') { res.json([]); // Return empty array if file does not exist } else { console.error(error); res.status(500).send('Error reading from S3'); } } }); // Update servers in S3 app.post('/api/servers', async (req, res) => { const { domains: servers } = req.body; // Keep name 'domains' for consistency // Validate servers structure if (!Array.isArray(servers)) { return res.status(400).send('Servers must be an array'); } // Validate each server has required fields for (let i = 0; i < servers.length; i++) { const server = servers[i]; if (!server.ip || !server.dns || !server.country || !server.provider || !server.tunnel) { return res.status(400).send(`Server at index ${i} is missing required fields`); } } const params = { Bucket: BUCKET_NAME, Key: 'servers.json', Body: JSON.stringify(servers, null, 2), // Pretty print JSON ContentType: 'application/json', }; try { await s3.putObject(params).promise(); res.send('File updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing to S3'); } }); // Новый эндпоинт для получения дат последнего изменения файлов S3 app.get('/api/s3/last-modified', async (req, res) => { try { const [domainsHead, asnsHead, serversHead] = await Promise.all([ s3.headObject({ Bucket: BUCKET_NAME, Key: 'domains.txt' }).promise(), s3.headObject({ Bucket: BUCKET_NAME, Key: 'asns.txt' }).promise(), s3.headObject({ Bucket: BUCKET_NAME, Key: 'servers.json' }).promise() ]); res.json({ domainsLastModified: domainsHead.LastModified ? domainsHead.LastModified.toISOString() : null, asnsLastModified: asnsHead.LastModified ? asnsHead.LastModified.toISOString() : null, serversLastModified: serversHead.LastModified ? serversHead.LastModified.toISOString() : null }); } catch (error) { console.error(error); res.status(500).send('Error fetching last modified dates from S3'); } }); // The "catchall" handler: for any request that doesn't // match one above, send back React's index.html file. app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); });