tehnicheskoe-zadanie-informa/server.js

179 lines
6.7 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");
function readData() {
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { instructions: [], users: [], log: [] }; }
}
function writeData(data) {
fs.writeFileSync(DATA, JSON.stringify(data, null, 2));
}
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({}); } });
});
}
function updateStatuses(instructions) {
const today = new Date().toISOString().slice(0, 10);
let changed = false;
instructions.forEach(inst => {
if (inst.status !== "Выполнено" && inst.planDate < today) {
inst.status = "Просрочено";
changed = true;
}
});
return changed;
}
http.createServer(async (req, res) => {
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 (req.method === "OPTIONS") {
res.writeHead(200);
return res.end();
}
const url = req.url.split("?")[0];
// API: Получить все поручения
if (req.method === "GET" && url === "/api/instructions") {
const data = readData();
const changed = updateStatuses(data.instructions);
if (changed) writeData(data);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(data.instructions));
}
// API: Получить одно поручение по ID
if (req.method === "GET" && url.startsWith("/api/instructions/")) {
const id = parseInt(url.split("/")[3]);
const data = readData();
const inst = data.instructions.find(i => i.id === id);
if (!inst) {
res.writeHead(404);
return res.end("Not found");
}
updateStatuses(data.instructions);
if (data.instructions.find(i => i.id === id)?.status === "Просрочено" && inst.status !== "Просрочено") {
inst.status = "Просрочено";
}
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(inst));
}
// API: Создать поручение
if (req.method === "POST" && url === "/api/instructions") {
const data = readData();
const inst = await body(req);
inst.id = Date.now();
inst.status = inst.status || "В работе";
inst.answers = inst.answers || [];
inst.history = inst.history || [{ date: new Date().toISOString().slice(0, 10), user: inst.rm || "Пользователь", field: "Создано", newValue: "-" }];
inst.attachments = inst.attachments || [];
data.instructions.push(inst);
writeData(data);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ ok: true, id: inst.id }));
}
// API: Обновить поручение
if (req.method === "PUT" && url.startsWith("/api/instructions/")) {
const id = parseInt(url.split("/")[3]);
const data = readData();
const idx = data.instructions.findIndex(i => i.id === id);
if (idx === -1) {
res.writeHead(404);
return res.end("Not found");
}
const updates = await body(req);
const old = data.instructions[idx];
// Обновляем поля
["title", "status", "issueDate", "planDate", "factDate", "note", "rm", "delegate"].forEach(field => {
if (updates[field] !== undefined && updates[field] !== old[field]) {
data.instructions[idx][field] = updates[field];
if (!updates._skipLog) {
data.instructions[idx].history.push({
date: new Date().toISOString().slice(0, 10),
user: updates.user || "Пользователь",
field: field === "rm" ? "РМ" : field === "delegate" ? "Исполнитель" : field,
newValue: String(updates[field])
});
}
}
});
if (updates.answer) {
data.instructions[idx].answers.push({
date: new Date().toISOString().slice(0, 10),
text: updates.answer.text,
author: updates.answer.author
});
}
writeData(data);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ ok: true }));
}
// API: Удалить поручение
if (req.method === "DELETE" && url.startsWith("/api/instructions/")) {
const id = parseInt(url.split("/")[3]);
const data = readData();
const idx = data.instructions.findIndex(i => i.id === id);
if (idx === -1) {
res.writeHead(404);
return res.end("Not found");
}
const removed = data.instructions.splice(idx, 1)[0];
data.log.push({ date: new Date().toISOString(), action: "delete", instruction: removed.title, user: "Пользователь" });
writeData(data);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ ok: true }));
}
// API: Получить пользователей
if (req.method === "GET" && url === "/api/users") {
const data = readData();
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(data.users));
}
// API: Выгрузка в CSV (Excel)
if (req.method === "GET" && url === "/api/export") {
const data = readData();
updateStatuses(data.instructions);
const head = ["ID;Поручение;Статус;Дата выдачи;Плановый срок;Фактический срок;РМ;Исполнитель;Примечание"];
const body = data.instructions.map(i =>
[i.id, `"${(i.title||"").replace(/"/g, '""')}"`, i.status, i.issueDate, i.planDate, i.factDate || "", i.rm || "", i.delegate || "", `"${(i.note||"").replace(/"/g, '""')}"`].join(";")
);
const csv = "\uFEFF" + [head, ...body].join("\n");
res.writeHead(200, {
"Content-Type": "text/csv; charset=utf-8",
"Content-Disposition": "attachment; filename=otchet.csv"
});
return res.end(csv);
}
// Статика: index.html / registry.html / card.html / style.css / script.js
let file = req.url === "/" ? "/index.html" : req.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 = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html";
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
return res.end(fs.readFileSync(full));
}
res.writeHead(404);
res.end("Not found");
}).listen(PORT, () => console.log("Сервер на порту " + PORT));