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