const http = require("http"); const fs = require("fs"); const path = require("path"); const PORT = process.env.PORT || 3000; const DATA = path.join(__dirname, "data.json"); const USERS = path.join(__dirname, "users.json"); const PERMISSIONS = path.join(__dirname, "permissions.json"); function readJSON(file, empty) { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch (_) { return empty; } } function writeJSON(file, data) { fs.writeFileSync(file, JSON.stringify(data, null, 2)); } function loadRecords() { const data = readJSON(DATA, { records: [], sheets: [] }); if (!data.records) data.records = []; if (!data.sheets) data.sheets = []; return data; } function saveRecords(data) { writeJSON(DATA, data); } function loadUsers() { return readJSON(USERS, { users: [] }); } function saveUsers(data) { writeJSON(USERS, data); } function loadPermissions() { return readJSON(PERMISSIONS, { permissions: [] }); } function savePermissions(data) { writeJSON(PERMISSIONS, data); } function body(req) { return new Promise((resolve) => { let s = ""; req.on("data", (c) => (s += c)); req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); } }); }); } const mime = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml", }; http.createServer(async (req, res) => { const url = req.url.split("?")[0]; const method = req.method; // API: Records if (method === "GET" && url === "/api/records") { res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(loadRecords())); } if (method === "POST" && url === "/api/records") { const rec = await body(req); const data = loadRecords(); data.records.push({ id: Date.now(), ...rec }); saveRecords(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } if (method === "PUT" && url === "/api/records") { const rec = await body(req); const data = loadRecords(); const idx = data.records.findIndex(r => r.id === rec.id); if (idx >= 0) { data.records[idx] = { ...data.records[idx], ...rec }; saveRecords(data); } res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } if (method === "DELETE" && url === "/api/records") { const { id } = await body(req); const data = loadRecords(); data.records = data.records.filter(r => r.id !== id); saveRecords(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // API: Sheets (области, города, ДПБО, ДПКК) if (method === "GET" && url === "/api/sheets") { res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(loadRecords())); } if (method === "POST" && url === "/api/sheets") { const sheet = await body(req); const data = loadRecords(); data.sheets.push({ id: Date.now(), ...sheet }); saveRecords(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // API: Users if (method === "GET" && url === "/api/users") { res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(loadUsers())); } if (method === "POST" && url === "/api/users") { const user = await body(req); const data = loadUsers(); data.users.push({ id: Date.now(), ...user }); saveUsers(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } if (method === "PUT" && url === "/api/users") { const user = await body(req); const data = loadUsers(); const idx = data.users.findIndex(u => u.id === user.id); if (idx >= 0) { data.users[idx] = { ...data.users[idx], ...user }; saveUsers(data); } res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } if (method === "DELETE" && url === "/api/users") { const { id } = await body(req); const data = loadUsers(); data.users = data.users.filter(u => u.id !== id); saveUsers(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // API: Permissions if (method === "GET" && url === "/api/permissions") { res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(loadPermissions())); } if (method === "POST" && url === "/api/permissions") { const perm = await body(req); const data = loadPermissions(); data.permissions.push({ id: Date.now(), ...perm }); savePermissions(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } if (method === "DELETE" && url === "/api/permissions") { const { id } = await body(req); const data = loadPermissions(); data.permissions = data.permissions.filter(p => p.id !== id); savePermissions(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // API: Statistics if (method === "POST" && url === "/api/stats") { const { startDate, endDate, groupBy } = await body(req); const data = loadRecords(); const start = startDate ? new Date(startDate) : null; const end = endDate ? new Date(endDate) : null; let filtered = data.records.filter(r => { if (!r.date) return true; const d = new Date(r.date); if (start && d < start) return false; if (end && d > end) return false; return true; }); const stats = {}; filtered.forEach(r => { const key = r[groupBy] || "Не указано"; if (!stats[key]) stats[key] = 0; stats[key]++; }); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ stats, total: filtered.length })); } // API: Export to Excel if (method === "GET" && url.startsWith("/api/export/excel")) { const XLSX = require("xlsx"); const data = loadRecords(); const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet(data.records.map(r => ({ "№": r.num || "", "Сегмент": r.segment || "", "Область": r.region || "", "Район": r.district || "", "Населенный пункт": r.settlement || "", "Адрес": r.address || "", "Наименование клиента": r.clientName || "", "Вид деятельности": r.activity || "", "Тех. возможность": r.techPossibility || "", "Статус строительства": r.constructionStatus || "", "Начало строительства": r.constructionStart || "", "Сроки завершения": r.constructionEnd || "", "Примечание": r.note || "", "№ устройства": r.deviceNum || "", "Статус подключения": r.connectionStatus || "", "Дата подключения": r.connectionDate || "", "Тарифный план": r.tariff || "", "ARPU (тыс. тг)": r.arpu || "" }))); XLSX.utils.book_append_sheet(wb, ws, "Все записи"); const regions = [...new Set(data.records.map(r => r.region).filter(Boolean))]; regions.forEach(region => { const regionData = data.records.filter(r => r.region === region); const regionWs = XLSX.utils.json_to_sheet(regionData.map(r => ({ "№": r.num || "", "Сегмент": r.segment || "", "Район": r.district || "", "Населенный пункт": r.settlement || "", "Адрес": r.address || "", "Наименование клиента": r.clientName || "", "Вид деятельности": r.activity || "", "Тех. возможность": r.techPossibility || "", "Статус строительства": r.constructionStatus || "", "Начало строительства": r.constructionStart || "", "Сроки завершения": r.constructionEnd || "", "Примечание": r.note || "", "№ устройства": r.deviceNum || "", "Статус подключения": r.connectionStatus || "", "Дата подключения": r.connectionDate || "", "Тарифный план": r.tariff || "", "ARPU (тыс. тг)": r.arpu || "" }))); XLSX.utils.book_append_sheet(wb, regionWs, region.slice(0, 31)); }); const buffer = XLSX.write(wb, { type: "buffer", bookType: "xlsx" }); res.writeHead(200, { "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Content-Disposition": "attachment; filename=clients.xlsx" }); return res.end(buffer); } // Static files let file = url === "/" ? "/index.html" : url.split("?")[0]; const full = path.join(__dirname, file); if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) { const ext = path.extname(full); const type = mime[ext] || "text/plain"; res.writeHead(200, { "Content-Type": type }); return res.end(fs.readFileSync(full)); } res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not found"); }).listen(PORT, () => console.log("Сервер запущен на порту " + PORT));