const http = require("http"); const fs = require("fs"); const path = require("path"); const mammoth = require("mammoth"); const pdfParse = require("pdf-parse"); const XLSX = require("xlsx"); const formidable = require("formidable"); const PORT = process.env.PORT || 3000; const DATA_FILE = path.join(__dirname, "data.json"); const UPLOADS_DIR = path.join(__dirname, "uploads"); if (!fs.existsSync(UPLOADS_DIR)) fs.mkdirSync(UPLOADS_DIR); if (!fs.existsSync(DATA_FILE)) { fs.writeFileSync(DATA_FILE, JSON.stringify({ tests: [], videos: [], users: {}, results: [] })); } function loadData() { try { return JSON.parse(fs.readFileSync(DATA_FILE, "utf8")); } catch { return { tests: [], videos: [], users: {}, results: [] }; } } function saveData(data) { fs.writeFileSync(DATA_FILE, 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({}); } }); }); } async function parseTestFile(filepath, filename) { const ext = path.extname(filename).toLowerCase(); let questions = []; if (ext === ".docx") { const result = await mammoth.extractRawText({ path: filepath }); const lines = result.value.split("\n").filter(l => l.trim()); let currentQ = null; let options = []; for (const line of lines) { const trimmed = line.trim(); if (/^\d+[\.\)]/.test(trimmed)) { if (currentQ && options.length >= 4) { questions.push({ q: currentQ, options, correct: 0 }); } currentQ = trimmed.replace(/^\d+[\.\)]\s*/, ""); options = []; } else if (/^[A-ЯA-Z]\)/i.test(trimmed)) { options.push(trimmed.replace(/^[A-ЯA-Z]\)\s*/, "")); } } if (currentQ && options.length >= 4) { questions.push({ q: currentQ, options, correct: 0 }); } } else if (ext === ".pdf") { const data = await pdfParse(fs.readFileSync(filepath)); const text = data.text; const lines = text.split("\n").filter(l => l.trim()); let currentQ = null; let options = []; for (const line of lines) { const trimmed = line.trim(); if (/^\d+[\.\)]/.test(trimmed)) { if (currentQ && options.length >= 4) { questions.push({ q: currentQ, options, correct: 0 }); } currentQ = trimmed.replace(/^\d+[\.\)]\s*/, ""); options = []; } else if (/^[A-ЯA-Z]\)/i.test(trimmed)) { options.push(trimmed.replace(/^[A-ЯA-Z]\)\s*/, "")); } } if (currentQ && options.length >= 4) { questions.push({ q: currentQ, options, correct: 0 }); } } else if (ext === ".xlsx" || ext === ".xls") { const wb = XLSX.readFile(filepath); const sheet = wb.Sheets[wb.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 }); for (const row of rows) { if (row.length >= 6 && row[0]) { questions.push({ q: String(row[0]), options: [String(row[1]||""), String(row[2]||""), String(row[3]||""), String(row[4]||"")], correct: parseInt(row[5]) || 0 }); } } } return questions; } http.createServer(async (req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); if (req.method === "OPTIONS") { res.writeHead(200); return res.end(); } // API: Get all tests if (req.method === "GET" && req.url === "/api/tests") { const data = loadData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.tests)); } // API: Upload test file if (req.method === "POST" && req.url === "/api/upload-test") { const form = formidable({ uploadDir: UPLOADS_DIR, keepExtensions: true }); form.parse(req, async (err, fields, files) => { if (err) { res.writeHead(500, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: err.message })); } const file = files.file && files.file[0]; if (!file) { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Файл табылмады" })); } try { const questions = await parseTestFile(file.filepath, file.originalFilename); if (questions.length === 0) { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Сұрақтар табылмады. Форматты тексеріңіз." })); } const data = loadData(); const newTest = { id: Date.now(), title: fields.title || path.basename(file.originalFilename, path.extname(file.originalFilename)), topicId: parseInt(fields.topicId) || 1, questions, createdAt: new Date().toISOString() }; data.tests.push(newTest); saveData(data); fs.unlinkSync(file.filepath); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, count: questions.length, test: newTest })); } catch (e) { res.writeHead(500, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: e.message })); } }); return; } // API: Upload video if (req.method === "POST" && req.url === "/api/upload-video") { const form = formidable({ uploadDir: UPLOADS_DIR, keepExtensions: true }); form.parse(req, (err, fields, files) => { if (err) { res.writeHead(500, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: err.message })); } const file = files.file && files.file[0]; if (!file) { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Файл табылмады" })); } const data = loadData(); const newVideo = { id: Date.now(), title: fields.title || path.basename(file.originalFilename, path.extname(file.originalFilename)), topic: fields.topic || "Жалпы", filename: file.newFilename, originalName: file.originalFilename, size: file.size, createdAt: new Date().toISOString() }; data.videos.push(newVideo); saveData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, video: newVideo })); }); return; } // API: Get all videos if (req.method === "GET" && req.url === "/api/videos") { const data = loadData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.videos)); } // API: Get video file if (req.method === "GET" && req.url.startsWith("/api/video/")) { const filename = req.url.split("/")[3]; const videopath = path.join(UPLOADS_DIR, filename); if (fs.existsSync(videopath)) { res.writeHead(200, { "Content-Type": "video/mp4" }); return fs.createReadStream(videopath).pipe(res); } res.writeHead(404); return res.end("Not found"); } // API: Save result if (req.method === "POST" && req.url === "/api/result") { const result = await body(req); const data = loadData(); data.results.push(result); // Update user stats in data.json if (!data.users[result.username]) { data.users[result.username] = { tests: [], totalTime: 0 }; } data.users[result.username].tests.push({ test: result.test, percent: result.percent, date: result.date }); data.users[result.username].totalTime += result.time; saveData(data); // Also update in localStorage format for students try { const studentsFile = path.join(__dirname, 'students.json'); let students = {}; if (fs.existsSync(studentsFile)) { students = JSON.parse(fs.readFileSync(studentsFile, 'utf8')); } if (students[result.username]) { if (!students[result.username].tests) students[result.username].tests = []; students[result.username].tests.push({ test: result.test, percent: result.percent, date: result.date }); students[result.username].totalTime = (students[result.username].totalTime || 0) + result.time; fs.writeFileSync(studentsFile, JSON.stringify(students, null, 2)); } } catch (e) { console.error('Failed to update students.json:', e); } res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // API: Get user stats if (req.method === "GET" && req.url.startsWith("/api/user/")) { const username = req.url.split("/")[3]; const data = loadData(); const user = data.users[username] || { tests: [], totalTime: 0 }; res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(user)); } // API: Get all students (admin only) if (req.method === "GET" && req.url === "/api/students") { try { const studentsFile = path.join(__dirname, 'students.json'); let students = {}; if (fs.existsSync(studentsFile)) { students = JSON.parse(fs.readFileSync(studentsFile, 'utf8')); } res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(students)); } catch (e) { res.writeHead(500, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: e.message })); } } // API: Ban/unban student if (req.method === "POST" && req.url === "/api/ban-student") { const { phone, banned } = await body(req); try { const studentsFile = path.join(__dirname, 'students.json'); let students = {}; if (fs.existsSync(studentsFile)) { students = JSON.parse(fs.readFileSync(studentsFile, 'utf8')); } if (students[phone]) { students[phone].banned = banned; fs.writeFileSync(studentsFile, JSON.stringify(students, null, 2)); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } res.writeHead(404, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: 'Оқушы табылмады' })); } catch (e) { res.writeHead(500, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: e.message })); } } // Static files 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" : ext === ".mp4" ? "video/mp4" : "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));