const http = require("http"); const fs = require("fs"); const path = require("path"); const PORT = process.env.PORT || 3000; const DATA = path.join(__dirname, "data.json"); const EMPTY = { commonTodos: [], personalTodos: [], systems: [], servers: [], admins: [], users: [], distribution: [], appUsers: [], auditLog: [] }; function logAction(user, action, details) { const data = load(); data.auditLog.push({ id: Date.now(), when: new Date().toISOString(), user: user, action: action, details: details }); if (data.auditLog.length > 1000) data.auditLog = data.auditLog.slice(-1000); save(data); } function load() { try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return EMPTY; } } function save(data) { fs.writeFileSync(DATA, 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({}); } }); }); } function json(res, data) { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(data)); } function notFound(res) { res.writeHead(404); res.end("Not found"); } function forbidden(res) { res.writeHead(403); res.end(JSON.stringify({ error: "Forbidden" })); } function checkPermission(req, section, action) { const user = req.headers["x-user"]; if (!user) return false; const data = load(); const appUser = (data.appUsers || []).find(u => u.login === user); if (!appUser) return false; const role = appUser.role; // Admin — полный доступ if (role === "admin") return true; // Оператор — только просмотр на отдельные вкладки + создание личных задач if (role === "operator") { if (action === "view") { // Оператор видит: дашборд, системы, серверы, админы, операторы, распределение const viewSections = ["dashboard", "systems", "servers", "admins", "operators", "distribution"]; return viewSections.includes(section); } // Оператор может создавать только личные задачи if (action === "create" && section === "personal-todos") return true; if (action === "complete" && section === "todos") return true; // закрывать свои задачи return false; } // Модератор — все вкладки кроме "Пользователи приложения" + гибкие права if (role === "moderator") { const perms = appUser.permissions || {}; // Проверка доступа к вкладке if (action === "view") { // Модератор НЕ видит "Пользователи приложения" if (section === "appUsers" || section === "users") return false; const sectionPerms = perms[section] || {}; return sectionPerms.view === true; } // Проверка действий (create, edit, delete) if (section === "appUsers" || section === "users") return false; const sectionPerms = perms[section] || {}; return sectionPerms[action] === true; } return false; } http.createServer(async (req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-User"); if (req.method === "OPTIONS") { res.writeHead(200, { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, X-User" }); res.end(); return; } const url = req.url.split("?")[0]; console.log("Request:", req.method, url); // API: Todos // API: Common Todos (общие задачи) if (req.method === "GET" && url === "/api/common-todos") { const data = load(); return json(res, data.commonTodos || []); } if (req.method === "POST" && url === "/api/common-todos") { const data = load(); const user = req.headers["x-user"]; const item = await body(req); item.id = Date.now(); item.createdBy = user; item.createdAt = new Date().toISOString(); // userStates: { login: { completed: boolean, completedAt: string|null } } item.userStates = {}; data.commonTodos.push(item); save(data); logAction(user, "create_common_todo", item.title); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/common-todos/")) { const id = parseInt(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; const idx = data.commonTodos.findIndex((t) => t.id === id); if (idx !== -1) { const bodyData = await body(req); // Если есть userState, обновляем только для текущего пользователя if (bodyData.userState !== undefined) { if (!data.commonTodos[idx].userStates) data.commonTodos[idx].userStates = {}; data.commonTodos[idx].userStates[user] = bodyData.userState; } else { data.commonTodos[idx] = { ...data.commonTodos[idx], ...bodyData }; } save(data); logAction(user, "update_common_todo", "id=" + id); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/common-todos/")) { const id = parseInt(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; data.commonTodos = data.commonTodos.filter((t) => t.id !== id); save(data); logAction(user, "delete_common_todo", "id=" + id); return json(res, { ok: true }); } // API: Personal Todos (личные задачи) if (req.method === "GET" && url === "/api/personal-todos") { const user = req.headers["x-user"]; const data = load(); const todos = data.personalTodos.filter(t => t.createdBy === user); return json(res, todos); } if (req.method === "POST" && url === "/api/personal-todos") { const data = load(); const user = req.headers["x-user"]; const item = await body(req); item.id = Date.now(); item.completed = false; item.completedAt = null; item.createdBy = user; data.personalTodos.push(item); save(data); logAction(user, "create_personal_todo", item.title); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/personal-todos/")) { const id = parseInt(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; const idx = data.personalTodos.findIndex((t) => t.id === id); if (idx !== -1) { data.personalTodos[idx] = { ...data.personalTodos[idx], ...(await body(req)) }; save(data); logAction(user, "update_personal_todo", "id=" + id); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/personal-todos/")) { const id = parseInt(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; data.personalTodos = data.personalTodos.filter((t) => t.id !== id); save(data); logAction(user, "delete_personal_todo", "id=" + id); return json(res, { ok: true }); } // API: Systems if (req.method === "GET" && url === "/api/systems") { return json(res, load().systems); } if (req.method === "POST" && url === "/api/systems") { if (!checkPermission(req, "systems", "edit")) return forbidden(res); const data = load(); const item = await body(req); item.id = Date.now(); data.systems.push(item); save(data); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/systems/")) { if (!checkPermission(req, "systems", "edit")) return forbidden(res); const id = parseInt(url.split("/").pop()); const data = load(); const idx = data.systems.findIndex((s) => s.id === id); if (idx !== -1) { data.systems[idx] = { ...data.systems[idx], ...(await body(req)) }; save(data); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/systems/")) { const id = parseInt(url.split("/").pop()); const data = load(); data.systems = data.systems.filter((s) => s.id !== id); save(data); return json(res, { ok: true }); } // API: Servers if (req.method === "GET" && url === "/api/servers") { return json(res, load().servers); } if (req.method === "POST" && url === "/api/servers") { if (!checkPermission(req, "servers", "edit")) return forbidden(res); const data = load(); const item = await body(req); item.id = Date.now(); data.servers.push(item); save(data); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/servers/")) { if (!checkPermission(req, "servers", "edit")) return forbidden(res); const id = parseInt(url.split("/").pop()); const data = load(); const idx = data.servers.findIndex((s) => s.id === id); if (idx !== -1) { data.servers[idx] = { ...data.servers[idx], ...(await body(req)) }; save(data); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/servers/")) { if (!checkPermission(req, "servers", "edit")) return forbidden(res); const id = parseInt(url.split("/").pop()); const data = load(); data.servers = data.servers.filter((s) => s.id !== id); save(data); return json(res, { ok: true }); } // API: Admins if (req.method === "GET" && url === "/api/admins") { return json(res, load().admins); } if (req.method === "POST" && url === "/api/admins") { const data = load(); const item = await body(req); item.id = Date.now(); data.admins.push(item); save(data); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/admins/")) { const id = parseInt(url.split("/").pop()); const data = load(); const idx = data.admins.findIndex((a) => a.id === id); if (idx !== -1) { data.admins[idx] = { ...data.admins[idx], ...(await body(req)) }; save(data); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/admins/")) { const idParam = url.split("/").pop(); const id = idParam.includes('.') ? parseFloat(idParam) : parseInt(idParam); const data = load(); data.admins = data.admins.filter((a) => a.id !== id); save(data); return json(res, { ok: true }); } // API: Admin Servers (multiple servers per admin) if (req.method === "GET" && url === "/api/admin-servers") { return json(res, load().adminServers || []); } if (req.method === "POST" && url === "/api/admin-servers") { const data = load(); const item = await body(req); item.id = Date.now(); if (!data.adminServers) data.adminServers = []; data.adminServers.push(item); save(data); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/admin-servers/")) { const id = parseInt(url.split("/").pop()); const data = load(); const idx = (data.adminServers || []).findIndex((s) => s.id === id); if (idx !== -1) { data.adminServers[idx] = { ...data.adminServers[idx], ...(await body(req)) }; save(data); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/admin-servers/")) { const id = parseInt(url.split("/").pop()); const data = load(); data.adminServers = (data.adminServers || []).filter((s) => s.id !== id); save(data); return json(res, { ok: true }); } // API: Import servers from file if (req.method === "POST" && url === "/api/import-servers") { const data = load(); const fs = require("fs"); const path = require("path"); const filePath = path.join(__dirname, "список серверов.txt"); if (!fs.existsSync(filePath)) { return json(res, { ok: false, error: "Файл не найден" }); } const content = fs.readFileSync(filePath, "utf8"); const lines = content.trim().split("\n").slice(1); const rows = lines.map(line => { const cols = line.split("\t"); return { admin: cols[0], city: cols[1], region: cols[2], ip: cols[3], cityCode: cols[4] }; }); const existingAdmins = new Set(data.admins.map(a => a.name)); rows.forEach(row => { if (!existingAdmins.has(row.admin)) { data.admins.push({ id: Date.now() + Math.random(), name: row.admin, position: "", phone: "", email: "" }); existingAdmins.add(row.admin); } }); const existingIps = new Set(data.servers.map(s => s.ip)); const addedIps = new Set(); rows.forEach(row => { const ip = row.ip.trim(); if (!existingIps.has(ip) && !addedIps.has(ip)) { data.servers.push({ id: Date.now() + Math.random(), admin: row.admin, city: row.city, region: row.region, cityCode: row.cityCode.trim(), ip: ip }); addedIps.add(ip); } }); save(data); return json(res, { ok: true, count: rows.length }); } // API: Import admins from file (старый формат) if (req.method === "POST" && url === "/api/import-admins") { const data = load(); const fs = require("fs"); const path = require("path"); const filePath = path.join(__dirname, "список админов.txt"); if (!fs.existsSync(filePath)) { return json(res, { ok: false, error: "Файл не найден" }); } const content = fs.readFileSync(filePath, "utf8"); const lines = content.trim().split("\n").slice(1); const rows = lines.map(line => { const cols = line.split("\t"); return { name: cols[0], workPhone: cols[1], homePhone: cols[2], mobilePhone: cols[3], city: cols[4] }; }); const existingNames = new Set(data.admins.map(a => a.name)); let added = 0; rows.forEach(row => { if (!existingNames.has(row.name)) { data.admins.push({ id: Date.now() + Math.random(), name: row.name, position: "", workPhone: row.workPhone || "", homePhone: row.homePhone || "", mobilePhone: row.mobilePhone || "", email: "" }); existingNames.add(row.name); added++; } }); save(data); return json(res, { ok: true, count: added }); } // API: Import admins from file -1 (обновление существующих) if (req.method === "POST" && url === "/api/import-admins-1") { const data = load(); const fs = require("fs"); const path = require("path"); const filePath = path.join(__dirname, "список админов-1.txt"); if (!fs.existsSync(filePath)) { return json(res, { ok: false, error: "Файл не найден" }); } const content = fs.readFileSync(filePath, "utf8"); const lines = content.trim().split("\n").slice(1); const rows = lines.map(line => { const cols = line.split("\t"); return { name: cols[0], workPhone: cols[1], homePhone: cols[2], mobilePhone: cols[3], city: cols[4] }; }); let updated = 0; let added = 0; rows.forEach(row => { const idx = data.admins.findIndex(a => a.name === row.name); if (idx !== -1) { // Обновляем существующего data.admins[idx].workPhone = row.workPhone; data.admins[idx].homePhone = row.homePhone; data.admins[idx].mobilePhone = row.mobilePhone; updated++; } else { // Добавляем нового data.admins.push({ id: Date.now() + Math.random(), name: row.name, position: "", workPhone: row.workPhone || "", homePhone: row.homePhone || "", mobilePhone: row.mobilePhone || "", email: "" }); added++; } }); save(data); return json(res, { ok: true, updated, added }); } // API: Users if (req.method === "GET" && url === "/api/users") { return json(res, load().users); } if (req.method === "POST" && url === "/api/users") { const data = load(); const item = await body(req); item.id = Date.now(); data.users.push(item); save(data); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/users/")) { const id = parseInt(url.split("/").pop()); const data = load(); const idx = data.users.findIndex((u) => u.id === id); if (idx !== -1) { data.users[idx] = { ...data.users[idx], ...(await body(req)) }; save(data); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/users/")) { const id = parseInt(url.split("/").pop()); const data = load(); data.users = data.users.filter((u) => u.id !== id); save(data); return json(res, { ok: true }); } // API: Distribution if (req.method === "GET" && url === "/api/distribution") { return json(res, load().distribution); } if (req.method === "POST" && url === "/api/distribution") { const data = load(); const user = req.headers["x-user"]; const item = await body(req); item.id = Date.now(); data.distribution.push(item); save(data); logAction(user, "create_distribution", JSON.stringify(item)); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/distribution/")) { const id = parseInt(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; const idx = data.distribution.findIndex((d) => d.id === id); if (idx !== -1) { data.distribution[idx] = { ...data.distribution[idx], ...(await body(req)) }; save(data); logAction(user, "update_distribution", "id=" + id); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/distribution/")) { const id = parseInt(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; data.distribution = data.distribution.filter((d) => d.id !== id); save(data); logAction(user, "delete_distribution", "id=" + id); return json(res, { ok: true }); } // API: Auth if (req.method === "POST" && url === "/api/login") { const data = load(); const { login, password } = await body(req); const user = data.appUsers.find((u) => u.login === login && u.password === password); if (user) { logAction(login, "login", "ok"); const permissions = user.role === 'admin' ? 'all' : (user.permissions || {}); return json(res, { ok: true, user: { login: user.login, role: user.role, name: user.name, permissions } }); } logAction(login || "unknown", "login", "failed"); res.writeHead(401, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: false, error: "Неверный логин или пароль" })); } // API: Profile текущего пользователя if (req.method === "GET" && url === "/api/profile") { const user = req.headers["x-user"]; if (!user) return forbidden(res); const data = load(); const appUser = data.appUsers.find((u) => u.login === user); if (!appUser) return forbidden(res); // Поддержка старых полей для совместимости return json(res, { login: appUser.login, name: appUser.name, position: appUser.position, workPhone: appUser.workPhone || appUser.phone || "", homePhone: appUser.homePhone || "", mobilePhone: appUser.mobilePhone || "", email: appUser.email || "", city: appUser.city || "", ipAddress: appUser.ipAddress || appUser.dbAddress || "", password: appUser.password || "" }); } if (req.method === "PUT" && url === "/api/profile") { const user = req.headers["x-user"]; if (!user) return forbidden(res); const data = load(); const idx = data.appUsers.findIndex((u) => u.login === user); if (idx === -1) return forbidden(res); const upd = await body(req); // Сохраняем новые поля, оставляем старые для совместимости data.appUsers[idx] = { ...data.appUsers[idx], ...upd, workPhone: upd.workPhone !== undefined ? upd.workPhone : data.appUsers[idx].workPhone, homePhone: upd.homePhone !== undefined ? upd.homePhone : data.appUsers[idx].homePhone, mobilePhone: upd.mobilePhone !== undefined ? upd.mobilePhone : data.appUsers[idx].mobilePhone, ipAddress: upd.ipAddress !== undefined ? upd.ipAddress : data.appUsers[idx].ipAddress }; save(data); logAction(user, "update_profile", user); return json(res, { ok: true }); } // API: Users (app users management) if (req.method === "GET" && url === "/api/app-users") { const users = load().appUsers.map((u) => ({ login: u.login, role: u.role, name: u.name, position: u.position, phone: u.phone, workPhone: u.workPhone, homePhone: u.homePhone, mobilePhone: u.mobilePhone, email: u.email, city: u.city, cityCode: u.cityCode, dbAddress: u.dbAddress, ipAddress: u.ipAddress, permissions: u.permissions })); return json(res, users); } if (req.method === "POST" && url === "/api/app-users") { const data = load(); const user = req.headers["x-user"]; const item = await body(req); if (data.appUsers.find((u) => u.login === item.login)) { res.writeHead(400, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: false, error: "Пользователь существует" })); } data.appUsers.push({ login: item.login, password: item.password, name: item.name || "", position: item.position || "", email: item.email || "", city: item.city || "", workPhone: item.workPhone || item.phone || "", homePhone: item.homePhone || "", mobilePhone: item.mobilePhone || "", ipAddress: item.ipAddress || item.dbAddress || "", role: item.role, permissions: item.role === "moderator" ? item.permissions || {} : {} }); save(data); logAction(user, "create_user", item.login); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/app-users/")) { const login = decodeURIComponent(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; const idx = data.appUsers.findIndex((u) => u.login === login); if (idx !== -1) { const upd = await body(req); // Сохраняем новые поля и поддерживаем старые для совместимости data.appUsers[idx] = { ...data.appUsers[idx], ...upd, workPhone: upd.workPhone !== undefined ? upd.workPhone : data.appUsers[idx].workPhone, homePhone: upd.homePhone !== undefined ? upd.homePhone : data.appUsers[idx].homePhone, mobilePhone: upd.mobilePhone !== undefined ? upd.mobilePhone : data.appUsers[idx].mobilePhone, ipAddress: upd.ipAddress !== undefined ? upd.ipAddress : data.appUsers[idx].ipAddress }; save(data); logAction(user, "update_user", login); } return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/app-users/")) { const login = decodeURIComponent(url.split("/").pop()); const data = load(); const user = req.headers["x-user"]; data.appUsers = data.appUsers.filter((u) => u.login !== login); save(data); logAction(user, "delete_user", login); return json(res, { ok: true }); } // API: Sync admins to app-users if (req.method === "POST" && url === "/api/sync-admins-to-users") { const data = load(); const user = req.headers["x-user"]; let created = 0; let updated = 0; (data.admins || []).forEach(admin => { // Используем login из админа, если нет - генерируем из имени let login = admin.login || ''; if (!login) { login = (admin.name || '').toLowerCase().replace(/[^a-zа-яё0-9]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, ''); } if (!login) return; const existing = data.appUsers.find(u => u.login === login); if (!existing) { // Создаём нового пользователя с правами оператора по умолчанию data.appUsers.push({ login: login, password: "password123", name: admin.name || "", position: admin.position || "", phone: admin.workPhone || admin.phone || "", email: admin.email || "", city: admin.city || "", cityCode: admin.cityCode || "", dbAddress: "", role: "operator", permissions: {} }); created++; logAction(user, "create_user_from_admin", login); } else { // Обновляем данные существующего existing.name = admin.name || existing.name; existing.position = admin.position || existing.position; existing.phone = admin.workPhone || admin.phone || existing.phone; existing.email = admin.email || existing.email; existing.city = admin.city || existing.city; existing.cityCode = admin.cityCode || existing.cityCode; updated++; } }); save(data); logAction(user, "sync_admins", "created=" + created + ", updated=" + updated); return json(res, { ok: true, created, updated }); } // API: Audit log if (req.method === "GET" && url === "/api/audit") { return json(res, load().auditLog.reverse().slice(0, 200)); } // API: Roles (admin only) if (req.method === "GET" && url === "/api/roles") { const user = req.headers["x-user"]; const data = load(); const appUser = (data.appUsers || []).find(u => u.login === user); if (!appUser || appUser.role !== "admin") return forbidden(res); return json(res, data.roles || []); } if (req.method === "POST" && url === "/api/roles") { const user = req.headers["x-user"]; const data = load(); const appUser = (data.appUsers || []).find(u => u.login === user); if (!appUser || appUser.role !== "admin") return forbidden(res); const b = await body(req); if (!data.roles) data.roles = []; if (data.roles.find(r => r.name === b.name)) return json(res, { error: "Роль существует" }, 400); data.roles.push({ name: b.name, description: b.description || "", permissions: b.permissions || {} }); save(data); logAction(user, "create", "role: " + b.name); return json(res, { ok: true }); } if (req.method === "PUT" && url.startsWith("/api/roles/")) { const user = req.headers["x-user"]; const data = load(); const appUser = (data.appUsers || []).find(u => u.login === user); if (!appUser || appUser.role !== "admin") return forbidden(res); const oldName = decodeURIComponent(url.split("/")[3]); const role = (data.roles || []).find(r => r.name === oldName); if (!role) return notFound(res); const b = await body(req); if (b.name && b.name !== oldName && data.roles.find(r => r.name === b.name)) { return json(res, { error: "Роль с таким именем существует" }, 400); } role.name = b.name || oldName; role.description = b.description !== undefined ? b.description : role.description; role.permissions = b.permissions !== undefined ? b.permissions : role.permissions; save(data); logAction(user, "edit", "role: " + oldName); return json(res, { ok: true }); } if (req.method === "DELETE" && url.startsWith("/api/roles/")) { const user = req.headers["x-user"]; const data = load(); const appUser = (data.appUsers || []).find(u => u.login === user); if (!appUser || appUser.role !== "admin") return forbidden(res); const name = decodeURIComponent(url.split("/")[3]); if (name === "admin") return json(res, { error: "Нельзя удалить роль admin" }, 400); const idx = (data.roles || []).findIndex(r => r.name === name); if (idx === -1) return notFound(res); data.roles.splice(idx, 1); // Сбросить роль у пользователей (data.appUsers || []).forEach(u => { if (u.role === name) u.role = "operator"; }); save(data); logAction(user, "delete", "role: " + name); return json(res, { ok: true }); } // API: Update moderator permissions (admin only) if (req.method === "POST" && url === "/api/update-permissions") { const user = req.headers["x-user"]; const data = load(); const appUser = (data.appUsers || []).find(u => u.login === user); if (!appUser || appUser.role !== "admin") return forbidden(res); (data.appUsers || []).forEach(u => { if (u.role === "moderator" && u.permissions) { const old = u.permissions; u.permissions = {}; ["todos", "systems", "servers", "admins", "operators", "distribution", "audit"].forEach(section => { const oldPerm = old[section] || {}; u.permissions[section] = { view: oldPerm.view === true, create: oldPerm.edit === true, edit: oldPerm.edit === true, delete: oldPerm.edit === true }; }); } if (u.role === "operator" && !u.permissions) { u.permissions = {}; } }); save(data); return json(res, { ok: true }); } // Static files let file = url === "/" ? "/index.html" : url; 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" : "text/html"; res.writeHead(200, { "Content-Type": type + "; charset=utf-8" }); return res.end(fs.readFileSync(full)); } notFound(res); }).listen(PORT, () => console.log("Server running on port " + PORT));