Бэкенд: сервер + data.json (законы, структура, видео-загрузка, жалобы, результаты тестов)
This commit is contained in:
parent
12e74010c1
commit
c7b78ec948
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
data.json
|
||||
uploads/
|
||||
node_modules/
|
||||
.vibe42-run.log
|
||||
.vibe42-run.pid
|
||||
82
package-lock.json
generated
Normal file
82
package-lock.json
generated
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "compliance-tv",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "compliance-tv",
|
||||
"dependencies": {
|
||||
"formidable": "^3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@paralleldrive/cuid2": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
|
||||
"integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
|
||||
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"dezalgo": "^1.0.4",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
package.json
Normal file
9
package.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "compliance-tv",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": { "start": "node server.js" },
|
||||
"dependencies": {
|
||||
"formidable": "^3.5.1"
|
||||
}
|
||||
}
|
||||
151
script.js
151
script.js
@ -1,37 +1,6 @@
|
||||
var LAWS = [
|
||||
{ id: 1, title: "Қазақстан Республикасының «Сыбайлас жемқорлыққа қарсы іс-қимыл туралы» Заңы", cat: "Заң", desc: "Сыбайлас жемқорлыққа қарсы іс-қимылдың негізгі қағидаттары, тыйымдар мен жауапкершілік." },
|
||||
{ id: 2, title: "ҚР «Мемлекеттік қызмет туралы» Заңы", cat: "Заң", desc: "Мемлекеттік қызметшілердің этикасы мен шектеулері, мүдделер қақтығысын реттеу." },
|
||||
{ id: 3, title: "«Жеке деректер және оларды қорғау туралы» Заң", cat: "Заң", desc: "Жеке деректерді жинау, өңдеу және сақтау талаптары." },
|
||||
{ id: 4, title: "Қазақстан Республикасының Азаматтық кодексі (құпия ақпарат бөлімі)", cat: "Құқық", desc: "Коммерциялық құпия және құпия ақпарат режимі." },
|
||||
{ id: 5, title: "«Ақпаратқа қол жеткізу туралы» Заң", cat: "Заң", desc: "Ақпаратқа қол жеткізу тәртібі және қызметтік ақпараттың құпиялылығы." },
|
||||
{ id: 6, title: "Телеканалдың Ішкі еңбек тәртібі", cat: "Ішкі регламент", desc: "Еңбек тәртібі, қауіпсіздік және жұмыс орнындағы әдеп нормалары." },
|
||||
{ id: 7, title: "Қаржылық бақылау жөніндегі нұсқаулық", cat: "Ішкі регламент", desc: "Қаржылық операцияларды бақылау, сыйлықтар мен төлемдерді декларациялау тәртібі." },
|
||||
{ id: 8, title: "Ақпараттық қауіпсіздік саясаты", cat: "Ішкі регламент", desc: "Ақпараттық ресурстарды пайдалану, пароль және құпия сөздермен жұмыс ережелері." },
|
||||
{ id: 9, title: "«Ақшаны жылыстатуға қарсы іс-қимыл туралы» Заң", cat: "Заң", desc: "Күдікті операциялар туралы хабарлау және қаржылық мониторинг талаптары." },
|
||||
{ id: 10, title: "Комплаенс-бақылау жөніндегі ереже", cat: "Ішкі регламент", desc: "Комплаенс-тексерулер, өзін-өзі тексеру және есептілік тәртібі." }
|
||||
];
|
||||
|
||||
var STRUCTURE = [
|
||||
{ id: 1, name: "Айгүл Сейтова", role: "Бас директор", dept: "Әкімшілік", head: true, emoji: "👩💼" },
|
||||
{ id: 2, name: "Ерлан Қасымов", role: "Директордың орынбасары", dept: "Әкімшілік", head: false, emoji: "👨💼" },
|
||||
{ id: 3, name: "Асет Бисенов", role: "Комплаенс офицер", dept: "Комплаенс қызметі", head: false, emoji: "🛡️" },
|
||||
{ id: 4, name: "Гүлназ Омарова", role: "Заңгер", dept: "Құқықтық департамент", head: false, emoji: "⚖️" },
|
||||
{ id: 5, name: "Дәурен Нұрлан", role: "Бас бухгалтер", dept: "Қаржы департаменті", head: false, emoji: "💼" },
|
||||
{ id: 6, name: "Майра Жұмабек", role: "HR директоры", dept: "Кадр департаменті", head: false, emoji: "🧑🏫" },
|
||||
{ id: 7, name: "Бауыржан Серік", role: "IT директоры", dept: "IT департаменті", head: false, emoji: "💻" },
|
||||
{ id: 8, name: "Ляззат Төлеген", role: "Бас редактор", dept: "Редакция", head: false, emoji: "📰" }
|
||||
];
|
||||
|
||||
var VIDEOS = [
|
||||
{ id: 1, title: "Комплаенс деген не?", category: "Кіріспе", duration: "4:32", desc: "Комплаенс ұғымы және телеканалдағы рөлі" },
|
||||
{ id: 2, title: "Мүдделер қақтығысы: қалай анықтауға?", category: "Этика", duration: "3:45", desc: "Мүдделер қақтығысының белгілері мен шешімдері" },
|
||||
{ id: 3, title: "Сыбайлас жемқорлыққа қарсы саясат", category: "Сыбайлас жемқорлық", duration: "5:18", desc: "Негізгі тыйымдар мен жауапкершілік" },
|
||||
{ id: 4, title: "Сыйлықтар мен төлемдер: шегі қандай?", category: "Сыбайлас жемқорлық", duration: "3:20", desc: "Сыйлық қабылдау ережелері және декларациялау" },
|
||||
{ id: 5, title: "Құпия ақпаратпен жұмыс", category: "Ақпарат", duration: "4:55", desc: "Құпия ақпаратты қорғау ережелері" },
|
||||
{ id: 6, title: "Жеке деректерді қорғау", category: "Ақпарат", duration: "3:55", desc: "Жеке деректермен жұмыс істеу талаптары" },
|
||||
{ id: 7, title: "Қысым мен кемсітушілік", category: "Этика", duration: "4:28", desc: "Жұмыс орнындағы әдеп және теңдік" },
|
||||
{ id: 8, title: "Әлеуметтік желілердегі мінез-құлық", category: "Этика", duration: "3:38", desc: "Қызметкердің әлеуметтік желідегі әдебі" }
|
||||
];
|
||||
var LAWS = [];
|
||||
var STRUCTURE = [];
|
||||
var VIDEOS = [];
|
||||
|
||||
var QUESTIONS = [
|
||||
{ q: "Сыйлықты декларациясыз қабылдауға болатын ең жоғары шекті мөлшері:", options: ["5 АЕК", "10 АЕК", "20 АЕК", "Кез келген"], correct: 0 },
|
||||
@ -52,14 +21,7 @@ var testTaken = false;
|
||||
var userName = "";
|
||||
var userDept = "";
|
||||
|
||||
var mockResults = [
|
||||
{ name: "Айгүл Сейтова", dept: "Әкімшілік", score: 10, total: 10, date: "2026-08-10" },
|
||||
{ name: "Дәурен Нұрлан", dept: "Қаржы", score: 9, total: 10, date: "2026-08-10" },
|
||||
{ name: "Майра Жұмабек", dept: "Кадр", score: 8, total: 10, date: "2026-08-09" },
|
||||
{ name: "Бауыржан Серік", dept: "IT", score: 7, total: 10, date: "2026-08-09" },
|
||||
{ name: "Ляззат Төлеген", dept: "Редакция", score: 6, total: 10, date: "2026-08-08" }
|
||||
];
|
||||
|
||||
var mockResults = [];
|
||||
var results = [];
|
||||
var resultsBackendReady = false;
|
||||
|
||||
@ -67,6 +29,19 @@ function getResults() {
|
||||
return resultsBackendReady ? results : mockResults;
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
Promise.all([
|
||||
fetch("/api/laws").then(function (r) { return r.json(); }).then(function (d) { LAWS = d; }),
|
||||
fetch("/api/structure").then(function (r) { return r.json(); }).then(function (d) { STRUCTURE = d; }),
|
||||
fetch("/api/videos").then(function (r) { return r.json(); }).then(function (d) { VIDEOS = d; })
|
||||
]).then(function () {
|
||||
renderLawChips();
|
||||
renderLaws();
|
||||
renderStructure();
|
||||
renderVideos();
|
||||
}).catch(function () {});
|
||||
}
|
||||
|
||||
function renderLaws() {
|
||||
var query = (document.getElementById("law-search").value || "").toLowerCase();
|
||||
var activeCat = document.querySelector(".chip.active");
|
||||
@ -122,9 +97,13 @@ function renderStructure() {
|
||||
|
||||
function renderVideos() {
|
||||
var grid = document.getElementById("video-grid");
|
||||
if (!VIDEOS.length) {
|
||||
grid.innerHTML = '<div class="empty">Әзірге видео жоқ — офицер жүктеген кезде пайда болады</div>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = VIDEOS.map(function (v) {
|
||||
return '<div class="video-card" onclick="openVideo(' + v.id + ')">' +
|
||||
'<div class="video-thumbnail">' + v.desc + '</div>' +
|
||||
'<div class="video-thumbnail">' + (v.desc || "") + '</div>' +
|
||||
'<div class="video-info">' +
|
||||
'<div class="video-title">' + v.title + '</div>' +
|
||||
'<div class="video-meta"><span>' + v.category + '</span><span class="video-duration">' + v.duration + '</span></div>' +
|
||||
@ -134,7 +113,18 @@ function renderVideos() {
|
||||
|
||||
function openVideo(id) {
|
||||
var v = VIDEOS.find(function (x) { return x.id === id; });
|
||||
if (v) alert("🎬 «" + v.title + "»\n\nРеалды нұсқада мұнда видео ойнайды (файлды офицер жүктеген кезде).");
|
||||
if (!v) return;
|
||||
if (v.src) {
|
||||
var overlay = document.createElement("div");
|
||||
overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,.9);z-index:999;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:16px";
|
||||
overlay.innerHTML = '<video src="' + v.src + '" controls autoplay style="max-width:90vw;max-height:80vh;border-radius:10px"></video>' +
|
||||
'<button style="padding:10px 24px;border:none;border-radius:8px;cursor:pointer;font-size:14px">Жабу (X)</button>';
|
||||
overlay.querySelector("button").addEventListener("click", function () { overlay.remove(); });
|
||||
overlay.addEventListener("click", function (e) { if (e.target === overlay) overlay.remove(); });
|
||||
document.body.appendChild(overlay);
|
||||
} else {
|
||||
alert("🎬 «" + v.title + "»\n\nРеалды нұсқада мұнда видео ойнайды (файлды офицер жүктеген кезде).");
|
||||
}
|
||||
}
|
||||
|
||||
function initTest() {
|
||||
@ -208,17 +198,20 @@ function finishTest() {
|
||||
}
|
||||
|
||||
function saveResult(name, dept, sc, total) {
|
||||
var rec = { name: name, dept: dept, score: sc, total: total, date: new Date().toISOString().slice(0, 10) };
|
||||
if (resultsBackendReady) {
|
||||
fetch("/api/results", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(rec)
|
||||
}).catch(function () {});
|
||||
} else {
|
||||
results.push(rec);
|
||||
renderDashboard();
|
||||
}
|
||||
var rec = { name: name, dept: dept, score: sc, total: total };
|
||||
fetch("/api/results", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(rec)
|
||||
}).then(function () {
|
||||
return fetch("/api/results").then(function (r) { return r.json(); });
|
||||
}).then(function (d) {
|
||||
if (Array.isArray(d)) {
|
||||
results = d;
|
||||
resultsBackendReady = true;
|
||||
renderDashboard();
|
||||
}
|
||||
}).catch(function () {});
|
||||
}
|
||||
|
||||
function renderStats() {
|
||||
@ -308,19 +301,17 @@ document.getElementById("complaint-form").addEventListener("submit", function (e
|
||||
category: form.category.value,
|
||||
text: form.text.value
|
||||
};
|
||||
if (resultsBackendReady) {
|
||||
fetch("/api/complaints", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data)
|
||||
}).then(function () {
|
||||
form.reset();
|
||||
showFormSuccess();
|
||||
}).catch(function () { form.reset(); showFormSuccess(); });
|
||||
} else {
|
||||
fetch("/api/complaints", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data)
|
||||
}).then(function () {
|
||||
form.reset();
|
||||
showFormSuccess();
|
||||
}
|
||||
}).catch(function () {
|
||||
form.reset();
|
||||
showFormSuccess();
|
||||
});
|
||||
});
|
||||
|
||||
function showFormSuccess() {
|
||||
@ -332,7 +323,28 @@ function showFormSuccess() {
|
||||
|
||||
document.getElementById("video-form").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
alert("📤 Бұл мүмкіндік сервер қосылғанда жұмыс істейді (Рецепт: видео жүктеу).");
|
||||
var form = e.target;
|
||||
var btn = form.querySelector("button");
|
||||
var file = form.file.files[0];
|
||||
if (!file) { alert("Видео файлды таңдаңыз"); return; }
|
||||
var fd = new FormData();
|
||||
fd.append("title", form.title.value);
|
||||
fd.append("file", file);
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Жүктелуде...";
|
||||
fetch("/api/upload", { method: "POST", body: fd })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (d.ok) {
|
||||
alert("✅ Видео жүктелді!");
|
||||
form.reset();
|
||||
loadData();
|
||||
} else {
|
||||
alert("Қате: файл жүктелмеді");
|
||||
}
|
||||
})
|
||||
.catch(function () { alert("Сервер қолжетімсіз"); })
|
||||
.finally(function () { btn.disabled = false; btn.textContent = "Жүктеу"; });
|
||||
});
|
||||
|
||||
document.querySelectorAll(".nav-link").forEach(function (link) {
|
||||
@ -344,9 +356,6 @@ document.querySelectorAll(".nav-link").forEach(function (link) {
|
||||
});
|
||||
});
|
||||
|
||||
renderLawChips();
|
||||
renderLaws();
|
||||
renderStructure();
|
||||
renderVideos();
|
||||
renderDashboard();
|
||||
loadData();
|
||||
tryBackend();
|
||||
|
||||
160
server.js
Normal file
160
server.js
Normal file
@ -0,0 +1,160 @@
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const formidable = require("formidable").default || require("formidable");
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const ROOT = __dirname;
|
||||
const DATA = path.join(ROOT, "data.json");
|
||||
const UPLOADS = path.join(ROOT, "uploads");
|
||||
const QUESTIONS_TOTAL = 10;
|
||||
|
||||
if (!fs.existsSync(UPLOADS)) fs.mkdirSync(UPLOADS);
|
||||
|
||||
const EMPTY = { laws: [], structure: [], videos: [], complaints: [], results: [] };
|
||||
|
||||
const SEED = {
|
||||
laws: [
|
||||
{ id: 1, title: "Қазақстан Республикасының «Сыбайлас жемқорлыққа қарсы іс-қимыл туралы» Заңы", cat: "Заң", desc: "Сыбайлас жемқорлыққа қарсы іс-қимылдың негізгі қағидаттары, тыйымдар мен жауапкершілік." },
|
||||
{ id: 2, title: "ҚР «Мемлекеттік қызмет туралы» Заңы", cat: "Заң", desc: "Мемлекеттік қызметшілердің этикасы мен шектеулері, мүдделер қақтығысын реттеу." },
|
||||
{ id: 3, title: "«Жеке деректер және оларды қорғау туралы» Заң", cat: "Заң", desc: "Жеке деректерді жинау, өңдеу және сақтау талаптары." },
|
||||
{ id: 4, title: "Қазақстан Республикасының Азаматтық кодексі (құпия ақпарат бөлімі)", cat: "Құқық", desc: "Коммерциялық құпия және құпия ақпарат режимі." },
|
||||
{ id: 5, title: "«Ақпаратқа қол жеткізу туралы» Заң", cat: "Заң", desc: "Ақпаратқа қол жеткізу тәртібі және қызметтік ақпараттың құпиялылығы." },
|
||||
{ id: 6, title: "Телеканалдың Ішкі еңбек тәртібі", cat: "Ішкі регламент", desc: "Еңбек тәртібі, қауіпсіздік және жұмыс орнындағы әдеп нормалары." },
|
||||
{ id: 7, title: "Қаржылық бақылау жөніндегі нұсқаулық", cat: "Ішкі регламент", desc: "Қаржылық операцияларды бақылау, сыйлықтар мен төлемдерді декларациялау тәртібі." },
|
||||
{ id: 8, title: "Ақпараттық қауіпсіздік саясаты", cat: "Ішкі регламент", desc: "Ақпараттық ресурстарды пайдалану, пароль және құпия сөздермен жұмыс ережелері." },
|
||||
{ id: 9, title: "«Ақшаны жылыстатуға қарсы іс-қимыл туралы» Заң", cat: "Заң", desc: "Күдікті операциялар туралы хабарлау және қаржылық мониторинг талаптары." },
|
||||
{ id: 10, title: "Комплаенс-бақылау жөніндегі ереже", cat: "Ішкі регламент", desc: "Комплаенс-тексерулер, өзін-өзі тексеру және есептілік тәртібі." }
|
||||
],
|
||||
structure: [
|
||||
{ id: 1, name: "Айгүл Сейтова", role: "Бас директор", dept: "Әкімшілік", head: true, emoji: "👩💼" },
|
||||
{ id: 2, name: "Ерлан Қасымов", role: "Директордың орынбасары", dept: "Әкімшілік", head: false, emoji: "👨💼" },
|
||||
{ id: 3, name: "Асет Бисенов", role: "Комплаенс офицер", dept: "Комплаенс қызметі", head: false, emoji: "🛡️" },
|
||||
{ id: 4, name: "Гүлназ Омарова", role: "Заңгер", dept: "Құқықтық департамент", head: false, emoji: "⚖️" },
|
||||
{ id: 5, name: "Дәурен Нұрлан", role: "Бас бухгалтер", dept: "Қаржы департаменті", head: false, emoji: "💼" },
|
||||
{ id: 6, name: "Майра Жұмабек", role: "HR директоры", dept: "Кадр департаменті", head: false, emoji: "🧑🏫" },
|
||||
{ id: 7, name: "Бауыржан Серік", role: "IT директоры", dept: "IT департаменті", head: false, emoji: "💻" },
|
||||
{ id: 8, name: "Ляззат Төлеген", role: "Бас редактор", dept: "Редакция", head: false, emoji: "📰" }
|
||||
],
|
||||
videos: [
|
||||
{ id: 1, title: "Комплаенс деген не?", category: "Кіріспе", duration: "4:32", desc: "Комплаенс ұғымы және телеканалдағы рөлі" },
|
||||
{ id: 2, title: "Мүдделер қақтығысы: қалай анықтауға?", category: "Этика", duration: "3:45", desc: "Мүдделер қақтығысының белгілері мен шешімдері" },
|
||||
{ id: 3, title: "Сыбайлас жемқорлыққа қарсы саясат", category: "Сыбайлас жемқорлық", duration: "5:18", desc: "Негізгі тыйымдар мен жауапкершілік" },
|
||||
{ id: 4, title: "Сыйлықтар мен төлемдер: шегі қандай?", category: "Сыбайлас жемқорлық", duration: "3:20", desc: "Сыйлық қабылдау ережелері және декларациялау" },
|
||||
{ id: 5, title: "Құпия ақпаратпен жұмыс", category: "Ақпарат", duration: "4:55", desc: "Құпия ақпаратты қорғау ережелері" },
|
||||
{ id: 6, title: "Жеке деректерді қорғау", category: "Ақпарат", duration: "3:55", desc: "Жеке деректермен жұмыс істеу талаптары" },
|
||||
{ id: 7, title: "Қысым мен кемсітушілік", category: "Этика", duration: "4:28", desc: "Жұмыс орнындағы әдеп және теңдік" },
|
||||
{ id: 8, title: "Әлеуметтік желілердегі мінез-құлық", category: "Этика", duration: "3:38", desc: "Қызметкердің әлеуметтік желідегі әдебі" }
|
||||
],
|
||||
complaints: [],
|
||||
results: []
|
||||
};
|
||||
|
||||
function load() {
|
||||
let d;
|
||||
try { d = JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { d = null; }
|
||||
if (!d) {
|
||||
save(JSON.parse(JSON.stringify(SEED)));
|
||||
return JSON.parse(JSON.stringify(SEED));
|
||||
}
|
||||
["laws", "structure", "videos", "complaints", "results"].forEach(function (k) {
|
||||
if (!Array.isArray(d[k])) d[k] = [];
|
||||
});
|
||||
return d;
|
||||
}
|
||||
function save(d) {
|
||||
fs.writeFileSync(DATA, JSON.stringify(d, 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 json(res, code, obj) {
|
||||
res.writeHead(code, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
http.createServer(async (req, res) => {
|
||||
const url = req.url.split("?")[0];
|
||||
|
||||
// ===== Законы и материалы =====
|
||||
if (req.method === "GET" && url === "/api/laws") return json(res, 200, load().laws);
|
||||
if (req.method === "POST" && url === "/api/laws") {
|
||||
const d = load(); const b = await body(req);
|
||||
d.laws.push({ id: Date.now(), cat: "Ішкі регламент", ...b });
|
||||
save(d); return json(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// ===== Структура =====
|
||||
if (req.method === "GET" && url === "/api/structure") return json(res, 200, load().structure);
|
||||
|
||||
// ===== Видео =====
|
||||
if (req.method === "GET" && url === "/api/videos") return json(res, 200, load().videos);
|
||||
if (req.method === "POST" && url === "/api/upload") {
|
||||
const form = formidable({ uploadDir: UPLOADS, keepExtensions: true, maxFileSize: 200 * 1024 * 1024 });
|
||||
form.parse(req, (err, fields, files) => {
|
||||
const f = files.file && files.file[0];
|
||||
if (err || !f) return json(res, 400, { error: "file" });
|
||||
const rel = "uploads/" + path.basename(f.filepath);
|
||||
const d = load();
|
||||
d.videos.push({
|
||||
id: Date.now(),
|
||||
title: (fields.title && fields.title[0]) || "Видео сабақ",
|
||||
category: "Жаңа сабақ",
|
||||
duration: "—",
|
||||
desc: "Жаңадан қосылған видео сабақ",
|
||||
src: "/" + rel
|
||||
});
|
||||
save(d);
|
||||
return json(res, 200, { ok: true, src: "/" + rel });
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== Жалобы =====
|
||||
if (req.method === "GET" && url === "/api/complaints") return json(res, 200, load().complaints);
|
||||
if (req.method === "POST" && url === "/api/complaints") {
|
||||
const d = load(); const b = await body(req);
|
||||
d.complaints.push({
|
||||
id: Date.now(),
|
||||
category: b.category || "Басқа",
|
||||
text: b.text || "",
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
status: "жаңа"
|
||||
});
|
||||
save(d); return json(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// ===== Результаты тестов =====
|
||||
if (req.method === "GET" && url === "/api/results") return json(res, 200, load().results);
|
||||
if (req.method === "POST" && url === "/api/results") {
|
||||
const d = load(); const b = await body(req);
|
||||
d.results.push({
|
||||
name: b.name || "Аноним",
|
||||
dept: b.dept || "—",
|
||||
score: Number(b.score) || 0,
|
||||
total: Number(b.total) || QUESTIONS_TOTAL,
|
||||
date: new Date().toISOString().slice(0, 10)
|
||||
});
|
||||
save(d); return json(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// ===== Статика =====
|
||||
let file = url === "/" ? "/index.html" : url;
|
||||
const full = path.join(ROOT, file);
|
||||
if (full.startsWith(ROOT) && 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" :
|
||||
ext === ".webm" ? "video/webm" :
|
||||
ext === ".mov" ? "video/quicktime" : "text/html";
|
||||
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
|
||||
return res.end(fs.readFileSync(full));
|
||||
}
|
||||
json(res, 404, { error: "Not found" });
|
||||
}).listen(PORT, () => console.log("Compliance TV сервер: порт " + PORT));
|
||||
Loading…
Reference in New Issue
Block a user