const http = require("http"); const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); const PORT = process.env.PORT || 3000; const DATA_FILE = path.join(__dirname, "data.json"); const USERS_FILE = path.join(__dirname, "users.json"); function load() { try { return JSON.parse(fs.readFileSync(DATA_FILE, "utf8")); } catch (e) { return { announcements: [], org: [], contacts: [], insider: [], giftRules: {}, videos: [], laws: [], tests: [], complaints: [], testResults: [], directMessages: [] }; } } function save(d) { fs.writeFileSync(DATA_FILE, JSON.stringify(d, null, 2)); } function loadUsers() { try { return JSON.parse(fs.readFileSync(USERS_FILE, "utf8")); } catch (e) { return []; } } function saveUsers(users) { fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); } const sessions = new Map(); function body(req) { return new Promise((resolve) => { let s = ""; req.on("data", (c) => (s += c)); req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (e) { resolve({}); } }); }); } function send(res, code, obj) { res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" }); res.end(JSON.stringify(obj)); } function getUser(req) { const h = req.headers["authorization"] || ""; const token = h.replace(/^Bearer\s+/i, ""); return sessions.get(token) || null; } function publicUser(u) { return { login: u.login, name: u.name, role: u.role }; } http.createServer(async (req, res) => { const url = req.url.split("?")[0]; const db = load(); // ---------- ЛОГИН ---------- if (req.method === "POST" && url === "/api/login") { const b = await body(req); const u = loadUsers().find((x) => x.login === String(b.login) && x.password === String(b.password)); if (!u) return send(res, 401, { ok: false, error: "Логин немесе құпия сөз қате" }); const token = crypto.randomBytes(24).toString("hex"); sessions.set(token, u); return send(res, 200, { ok: true, token, user: publicUser(u) }); } // ---------- ПАРОЛЬДЫ ҚАЛПЫНА КЕЛТІРУ ---------- if (req.method === "POST" && url === "/api/recover-password") { const b = await body(req); const users = loadUsers(); const u = users.find((x) => x.login === String(b.login)); if (!u) return send(res, 404, { ok: false, error: "Мұндай логин табылмады" }); const newPass = "Qonaev" + Math.floor(1000 + Math.random() * 9000); u.password = newPass; saveUsers(users); return send(res, 200, { ok: true, password: newPass, name: u.name }); } // ---------- АККАУНТТАРДЫ БАСҚАРУ (админ) ---------- if (req.method === "GET" && url === "/api/users") { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); return send(res, 200, { ok: true, data: loadUsers().map((x) => ({ login: x.login, name: x.name, role: x.role, password: x.password, })) }); } if (req.method === "POST" && url === "/api/users") { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const b = await body(req); if (!b.login || !b.name || !b.password) return send(res, 400, { ok: false, error: "Логин, аты-жөні және құпия сөз міндетті" }); const users = loadUsers(); if (users.find((x) => x.login === String(b.login))) return send(res, 400, { ok: false, error: "Мұндай логин бар" }); users.push({ login: String(b.login), password: String(b.password), name: String(b.name), role: b.role === "admin" ? "admin" : "user" }); saveUsers(users); return send(res, 200, { ok: true }); } const uDelMatch = url.match(/^\/api\/users\/([^/]+)$/); if (req.method === "DELETE" && uDelMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const users = loadUsers(); const i = users.findIndex((x) => x.login === uDelMatch[1]); if (i < 0) return send(res, 404, { ok: false, error: "Табылмады" }); users.splice(i, 1); saveUsers(users); return send(res, 200, { ok: true }); } const uPassMatch = url.match(/^\/api\/users\/([^/]+)\/password$/); if (req.method === "POST" && uPassMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const b = await body(req); const users = loadUsers(); const u = users.find((x) => x.login === uPassMatch[1]); if (!u) return send(res, 404, { ok: false, error: "Табылмады" }); if (!b.password) return send(res, 400, { ok: false, error: "Жаңа құпия сөзді жазыңыз" }); u.password = String(b.password); saveUsers(users); return send(res, 200, { ok: true }); } // ---------- АУТЕНТИФИКАЦИЯ ---------- const user = getUser(req); const isApi = url.startsWith("/api/"); if (isApi && !user) return send(res, 401, { ok: false, error: "Кіру қажет" }); // ---------- КОНТЕНТ ---------- if (req.method === "GET" && url === "/api/content") { return send(res, 200, { ok: true, data: { announcements: db.announcements, org: db.org, contacts: db.contacts, insider: db.insider, giftRules: db.giftRules, videos: db.videos, laws: db.laws, tests: db.tests.map((t) => ({ id: t.id, title: t.title, pass: t.pass, questions: t.questions.map((q) => ({ q: q.q, options: q.options })), })), }}); } // ---------- ХАБАРЛАНДЫРУ (админ) ---------- if (req.method === "POST" && url === "/api/announcements") { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const b = await body(req); if (!b.title || !b.text) return send(res, 400, { ok: false, error: "Тақырып пен мәтінді толтырыңыз" }); db.announcements.unshift({ id: Date.now(), title: b.title, text: b.text, date: b.date || new Date().toISOString().slice(0, 10) }); save(db); return send(res, 200, { ok: true }); } // ---------- ШАҒЫМДАР ---------- if (req.method === "GET" && url === "/api/complaints") { let list = db.complaints; if (user.role !== "admin") { list = list.filter((c) => c.authorLogin === user.login || c.anonymous); } return send(res, 200, { ok: true, data: list }); } if (req.method === "POST" && url === "/api/complaints") { const b = await body(req); if (!b.text) return send(res, 400, { ok: false, error: "Мәтінді толтырыңыз" }); const item = { id: Date.now(), category: b.category || "Басқа", text: String(b.text).slice(0, 3000), anonymous: !!b.anonymous, status: "new", date: new Date().toISOString().slice(0, 10), authorLogin: b.anonymous ? null : user.login, authorName: b.anonymous ? null : user.name, }; db.complaints.unshift(item); save(db); return send(res, 200, { ok: true }); } const compReplyMatch = url.match(/^\/api\/complaints\/(\d+)\/reply$/); if (req.method === "POST" && compReplyMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const b = await body(req); const c = db.complaints.find((x) => x.id === Number(compReplyMatch[1])); if (!c) return send(res, 404, { ok: false, error: "Табылмады" }); if (!b.reply) return send(res, 400, { ok: false, error: "Жауап мәтінін жазыңыз" }); c.reply = String(b.reply).slice(0, 3000); c.replyDate = new Date().toISOString().slice(0, 10); if (c.status === "new") c.status = "work"; save(db); return send(res, 200, { ok: true }); } const stMatch = url.match(/^\/api\/complaints\/(\d+)\/status$/); if (req.method === "POST" && stMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const b = await body(req); const c = db.complaints.find((x) => x.id === Number(stMatch[1])); if (!c) return send(res, 404, { ok: false, error: "Табылмады" }); if (["new", "work", "done"].includes(b.status)) c.status = b.status; save(db); return send(res, 200, { ok: true }); } // ---------- ТЕСТ НӘТИЖЕЛЕРІ ---------- if (req.method === "GET" && url === "/api/test-results") { let list = db.testResults; if (user.role !== "admin") list = list.filter((r) => r.login === user.login); return send(res, 200, { ok: true, data: list }); } if (req.method === "POST" && url === "/api/test-results") { const b = await body(req); const test = db.tests.find((t) => t.id === Number(b.testId)); if (!test) return send(res, 400, { ok: false, error: "Тест табылмады" }); const answers = Array.isArray(b.answers) ? b.answers : []; let correct = 0; test.questions.forEach((q, i) => { if (answers[i] === q.correct) correct++; }); const total = test.questions.length; const score = total ? Math.round((correct / total) * 100) : 0; db.testResults.push({ id: Date.now(), testId: test.id, login: user.login, name: user.name, score, date: new Date().toISOString().slice(0, 10), }); save(db); return send(res, 200, { ok: true, score, correct, total }); } // ---------- КОМПЛАЕНС ОФИЦЕРГЕ ЖЕКЕ ХАТ ---------- if (req.method === "GET" && url === "/api/direct-messages") { let list = db.directMessages || []; if (user.role !== "admin") list = list.filter((m) => m.authorLogin === user.login); return send(res, 200, { ok: true, data: list }); } if (req.method === "POST" && url === "/api/direct-messages") { const b = await body(req); if (!b.text) return send(res, 400, { ok: false, error: "Мәтінді толтырыңыз" }); const item = { id: Date.now(), text: String(b.text).slice(0, 3000), category: b.category || "Жалпы", status: "new", date: new Date().toISOString().slice(0, 10), authorLogin: b.anonymous ? null : user.login, authorName: b.anonymous ? null : user.name, anonymous: !!b.anonymous, }; if (!db.directMessages) db.directMessages = []; db.directMessages.unshift(item); save(db); return send(res, 200, { ok: true }); } const dmReplyMatch = url.match(/^\/api\/direct-messages\/(\d+)\/reply$/); if (req.method === "POST" && dmReplyMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const b = await body(req); const m = (db.directMessages || []).find((x) => x.id === Number(dmReplyMatch[1])); if (!m) return send(res, 404, { ok: false, error: "Табылмады" }); if (!b.reply) return send(res, 400, { ok: false, error: "Жауап мәтінін жазыңыз" }); m.reply = String(b.reply).slice(0, 3000); m.replyDate = new Date().toISOString().slice(0, 10); if (m.status === "new") m.status = "work"; save(db); return send(res, 200, { ok: true }); } const dmMatch = url.match(/^\/api\/direct-messages\/(\d+)\/status$/); if (req.method === "POST" && dmMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const b = await body(req); const m = (db.directMessages || []).find((x) => x.id === Number(dmMatch[1])); if (!m) return send(res, 404, { ok: false, error: "Табылмады" }); if (["new", "work", "done"].includes(b.status)) m.status = b.status; save(db); return send(res, 200, { ok: true }); } // ---------- СТАТИСТИКА (админ) ---------- if (req.method === "GET" && url === "/api/stats") { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); const byStatus = { new: 0, work: 0, done: 0 }; const byCategory = {}; db.complaints.forEach((c) => { byStatus[c.status] = (byStatus[c.status] || 0) + 1; byCategory[c.category] = (byCategory[c.category] || 0) + 1; }); const byTest = db.tests.map((t) => { const rs = db.testResults.filter((r) => r.testId === t.id); const avg = rs.length ? Math.round(rs.reduce((s, r) => s + r.score, 0) / rs.length) : 0; return { title: t.title, count: rs.length, avg }; }); const dms = db.directMessages || []; const dmOpen = dms.filter((m) => m.status !== "done").length; return send(res, 200, { ok: true, data: { users: loadUsers().length, complaintsTotal: db.complaints.length, complaintsOpen: byStatus.new + byStatus.work, testsTotal: db.testResults.length, dmTotal: dms.length, dmOpen, dmDone: dms.length - dmOpen, byStatus, byCategory: Object.keys(byCategory).map((k) => ({ cat: k, count: byCategory[k] })), byTest, }}); } // ---------- СТАТИКА ---------- let file = url === "/" ? "/index.html" : url; 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" : ext === ".json" ? "application/json" : "text/html"; res.writeHead(200, { "Content-Type": type + "; charset=utf-8" }); return res.end(fs.readFileSync(full)); } send(res, 404, { ok: false, error: "Not found" }); }).listen(PORT, () => console.log("Qonaev TV compliance порталы: http://localhost:" + PORT));