const express = require("express"); const cors = require("cors"); const helmet = require("helmet"); const fs = require("fs").promises; const path = require("path"); require("dotenv").config(); const app = express(); const PORT = process.env.PORT || 3001; app.use(helmet()); app.use( cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") || [ "http://localhost:3000", ], credentials: true, }), ); app.use(express.json()); const authenticateAdmin = (req, res, next) => { const adminPassword = req.headers.authorization?.replace("Bearer ", ""); if (adminPassword !== process.env.ADMIN_PASSWORD) { return res.status(401).json({ error: "Unauthorized" }); } next(); }; const DISCOUNT_CODES_PATH = path.join(__dirname, "data", "discountCodes.json"); const PRODUCTS_PATH = path.join(__dirname, "data", "products.js"); let cache = { discountCodes: null, products: null, lastUpdated: { discountCodes: null, products: null, }, }; const clearCache = () => { cache.discountCodes = null; cache.products = null; cache.lastUpdated.discountCodes = null; cache.lastUpdated.products = null; }; const readDiscountCodes = async () => { try { const data = await fs.readFile(DISCOUNT_CODES_PATH, "utf8"); return JSON.parse(data); } catch (error) { console.error("Error reading discount codes:", error); return {}; } }; const writeDiscountCodes = async (codes) => { try { await fs.writeFile( DISCOUNT_CODES_PATH, JSON.stringify(codes, null, 2), "utf8", ); return true; } catch (error) { console.error("Error writing discount codes:", error); return false; } }; const readProducts = async () => { try { const data = await fs.readFile(PRODUCTS_PATH, "utf8"); const match = data.match( /export const products: Product\[\] = (\[[\s\S]*?\]);/, ); if (match) { const productsString = match[1] .replace(/(\w+):/g, '"$1":') .replace(/'/g, '"') .replace(/,\s*}/g, "}") .replace(/,\s*]/g, "]"); return JSON.parse(productsString); } return []; } catch (error) { console.error("Error reading products:", error); return []; } }; const writeProducts = async (products) => { try { const jsContent = `import type { Product } from "../types/product"; export const products: Product[] = ${JSON.stringify(products, null, 2) .replace(/"([^"]+)":/g, "$1:") .replace(/"/g, '"')}; `; await fs.writeFile(PRODUCTS_PATH, jsContent, "utf8"); return true; } catch (error) { console.error("Error writing products:", error); return false; } }; app.get("/health", (req, res) => { res.json({ status: "ok", timestamp: new Date().toISOString() }); }); app.get("/api/discount-codes", async (req, res) => { try { if (!cache.discountCodes) { console.log("Loading discount codes from file..."); cache.discountCodes = await readDiscountCodes(); cache.lastUpdated.discountCodes = new Date(); } res.json(cache.discountCodes); } catch (error) { console.error("Error getting discount codes:", error); res.status(500).json({ error: "Failed to get discount codes" }); } }); app.post("/api/discount-codes", authenticateAdmin, async (req, res) => { try { const { code, percentage, expiration, description } = req.body; if (!code || !percentage) { return res .status(400) .json({ error: "Code and percentage are required" }); } const discountCodes = await readDiscountCodes(); const newCode = { percentage: parseInt(percentage), description: description || "", }; if (expiration) { newCode.expiration = expiration; } discountCodes[code] = newCode; const success = await writeDiscountCodes(discountCodes); if (!success) { return res.status(500).json({ error: "Failed to save discount code" }); } clearCache(); res.json({ success: true, code: newCode }); } catch (error) { console.error("Error adding discount code:", error); res.status(500).json({ error: "Failed to add discount code" }); } }); app.delete("/api/discount-codes/:code", authenticateAdmin, async (req, res) => { try { const { code } = req.params; const discountCodes = await readDiscountCodes(); if (!discountCodes[code]) { return res.status(404).json({ error: "Discount code not found" }); } delete discountCodes[code]; const success = await writeDiscountCodes(discountCodes); if (!success) { return res.status(500).json({ error: "Failed to delete discount code" }); } clearCache(); res.json({ success: true }); } catch (error) { console.error("Error deleting discount code:", error); res.status(500).json({ error: "Failed to delete discount code" }); } }); app.get("/api/products", async (req, res) => { try { if (!cache.products) { console.log("Loading products from file..."); cache.products = await readProducts(); cache.lastUpdated.products = new Date(); } res.json(cache.products); } catch (error) { console.error("Error getting products:", error); res.status(500).json({ error: "Failed to get products" }); } }); app.put("/api/products/:productId", authenticateAdmin, async (req, res) => { try { const { productId } = req.params; const { pricePerStack } = req.body; if (pricePerStack === undefined) { return res.status(400).json({ error: "pricePerStack is required" }); } const products = await readProducts(); const productIndex = products.findIndex((p) => p.id === productId); if (productIndex === -1) { return res.status(404).json({ error: "Product not found" }); } products[productIndex].pricePerStack = parseFloat(pricePerStack); const success = await writeProducts(products); if (!success) { return res.status(500).json({ error: "Failed to update product" }); } clearCache(); res.json({ success: true, product: products[productIndex] }); } catch (error) { console.error("Error updating product:", error); res.status(500).json({ error: "Failed to update product" }); } }); app.get("/api/cache-status", (req, res) => { res.json({ cache: { discountCodes: { cached: !!cache.discountCodes, lastUpdated: cache.lastUpdated.discountCodes, }, products: { cached: !!cache.products, lastUpdated: cache.lastUpdated.products, }, }, }); }); app.post("/api/clear-cache", authenticateAdmin, (req, res) => { clearCache(); res.json({ success: true, message: "Cache cleared" }); }); app.use((error, req, res, next) => { console.error("Unhandled error:", error); res.status(500).json({ error: "Internal server error" }); }); app.use((req, res) => { res.status(404).json({ error: "Not found" }); }); const initializeDataDirectory = async () => { const dataDir = path.join(__dirname, "data"); try { await fs.access(dataDir); } catch { await fs.mkdir(dataDir, { recursive: true }); try { await fs.access(DISCOUNT_CODES_PATH); } catch { await fs.writeFile(DISCOUNT_CODES_PATH, "{}", "utf8"); } try { await fs.access(PRODUCTS_PATH); } catch { const initialProducts = `import type { Product } from "../types/product"; export const products: Product[] = [ { id: "spruce-log", name: "Spruce Log", pricePerStack: 1, }, { id: "birch-log", name: "Birch Log", pricePerStack: 1, }, { id: "oak-log", name: "Oak Log", pricePerStack: 1, }, { id: "jungle-log", name: "Jungle Log", pricePerStack: 1, }, { id: "cobblestone", name: "Cobblestone", pricePerStack: 1 / 16, defaultStacksPerDia: 16, variants: ["Cobblestone", "Stone", "Stone Bricks"], }, { id: "sand", name: "Sand", pricePerStack: 1, }, { id: "gunpowder", name: "Gunpowder", pricePerStack: 1 / 4, defaultStacksPerDia: 4, }, ]; `; await fs.writeFile(PRODUCTS_PATH, initialProducts, "utf8"); } } }; const startServer = async () => { try { await initializeDataDirectory(); app.listen(PORT, () => { console.log(`🚀 Data server running on port ${PORT}`); console.log(`📂 Discount codes: ${DISCOUNT_CODES_PATH}`); console.log(`📂 Products: ${PRODUCTS_PATH}`); console.log( `🔐 Admin password: ${process.env.ADMIN_PASSWORD ? "Set" : "Not set"}`, ); }); } catch (error) { console.error("Failed to start server:", error); process.exit(1); } }; startServer();