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_FILE = path.join(__dirname, "users.json"); // Простая авторизация по токенам function loadUsers() { try { return JSON.parse(fs.readFileSync(USERS_FILE, "utf8")); } catch (_) { return { users: [{ id: "admin", name: "Админ", token: "tok_" + Date.now() }] }; } } function saveUsers(users) { fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); } function readData() { try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { events: [] }; } } 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 checkAuth(req) { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) return null; const users = loadUsers(); return users.users.find(u => u.token === token) || null; } 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, Authorization"); if (req.method === "OPTIONS") { res.writeHead(200); return res.end(); } const urlPath = req.url.split("?")[0]; const queryString = req.url.split("?")[1] || ""; const query = new URLSearchParams(queryString); // API: Авторизация (GET и POST для работы через proxy) if ((req.method === "POST" || req.method === "GET") && urlPath === "/service/login") { const username = query.get("username") || (await body(req)).username; const password = query.get("password") || (await body(req)).password; const users = loadUsers(); const user = users.users.find(u => u.id === username && u.password === password); if (user) { res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, token: user.token, name: user.name })); } res.writeHead(401, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Неверный логин или пароль" })); } // API: Получить текущего пользователя if (req.method === "GET" && urlPath === "/service/me") { const user = checkAuth(req); if (user) { res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ id: user.id, name: user.name, isAdmin: user.id === 'admin' })); } res.writeHead(401, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Не авторизован" })); } // API: Получить всех пользователей (только admin) if (req.method === "GET" && urlPath === "/service/users") { const user = checkAuth(req); if (!user || user.id !== 'admin') { res.writeHead(403, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Только для администратора" })); } const users = loadUsers(); // Не отдаём пароли и токены const safeUsers = users.users.map(u => ({ id: u.id, name: u.name })); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(safeUsers)); } // API: Добавить пользователя (только admin) if (req.method === "POST" && urlPath === "/service/users") { const user = checkAuth(req); if (!user || user.id !== 'admin') { res.writeHead(403, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Только для администратора" })); } const { id, name, password } = await body(req); if (!id || !name || !password) { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Нужны id, name и password" })); } const users = loadUsers(); if (users.users.find(u => u.id === id)) { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Пользователь уже существует" })); } const token = "tok_" + id + "_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8); users.users.push({ id, name, password, token }); saveUsers(users); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, user: { id, name, password, token } })); } // API: Удалить пользователя (только admin, нельзя удалить admin и себя) if (req.method === "DELETE" && urlPath.startsWith("/service/users/")) { const user = checkAuth(req); if (!user || user.id !== 'admin') { res.writeHead(403, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Только для администратора" })); } const idToDelete = urlPath.split("/")[3]; if (idToDelete === 'admin') { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Нельзя удалить admin" })); } const users = loadUsers(); users.users = users.users.filter(u => u.id !== idToDelete); saveUsers(users); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // API: Изменить пароль пользователя (только admin) if (req.method === "PUT" && urlPath.startsWith("/service/users/")) { const user = checkAuth(req); if (!user || user.id !== 'admin') { res.writeHead(403, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Только для администратора" })); } const userId = urlPath.split("/")[3]; const { password } = await body(req); if (!password) { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Нужен password" })); } const users = loadUsers(); const u = users.users.find(u => u.id === userId); if (!u) { res.writeHead(404, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Пользователь не найден" })); } u.password = password; saveUsers(users); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // API: GET /service/events if (req.method === "GET" && urlPath === "/service/events") { const tab = query.get("tab") || "biot"; const data = readData(); const events = data[tab + "Events"] || []; res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(events)); } // API: POST /service/events (требуется авторизация) if (req.method === "POST" && urlPath === "/service/events") { const user = checkAuth(req); if (!user) { res.writeHead(401, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Требуется авторизация" })); } const tab = query.get("tab") || "biot"; console.log("POST /service/events tab:", tab, "user:", user.name); const data = readData(); const event = await body(req); console.log("Event data:", event); event.id = Date.now(); event.tab = tab; event.createdBy = user.id; const arrayName = tab + "Events"; if (!data[arrayName]) data[arrayName] = []; data[arrayName].push(event); console.log("Saving to:", arrayName, "Total:", data[arrayName].length); writeData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, event })); } // API: PUT /service/events/:id (требуется авторизация) if (req.method === "PUT" && urlPath.startsWith("/service/events/")) { const user = checkAuth(req); if (!user) { res.writeHead(401, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Требуется авторизация" })); } const tab = query.get("tab") || "biot"; const id = parseInt(urlPath.split("/")[3]); const data = readData(); const events = data[tab + "Events"] || []; const idx = events.findIndex(e => e.id === id); if (idx !== -1) { events[idx] = { ...events[idx], ...await body(req), updatedBy: user.id }; data[tab + "Events"] = events; writeData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } res.writeHead(404); return res.end(JSON.stringify({ error: "Не найдено" })); } // API: DELETE /service/events/:id (требуется авторизация) if (req.method === "DELETE" && urlPath.startsWith("/service/events/")) { const user = checkAuth(req); if (!user) { res.writeHead(401, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Требуется авторизация" })); } const tab = query.get("tab") || "biot"; const id = parseInt(urlPath.split("/")[3]); const data = readData(); data[tab + "Events"] = (data[tab + "Events"] || []).filter(e => e.id !== id); writeData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // Статика let file = urlPath === "/" ? "/index.html" : urlPath; 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));