compliance-tv/server.js

161 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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