268 lines
9.6 KiB
JavaScript
268 lines
9.6 KiB
JavaScript
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));
|