590 lines
22 KiB
JavaScript
590 lines
22 KiB
JavaScript
const http = require("http");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const formidable = require("formidable");
|
||
const pdfParse = require("pdf-parse");
|
||
const mammoth = require("mammoth");
|
||
const XLSX = require("xlsx");
|
||
|
||
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, { recursive: true });
|
||
}
|
||
|
||
function loadData() {
|
||
try {
|
||
return JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
|
||
} catch (_) {
|
||
return {
|
||
checks: [],
|
||
templates: [
|
||
{
|
||
id: 1,
|
||
name: "Рассмотрение штрафных санкций",
|
||
requiredDocs: [
|
||
"Служебная записка",
|
||
"Договор",
|
||
"Дополнительное соглашение",
|
||
"Расчёт",
|
||
"Подтверждение нарушения",
|
||
"Переписка с подрядчиком",
|
||
"Уведомление",
|
||
"Акт",
|
||
"Заключение ответственного подразделения"
|
||
]
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "Заключение договора",
|
||
requiredDocs: [
|
||
"Служебная записка",
|
||
"Проект договора",
|
||
"Обоснование цены",
|
||
"Согласование контрагента",
|
||
"Лицензии (при необходимости)"
|
||
]
|
||
},
|
||
{
|
||
id: 3,
|
||
name: "Дополнительное соглашение",
|
||
requiredDocs: [
|
||
"Служебная записка",
|
||
"Проект дополнительного соглашения",
|
||
"Обоснование изменений",
|
||
"Расчёт изменений (если есть)"
|
||
]
|
||
}
|
||
],
|
||
typicalRemarks: [
|
||
{ id: 1, category: "КРИТИЧНО", type: "Расчёт", text: "Расчёт не подтверждён документально", blocking: true },
|
||
{ id: 2, category: "КРИТИЧНО", type: "Договор", text: "В представленных документах имеются расхождения в размере суммы", blocking: true },
|
||
{ id: 3, category: "ТРЕБУЕТ ДОРАБОТКИ", type: "Акт", text: "Отсутствует документ, подтверждающий дату возникновения обязательства", blocking: false },
|
||
{ id: 4, category: "ТРЕБУЕТ ДОРАБОТКИ", type: "Расчёт", text: "Количество дней просрочки не соответствует представленному расчёту", blocking: false },
|
||
{ id: 5, category: "ТЕХНИЧЕСКОЕ", type: "Любой", text: "Опечатка в наименовании организации", blocking: false }
|
||
]
|
||
};
|
||
}
|
||
}
|
||
|
||
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 extractText(filePath, originalName) {
|
||
try {
|
||
const ext = path.extname(originalName).toLowerCase();
|
||
|
||
if (ext === ".pdf") {
|
||
const dataBuffer = fs.readFileSync(filePath);
|
||
const data = await pdfParse(dataBuffer);
|
||
return { text: data.text, pages: data.numpages };
|
||
}
|
||
|
||
if (ext === ".docx") {
|
||
const result = await mammoth.extractRawText({ path: filePath });
|
||
return { text: result.value, pages: 1 };
|
||
}
|
||
|
||
if (ext === ".xlsx" || ext === ".xls") {
|
||
const wb = XLSX.readFile(filePath);
|
||
let text = "";
|
||
wb.SheetNames.forEach((sheetName) => {
|
||
const sheet = wb.Sheets[sheetName];
|
||
text += `=== ${sheetName} ===\n`;
|
||
text += XLSX.utils.sheet_to_csv(sheet) + "\n";
|
||
});
|
||
return { text, pages: wb.SheetNames.length };
|
||
}
|
||
|
||
if (ext === ".txt") {
|
||
const text = fs.readFileSync(filePath, "utf8");
|
||
return { text, pages: 1 };
|
||
}
|
||
|
||
return { text: "", pages: 0 };
|
||
} catch (err) {
|
||
console.error("Error extracting text:", err);
|
||
return { text: "", pages: 0 };
|
||
}
|
||
}
|
||
|
||
function classifyDocument(text, filename) {
|
||
const lowerText = text.toLowerCase();
|
||
const lowerName = filename.toLowerCase();
|
||
|
||
const classifiers = [
|
||
{ type: "Служебная записка", keywords: ["служебная записка", "докладываю", "предлагаю", "прошу рассмотреть"] },
|
||
{ type: "Договор", keywords: ["договор", "контракт", "стороны обязуются", "предмет договора", "условия договора"] },
|
||
{ type: "Дополнительное соглашение", keywords: ["дополнительное соглашение", "изменить договор", "внести изменения"] },
|
||
{ type: "Акт выполненных работ", keywords: ["акт выполненных работ", "акт приёмки", "работы выполнены", "услуги оказаны"] },
|
||
{ type: "Расчёт", keywords: ["расчёт", "калькуляция", "сумма", "итого", "всего"] },
|
||
{ type: "Претензия", keywords: ["претензия", "требую", "нарушение обязательств", "неустойка"] },
|
||
{ type: "Уведомление", keywords: ["уведомляем", "уведомление", "извещаем"] },
|
||
{ type: "Протокол", keywords: ["протокол", "заседание", "решили", "постановили"] },
|
||
{ type: "Пояснительная записка", keywords: ["пояснительная записка", "пояснения", "обоснование"] },
|
||
{ type: "Справка", keywords: ["справка", "подтверждает", "выдана"] },
|
||
{ type: "Заключение", keywords: ["заключение", "вывод", "рекомендация"] },
|
||
{ type: "Письмо", keywords: ["исх.", "вх.", "сообщаем", "информируем"] },
|
||
{ type: "Доверенность", keywords: ["доверенность", "уполномочиваю", "представлять интересы"] },
|
||
{ type: "Приказ", keywords: ["приказ", "приказываю", "распоряжение"] },
|
||
{ type: "Таблица", keywords: ["№ п/п", "наименование", "количество", "цена"] }
|
||
];
|
||
|
||
let bestMatch = { type: "Иной документ", score: 0 };
|
||
|
||
for (const classifier of classifiers) {
|
||
let score = 0;
|
||
for (const keyword of classifier.keywords) {
|
||
if (lowerText.includes(keyword)) score += 2;
|
||
if (lowerName.includes(keyword.toLowerCase())) score += 3;
|
||
}
|
||
if (score > bestMatch.score) {
|
||
bestMatch = { type: classifier.type, score };
|
||
}
|
||
}
|
||
|
||
return bestMatch.score > 0 ? bestMatch.type : "Иной документ";
|
||
}
|
||
|
||
function extractRequisites(text) {
|
||
const requisites = {};
|
||
|
||
const contractNumberMatch = text.match(/договор\s*(?:№|N|N-o)?\s*(\d+(?:\/[\d-]+)?)/i);
|
||
if (contractNumberMatch) requisites.contractNumber = contractNumberMatch[1];
|
||
|
||
const contractDateMatch = text.match(/(?:от|даты)\s*(\d{1,2}\.\d{1,2}\.\d{2,4})/i);
|
||
if (contractDateMatch) requisites.contractDate = contractDateMatch[1];
|
||
|
||
const amountMatch = text.match(/(\d{1,3}(?:\s?\d{3})*(?:\.\d+)?\s*(?:тенге|₸|KZT|руб\.?|рублей))/i);
|
||
if (amountMatch) requisites.amount = amountMatch[1];
|
||
|
||
const counterpartyMatch = text.match(/(?:ООО|ТОО|АО|ИП|КГУ)\s*["']?([^"'\n]{5,100})["']?/i);
|
||
if (counterpartyMatch) requisites.counterparty = counterpartyMatch[1].trim();
|
||
|
||
const iinMatch = text.match(/(?:ИИН|БИН)\s*(\d{9,12})/i);
|
||
if (iinMatch) requisites.iin = iinMatch[1];
|
||
|
||
const periodMatch = text.match(/(?:период|срок)\s*(?:с|по|до)?\s*(\d{1,2}\.\d{1,2}\.\d{2,4})\s*(?:по|до|по)\s*(\d{1,2}\.\d{1,2}\.\d{2,4})/i);
|
||
if (periodMatch) {
|
||
requisites.periodFrom = periodMatch[1];
|
||
requisites.periodTo = periodMatch[2];
|
||
}
|
||
|
||
const daysMatch = text.match(/(\d+)\s*(?:дн\.?|дней|дня|день|календарных?\s*дней)/i);
|
||
if (daysMatch) requisites.days = parseInt(daysMatch[1]);
|
||
|
||
const penaltyMatch = text.match(/(?:неустойка|пеня|штраф)\s*(?:в размере|составляет)?\s*(\d{1,3}(?:\s?\d{3})*(?:\.\d+)?\s*(?:тенге|₸|KZT|%|процент))/i);
|
||
if (penaltyMatch) requisites.penalty = penaltyMatch[1];
|
||
|
||
const signatoryMatch = text.match(/(?:должность|подписал|руководитель)[\s\n:]+([^\n]{5,80})/i);
|
||
if (signatoryMatch) requisites.signatory = signatoryMatch[1].trim();
|
||
|
||
return requisites;
|
||
}
|
||
|
||
function checkArithmetic(text) {
|
||
const errors = [];
|
||
const lines = text.split("\n");
|
||
|
||
for (let i = 0; i < lines.length; i++) {
|
||
const line = lines[i];
|
||
|
||
const multiplicationMatch = line.match(/(\d+(?:\.\d+)?)\s*[\*x×]\s*(\d+(?:\.\d+)?)\s*=?\s*(\d+(?:\.\d+)?)/i);
|
||
if (multiplicationMatch) {
|
||
const a = parseFloat(multiplicationMatch[1]);
|
||
const b = parseFloat(multiplicationMatch[2]);
|
||
const result = parseFloat(multiplicationMatch[3]);
|
||
const expected = a * b;
|
||
if (Math.abs(expected - result) / expected > 0.01) {
|
||
errors.push({
|
||
line: i + 1,
|
||
type: "Умножение",
|
||
formula: `${a} × ${b}`,
|
||
indicated: result,
|
||
calculated: expected,
|
||
difference: Math.abs(expected - result)
|
||
});
|
||
}
|
||
}
|
||
|
||
const additionMatch = line.match(/(\d+(?:\.\d+)?)\s*\+\s*(\d+(?:\.\d+)?)\s*=?\s*(\d+(?:\.\d+)?)/i);
|
||
if (additionMatch) {
|
||
const a = parseFloat(additionMatch[1]);
|
||
const b = parseFloat(additionMatch[2]);
|
||
const result = parseFloat(additionMatch[3]);
|
||
const expected = a + b;
|
||
if (Math.abs(expected - result) / (expected || 1) > 0.01) {
|
||
errors.push({
|
||
line: i + 1,
|
||
type: "Сложение",
|
||
formula: `${a} + ${b}`,
|
||
indicated: result,
|
||
calculated: expected,
|
||
difference: Math.abs(expected - result)
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return errors;
|
||
}
|
||
|
||
function compareRequisites(docs) {
|
||
const allReqs = docs.map(d => ({ name: d.name, type: d.docType, ...d.requisites }));
|
||
const discrepancies = [];
|
||
|
||
const fields = ["contractNumber", "contractDate", "counterparty", "amount", "iin"];
|
||
const fieldNames = {
|
||
contractNumber: "Номер договора",
|
||
contractDate: "Дата договора",
|
||
counterparty: "Контрагент",
|
||
amount: "Сумма",
|
||
iin: "ИИН/БИН"
|
||
};
|
||
|
||
for (const field of fields) {
|
||
const values = {};
|
||
for (const req of allReqs) {
|
||
if (req[field]) {
|
||
const key = String(req[field]).trim();
|
||
if (!values[key]) values[key] = [];
|
||
values[key].push(req.name);
|
||
}
|
||
}
|
||
|
||
if (Object.keys(values).length > 1) {
|
||
const entries = Object.entries(values);
|
||
discrepancies.push({
|
||
field: fieldNames[field],
|
||
values: entries.map(([val, docs]) => ({ value: val, documents: docs })),
|
||
severity: field === "amount" || field === "contractNumber" ? "КРИТИЧНО" : "ТРЕБУЕТ ДОРАБОТКИ"
|
||
});
|
||
}
|
||
}
|
||
|
||
return discrepancies;
|
||
}
|
||
|
||
function generateRemarks(docs, discrepancies, arithmeticErrors) {
|
||
const remarks = [];
|
||
const existingIds = new Set();
|
||
|
||
for (const disc of discrepancies) {
|
||
const id = Date.now() + Math.random() * 1000;
|
||
if (!existingIds.has(id)) {
|
||
existingIds.add(id);
|
||
remarks.push({
|
||
id,
|
||
category: disc.severity,
|
||
type: "Расхождение",
|
||
document: "Сравнение документов",
|
||
text: `Выявлено расхождение в поле "${disc.field}": ${disc.values.map(v => `${v.documents.join(", ")} — ${v.value}`).join("; ")}`,
|
||
recommendation: "Уточнить корректное значение и привести документы к единому значению",
|
||
confirmed: true,
|
||
page: null
|
||
});
|
||
}
|
||
}
|
||
|
||
for (const err of arithmeticErrors) {
|
||
const id = Date.now() + Math.random() * 1000 + remarks.length;
|
||
if (!existingIds.has(id)) {
|
||
existingIds.add(id);
|
||
remarks.push({
|
||
id,
|
||
category: "КРИТИЧНО",
|
||
type: "Арифметика",
|
||
document: err.document || "Не определён",
|
||
text: `Ошибка в расчёте: ${err.formula} = ${err.calculated}, указано ${err.indicated}`,
|
||
recommendation: "Проверить расчёт и исправить значение",
|
||
confirmed: true,
|
||
page: err.line
|
||
});
|
||
}
|
||
}
|
||
|
||
return remarks;
|
||
}
|
||
|
||
http.createServer(async (req, res) => {
|
||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||
|
||
if (req.method === "OPTIONS") {
|
||
res.writeHead(200);
|
||
res.end();
|
||
return;
|
||
}
|
||
|
||
const url = req.url.split("?")[0];
|
||
|
||
if (req.method === "GET" && url === "/api/data") {
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(loadData()));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/checks") {
|
||
const data = await body(req);
|
||
const db = loadData();
|
||
const newCheck = {
|
||
id: Date.now(),
|
||
...data,
|
||
status: "Новая",
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
version: 1,
|
||
documents: [],
|
||
remarks: [],
|
||
conclusion: null
|
||
};
|
||
db.checks.push(newCheck);
|
||
saveData(db);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, check: newCheck }));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "GET" && url.startsWith("/api/checks/")) {
|
||
const id = parseInt(url.split("/")[3]);
|
||
const db = loadData();
|
||
const check = db.checks.find(c => c.id === id);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(check || {}));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "PUT" && url.startsWith("/api/checks/")) {
|
||
const id = parseInt(url.split("/")[3]);
|
||
const updateData = await body(req);
|
||
const db = loadData();
|
||
const idx = db.checks.findIndex(c => c.id === id);
|
||
if (idx !== -1) {
|
||
db.checks[idx] = { ...db.checks[idx], ...updateData, updatedAt: new Date().toISOString() };
|
||
saveData(db);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, check: db.checks[idx] }));
|
||
} else {
|
||
res.writeHead(404);
|
||
res.end(JSON.stringify({ error: "Not found" }));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (req.method === "DELETE" && url.startsWith("/api/checks/")) {
|
||
const id = parseInt(url.split("/")[3]);
|
||
const db = loadData();
|
||
db.checks = db.checks.filter(c => c.id !== id);
|
||
saveData(db);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true }));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/upload") {
|
||
const checkId = parseInt(req.url.split("?")[1]?.split("=")[1] || "0");
|
||
|
||
const form = formidable({
|
||
uploadDir: UPLOADS_DIR,
|
||
keepExtensions: true,
|
||
maxFileSize: 50 * 1024 * 1024
|
||
});
|
||
|
||
form.parse(req, async (err, fields, files) => {
|
||
if (err) {
|
||
res.writeHead(500, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: err.message }));
|
||
return;
|
||
}
|
||
|
||
const file = files.file && files.file[0];
|
||
if (!file) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: "No file uploaded" }));
|
||
return;
|
||
}
|
||
|
||
const originalName = file.originalFilename || file.filepath.split("/").pop();
|
||
const ext = path.extname(originalName).toLowerCase();
|
||
const supportedExts = [".pdf", ".docx", ".xlsx", ".xls", ".jpg", ".jpeg", ".png", ".txt"];
|
||
|
||
if (!supportedExts.includes(ext)) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: "Unsupported format" }));
|
||
return;
|
||
}
|
||
|
||
const fileSize = fs.statSync(file.filepath).size;
|
||
const { text, pages } = await extractText(file.filepath, originalName);
|
||
const docType = classifyDocument(text, originalName);
|
||
const requisites = extractRequisites(text);
|
||
const arithmeticErrors = checkArithmetic(text);
|
||
|
||
const docRecord = {
|
||
id: Date.now(),
|
||
name: originalName,
|
||
format: ext.slice(1).toUpperCase(),
|
||
size: fileSize,
|
||
uploadedAt: new Date().toISOString(),
|
||
pages,
|
||
docType,
|
||
status: "Обработан",
|
||
text,
|
||
requisites,
|
||
arithmeticErrors
|
||
};
|
||
|
||
if (checkId) {
|
||
const db = loadData();
|
||
const check = db.checks.find(c => c.id === checkId);
|
||
if (check) {
|
||
check.documents.push(docRecord);
|
||
check.updatedAt = new Date().toISOString();
|
||
|
||
// Сохраняем ручные замечания (у которых нет типа "Расхождение" или "Арифметика")
|
||
const manualRemarks = (check.remarks || []).filter(r =>
|
||
r.type !== "Расхождение" && r.type !== "Арифметика"
|
||
);
|
||
|
||
const discrepancies = compareRequisites(check.documents);
|
||
const allArithmeticErrors = check.documents.flatMap(d =>
|
||
(d.arithmeticErrors || []).map(e => ({ ...e, document: d.name }))
|
||
);
|
||
const autoRemarks = generateRemarks(check.documents, discrepancies, allArithmeticErrors);
|
||
check.remarks = [...manualRemarks, ...autoRemarks];
|
||
|
||
saveData(db);
|
||
}
|
||
}
|
||
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, document: docRecord }));
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/analyze") {
|
||
const { checkId } = await body(req);
|
||
const db = loadData();
|
||
const check = db.checks.find(c => c.id === checkId);
|
||
|
||
if (!check) {
|
||
res.writeHead(404, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ error: "Check not found" }));
|
||
return;
|
||
}
|
||
|
||
const discrepancies = compareRequisites(check.documents);
|
||
const allArithmeticErrors = check.documents.flatMap(d =>
|
||
(d.arithmeticErrors || []).map(e => ({ ...e, document: d.name }))
|
||
);
|
||
|
||
// Сохраняем только ручные замечания, автоматические перегенерируем
|
||
const manualRemarks = (check.remarks || []).filter(r =>
|
||
r.type !== "Расхождение" && r.type !== "Арифметика" && r.type !== "Комплектность"
|
||
);
|
||
|
||
const autoRemarks = generateRemarks(check.documents, discrepancies, allArithmeticErrors);
|
||
|
||
const template = db.templates.find(t => t.name === check.questionType);
|
||
if (template) {
|
||
const providedTypes = check.documents.map(d => d.docType);
|
||
for (const required of template.requiredDocs) {
|
||
const isProvided = providedTypes.some(t => t.includes(required) || required.includes(t));
|
||
if (!isProvided) {
|
||
autoRemarks.push({
|
||
id: Date.now() + Math.random() * 1000,
|
||
category: "КРИТИЧНО",
|
||
type: "Комплектность",
|
||
document: required,
|
||
text: `Отсутствует обязательный документ: ${required}`,
|
||
recommendation: "Представить недостающий документ",
|
||
confirmed: true,
|
||
page: null
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
check.remarks = [...manualRemarks, ...autoRemarks];
|
||
|
||
const criticalCount = check.remarks.filter(r => r.category === "КРИТИЧНО" && r.confirmed).length;
|
||
check.status = criticalCount > 0 ? "На доработке" : "Готово к комиссии";
|
||
check.updatedAt = new Date().toISOString();
|
||
|
||
saveData(db);
|
||
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, remarks: check.remarks, status: check.status }));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "GET" && url === "/api/templates") {
|
||
const db = loadData();
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(db.templates));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/templates") {
|
||
const data = await body(req);
|
||
const db = loadData();
|
||
const newTemplate = { id: Date.now(), ...data };
|
||
db.templates.push(newTemplate);
|
||
saveData(db);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, template: newTemplate }));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "GET" && url === "/api/remarks") {
|
||
const db = loadData();
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify(db.typicalRemarks));
|
||
return;
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/remarks") {
|
||
const data = await body(req);
|
||
const db = loadData();
|
||
const newRemark = { id: Date.now(), ...data };
|
||
db.typicalRemarks.push(newRemark);
|
||
saveData(db);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, remark: newRemark }));
|
||
return;
|
||
}
|
||
|
||
let file = url === "/" ? "/index.html" : 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; charset=utf-8" : ext === ".js" ? "application/javascript; charset=utf-8" : "text/html; charset=utf-8";
|
||
res.writeHead(200, { "Content-Type": type });
|
||
res.end(fs.readFileSync(full));
|
||
return;
|
||
}
|
||
|
||
res.writeHead(404);
|
||
res.end("Not found");
|
||
}).listen(PORT, () => console.log("Сервер запущен на порту " + PORT));
|