na-kakuyu-temu-mozhno-sozdat/server.js

186 lines
5.7 KiB
JavaScript

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");
function readData() {
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { products: [], announcements: [], siteSettings: {} }; }
}
function writeData(data) {
fs.writeFileSync(DATA, JSON.stringify(data, null, 2));
}
function parseBody(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, status = 200) {
res.writeHead(status, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
res.end(JSON.stringify(data));
}
function notFound(res) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found");
}
const routes = {
// PUBLIC API
"GET /api/products": (req, res) => {
const data = readData();
json(res, { ok: true, data: data.products.sort((a, b) => a.order - b.order) });
},
"GET /api/products/:id": (req, res, params) => {
const data = readData();
const product = data.products.find(p => p.id === parseInt(params.id));
if (!product) return json(res, { ok: false, error: "Product not found" }, 404);
json(res, { ok: true, data: product });
},
"GET /api/announcements": (req, res) => {
const data = readData();
json(res, { ok: true, data: data.announcements.filter(a => a.active) });
},
"GET /api/settings": (req, res) => {
const data = readData();
json(res, { ok: true, data: data.siteSettings });
},
// ADMIN API
"POST /api/admin/products": async (req, res) => {
const body = await parseBody(req);
const data = readData();
const newProduct = {
id: Date.now(),
...body,
order: data.products.length + 1
};
data.products.push(newProduct);
writeData(data);
json(res, { ok: true, data: newProduct }, 201);
},
"PUT /api/admin/products/:id": async (req, res, params) => {
const body = await parseBody(req);
const data = readData();
const idx = data.products.findIndex(p => p.id === parseInt(params.id));
if (idx === -1) return json(res, { ok: false, error: "Product not found" }, 404);
data.products[idx] = { ...data.products[idx], ...body, id: parseInt(params.id) };
writeData(data);
json(res, { ok: true, data: data.products[idx] });
},
"DELETE /api/admin/products/:id": (req, res, params) => {
const data = readData();
data.products = data.products.filter(p => p.id !== parseInt(params.id));
writeData(data);
json(res, { ok: true, deleted: parseInt(params.id) });
},
"POST /api/admin/announcements": async (req, res) => {
const body = await parseBody(req);
const data = readData();
const newAnnouncement = { id: Date.now(), ...body, active: true };
data.announcements.push(newAnnouncement);
writeData(data);
json(res, { ok: true, data: newAnnouncement }, 201);
},
"PUT /api/admin/announcements/:id": async (req, res, params) => {
const body = await parseBody(req);
const data = readData();
const idx = data.announcements.findIndex(a => a.id === parseInt(params.id));
if (idx === -1) return json(res, { ok: false, error: "Not found" }, 404);
data.announcements[idx] = { ...data.announcements[idx], ...body, id: parseInt(params.id) };
writeData(data);
json(res, { ok: true, data: data.announcements[idx] });
},
"DELETE /api/admin/announcements/:id": (req, res, params) => {
const data = readData();
data.announcements = data.announcements.filter(a => a.id !== parseInt(params.id));
writeData(data);
json(res, { ok: true, deleted: parseInt(params.id) });
},
"PUT /api/admin/settings": async (req, res) => {
const body = await parseBody(req);
const data = readData();
data.siteSettings = { ...data.siteSettings, ...body };
writeData(data);
json(res, { ok: true, data: data.siteSettings });
}
};
const mimeTypes = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "application/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".png": "image/png",
".jpg": "image/jpeg",
".svg": "image/svg+xml"
};
http.createServer(async (req, res) => {
const url = req.url.split("?")[0];
const method = req.method;
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");
if (method === "OPTIONS") {
res.writeHead(204);
return res.end();
}
// API ROUTING
for (const [route, handler] of Object.entries(routes)) {
const [m, p] = route.split(" ");
if (method !== m) continue;
const routeParts = p.split("/");
const urlParts = url.split("/");
if (routeParts.length !== urlParts.length) continue;
const params = {};
let match = true;
for (let i = 0; i < routeParts.length; i++) {
if (routeParts[i].startsWith(":")) {
params[routeParts[i].slice(1)] = urlParts[i];
} else if (routeParts[i] !== urlParts[i]) {
match = false;
break;
}
}
if (match) {
return handler(req, res, params);
}
}
// 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 = mimeTypes[ext] || "application/octet-stream";
res.writeHead(200, { "Content-Type": type });
return res.end(fs.readFileSync(full));
}
notFound(res);
}).listen(PORT, () => console.log("Server running on port " + PORT));