ubt-tarih-sayt-dayyndalu-ush/server.js

320 lines
11 KiB
JavaScript

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));