import cors from "cors"; import express from "express"; import path from "path"; import { fileURLToPath } from "url"; import { connectToDatabase } from "./db.js"; import { config } from "./config.js"; import { MenuItem } from "./models/MenuItem.js"; import { Order } from "./models/Order.js"; import { computeOrderTotal, ensureSeedMenu, getSettings, nextOrderNumber, resetMenuToDefaults } from "./services.js"; const app = express(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const clientDist = path.resolve(__dirname, "../client/dist"); app.use(cors()); app.use(express.json()); app.get("/api/health", (_request, response) => { response.json({ ok: true }); }); app.get("/api/bootstrap", async (_request, response, next) => { try { await ensureSeedMenu(); const [menu, orders] = await Promise.all([ MenuItem.find().sort({ category: 1, name: 1 }), Order.find({ status: { $ne: "completed" } }).sort({ createdAt: -1 }) ]); response.json({ menu, orders, settings: getSettings() }); } catch (error) { next(error); } }); app.post("/api/menu", async (request, response, next) => { try { const { name, category, description = "", basePrice } = request.body; if (!name || !category || typeof basePrice !== "number" || Number.isNaN(basePrice)) { return response.status(400).json({ error: "Invalid menu item payload." }); } const menuItem = await MenuItem.create({ name, category, description, basePrice }); response.status(201).json({ menuItem }); } catch (error) { next(error); } }); app.delete("/api/menu/:menuItemId", async (request, response, next) => { try { await MenuItem.findByIdAndDelete(request.params.menuItemId); response.json({ ok: true }); } catch (error) { next(error); } }); app.post("/api/menu/reset", async (_request, response, next) => { try { const menu = await resetMenuToDefaults(); response.json({ menu }); } catch (error) { next(error); } }); app.post("/api/orders", async (request, response, next) => { try { const { customerName = "", paymentMethod = "cash", lines } = request.body; if (!Array.isArray(lines) || lines.length === 0) { return response.status(400).json({ error: "Orders require at least one line item." }); } const total = computeOrderTotal(lines); const order = await Order.create({ number: await nextOrderNumber(), customerName, paymentMethod, lines, total }); response.status(201).json({ order }); } catch (error) { next(error); } }); app.patch("/api/orders/:orderId/status", async (request, response, next) => { try { const { status } = request.body; const order = await Order.findByIdAndUpdate( request.params.orderId, { status }, { new: true, runValidators: true } ); response.json({ order }); } catch (error) { next(error); } }); app.delete("/api/orders/:orderId", async (request, response, next) => { try { await Order.findByIdAndDelete(request.params.orderId); response.json({ ok: true }); } catch (error) { next(error); } }); app.use(express.static(clientDist)); app.get("*", (request, response, next) => { if (request.path.startsWith("/api/")) { return next(); } response.sendFile(path.join(clientDist, "index.html")); }); app.use((error, _request, response, _next) => { console.error(error); response.status(500).json({ error: error.message || "Internal server error" }); }); connectToDatabase() .then(() => { app.listen(config.port, () => { console.log(`Ranaze POS server listening on port ${config.port}`); }); }) .catch((error) => { console.error("Failed to connect to MongoDB", error); process.exit(1); });