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"); function load() { try { return JSON.parse(fs.readFileSync(DATA_FILE, "utf8")); } catch (e) { return { users: [], announcements: [], org: [], videos: [], laws: [], tests: [], complaints: [], testResults: [] }; } } function save(d) { fs.writeFileSync(DATA_FILE, JSON.stringify(d, 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 = db.users.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) }); } // ---------- АУТЕНТИФИКАЦИЯ ---------- 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, 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 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: "Тест табылмады" }); db.testResults.push({ id: Date.now(), testId: test.id, login: user.login, name: user.name, score: Number(b.score) || 0, date: new Date().toISOString().slice(0, 10), }); 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 }; }); return send(res, 200, { ok: true, data: { users: db.users.length, complaintsTotal: db.complaints.length, complaintsOpen: byStatus.new + byStatus.work, testsTotal: db.testResults.length, 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));