https-docs-google-com-spread/server.js

268 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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));