216 lines
6.6 KiB
JavaScript
216 lines
6.6 KiB
JavaScript
const http = require("http");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const url = require("url");
|
||
|
||
const PORT = process.env.PORT || 3000;
|
||
const DATA_FILE = path.join(__dirname, "data.json");
|
||
|
||
const EMPTY_DATA = {
|
||
fuelCards: [],
|
||
log: []
|
||
};
|
||
|
||
function load() {
|
||
try {
|
||
const content = fs.readFileSync(DATA_FILE, "utf8");
|
||
return JSON.parse(content);
|
||
} catch {
|
||
return EMPTY_DATA;
|
||
}
|
||
}
|
||
|
||
function save(data) {
|
||
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
||
}
|
||
|
||
function logAction(action, details) {
|
||
const data = load();
|
||
data.log.push({
|
||
when: new Date().toISOString(),
|
||
what: `${action}: ${details}`,
|
||
who: "user"
|
||
});
|
||
save(data);
|
||
}
|
||
|
||
const server = http.createServer((req, res) => {
|
||
const parsedUrl = url.parse(req.url, true);
|
||
const pathname = parsedUrl.pathname.split("?")[0];
|
||
const method = req.method;
|
||
|
||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||
|
||
if (method === "OPTIONS") {
|
||
res.writeHead(204);
|
||
res.end();
|
||
return;
|
||
}
|
||
|
||
if (pathname === "/api/fuel-cards" && method === "GET") {
|
||
const data = load();
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(data.fuelCards));
|
||
return;
|
||
}
|
||
|
||
if (pathname === "/api/fuel-cards" && method === "POST") {
|
||
let body = "";
|
||
req.on("data", chunk => { body += chunk; });
|
||
req.on("end", () => {
|
||
try {
|
||
const card = JSON.parse(body);
|
||
const data = load();
|
||
card.id = Date.now();
|
||
card.createdAt = new Date().toISOString();
|
||
data.fuelCards.push(card);
|
||
save(data);
|
||
logAction("Добавлен талон", `водитель: ${card.driver}, авто: ${card.vehicle}, литраж: ${card.liters}л`);
|
||
res.writeHead(201, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(card));
|
||
} catch (err) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: err.message }));
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (pathname === "/api/fuel-cards" && method === "PUT") {
|
||
let body = "";
|
||
req.on("data", chunk => { body += chunk; });
|
||
req.on("end", () => {
|
||
try {
|
||
const updated = JSON.parse(body);
|
||
const data = load();
|
||
const index = data.fuelCards.findIndex(c => c.id === updated.id);
|
||
if (index === -1) {
|
||
res.writeHead(404, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: "Не найдено" }));
|
||
return;
|
||
}
|
||
const old = data.fuelCards[index];
|
||
data.fuelCards[index] = { ...old, ...updated };
|
||
save(data);
|
||
logAction("Изменён талон", `ID: ${updated.id}, водитель: ${updated.driver}`);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(data.fuelCards[index]));
|
||
} catch (err) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: err.message }));
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (pathname.startsWith("/api/fuel-cards/") && method === "DELETE") {
|
||
const id = parseInt(parsedUrl.pathname.split("/").pop());
|
||
const data = load();
|
||
const card = data.fuelCards.find(c => c.id === id);
|
||
if (!card) {
|
||
res.writeHead(404, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: "Не найдено" }));
|
||
return;
|
||
}
|
||
data.fuelCards = data.fuelCards.filter(c => c.id !== id);
|
||
save(data);
|
||
logAction("Удалён талон", `водитель: ${card.driver}, авто: ${card.vehicle}, литраж: ${card.liters}л`);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ success: true }));
|
||
return;
|
||
}
|
||
|
||
if (pathname === "/api/log" && method === "GET") {
|
||
const data = load();
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(data.log.slice(-50).reverse()));
|
||
return;
|
||
}
|
||
|
||
if (pathname === "/api/stats" && method === "GET") {
|
||
const data = load();
|
||
const cards = data.fuelCards;
|
||
|
||
const totalLiters = cards.reduce((sum, c) => sum + (parseFloat(c.liters) || 0), 0);
|
||
const totalAmount = cards.reduce((sum, c) => sum + (parseFloat(c.amount) || 0), 0);
|
||
const totalCards = cards.length;
|
||
|
||
const byDriver = {};
|
||
cards.forEach(c => {
|
||
if (!byDriver[c.driver]) {
|
||
byDriver[c.driver] = { liters: 0, amount: 0, count: 0 };
|
||
}
|
||
byDriver[c.driver].liters += parseFloat(c.liters) || 0;
|
||
byDriver[c.driver].amount += parseFloat(c.amount) || 0;
|
||
byDriver[c.driver].count += 1;
|
||
});
|
||
|
||
const byVehicle = {};
|
||
cards.forEach(c => {
|
||
if (!byVehicle[c.vehicle]) {
|
||
byVehicle[c.vehicle] = { liters: 0, amount: 0, count: 0 };
|
||
}
|
||
byVehicle[c.vehicle].liters += parseFloat(c.liters) || 0;
|
||
byVehicle[c.vehicle].amount += parseFloat(c.amount) || 0;
|
||
byVehicle[c.vehicle].count += 1;
|
||
});
|
||
|
||
const byGasStation = {};
|
||
cards.forEach(c => {
|
||
if (!byGasStation[c.gasStation]) {
|
||
byGasStation[c.gasStation] = { liters: 0, amount: 0, count: 0 };
|
||
}
|
||
byGasStation[c.gasStation].liters += parseFloat(c.liters) || 0;
|
||
byGasStation[c.gasStation].amount += parseFloat(c.amount) || 0;
|
||
byGasStation[c.gasStation].count += 1;
|
||
});
|
||
|
||
const stats = {
|
||
totalLiters,
|
||
totalAmount,
|
||
totalCards,
|
||
byDriver,
|
||
byVehicle,
|
||
byGasStation,
|
||
avgPricePerLiter: totalLiters > 0 ? (totalAmount / totalLiters).toFixed(2) : 0
|
||
};
|
||
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(stats));
|
||
return;
|
||
}
|
||
|
||
const filePath = path.join(__dirname, parsedUrl.pathname === "/" ? "index.html" : parsedUrl.pathname);
|
||
const ext = path.extname(filePath).toLowerCase();
|
||
const mimeTypes = {
|
||
".html": "text/html",
|
||
".css": "text/css",
|
||
".js": "application/javascript",
|
||
".json": "application/json",
|
||
".png": "image/png",
|
||
".jpg": "image/jpeg",
|
||
".svg": "image/svg+xml"
|
||
};
|
||
|
||
fs.readFile(filePath, (err, content) => {
|
||
if (err) {
|
||
if (err.code === "ENOENT") {
|
||
res.writeHead(404);
|
||
res.end("Not found");
|
||
} else {
|
||
res.writeHead(500);
|
||
res.end("Server error");
|
||
}
|
||
return;
|
||
}
|
||
res.writeHead(200, { "Content-Type": mimeTypes[ext] || "text/plain" });
|
||
res.end(content);
|
||
});
|
||
});
|
||
|
||
server.listen(PORT, () => {
|
||
console.log(`Сервер запущен на порту ${PORT}`);
|
||
});
|