186 lines
5.7 KiB
JavaScript
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));
|