/* users-api.js User management API endpoints for Express with SQLite. Exports a function that takes an Express app instance and sets up: - POST /api/users Create a new user - GET /api/users/:username Get a user by username - PUT /api/users/:username Update a user's balance and linked computers - GET /api/users List all users User schema: - username (string, unique) - balance (float) - linked_computers (array of ints, stored as JSON string) */ const express = require("express"); const path = require("path"); const axios = require("axios"); // Use a persistent DB file in the project directory // Helper to parse linked_computers from DB function parseLinkedComputers(val) { try { const arr = JSON.parse(val); return Array.isArray(arr) ? arr : []; } catch { return []; } } // Export a function to register routes module.exports = function (app, wss, db) { // Helper to get balance from DB function getBalance(username) { const row = db.get("SELECT balance FROM users WHERE username = ?", [ username, ]); return row ? row.balance : 0; } // Create user app.post("/api/users", express.json(), (req, res) => { const { username, password, adresses } = req.body; const balance = 1000; let adressesArr = Array.isArray(adresses) ? adresses.filter((a) => typeof a === "string") : []; if (typeof username !== "string" || typeof password !== "string") { return res.status(400).json({ ok: false, error: "Invalid input" }); } db.run( "INSERT INTO users (username, password, balance, adresses) VALUES (?, ?, ?, ?)", [username, password, balance, JSON.stringify(adressesArr)], function (err) { if (err) return res.status(400).json({ ok: false, error: err.message }); res.json({ ok: true }); }, ); }); // Get user by username app.get("/api/users/:username", (req, res) => { db.get( "SELECT * FROM users WHERE username = ?", [req.params.username], (err, row) => { if (err || !row) return res.status(404).json({ ok: false, error: "User not found" }); if (row && typeof row.adresses === "string") { try { row.adresses = JSON.parse(row.adresses); } catch { row.adresses = []; } } res.json({ ok: true, user: row }); }, ); }); // Update user balance app.put("/api/users/:username", express.json(), (req, res) => { const { balance, adresses } = req.body; let updates = []; let params = []; if (typeof balance === "number") { updates.push("balance = ?"); params.push(balance); } if (Array.isArray(adresses)) { updates.push("adresses = ?"); params.push( JSON.stringify(adresses.filter((a) => typeof a === "string")), ); } if (updates.length === 0) { return res .status(400) .json({ ok: false, error: "No valid fields to update" }); } params.push(req.params.username); db.run( `UPDATE users SET ${updates.join(", ")} WHERE username = ?`, params, function (err) { if (err) return res.status(400).json({ ok: false, error: err.message }); if (this.changes === 0) return res.status(404).json({ ok: false, error: "User not found" }); res.json({ ok: true }); }, ); }); // List all users app.get("/api/users", (req, res) => { db.all("SELECT * FROM users", [], (err, rows) => { if (err) return res.status(500).json({ ok: false, error: err.message }); rows.forEach((row) => { if (row && typeof row.adresses === "string") { try { row.adresses = JSON.parse(row.adresses); } catch { row.adresses = []; } } }); res.json({ ok: true, users: rows }); }); }); // Create a new computer and link to user app.post("/api/computers", express.json(), (req, res) => { const { linked_user } = req.body; if (typeof linked_user !== "string") { return res.status(400).json({ ok: false, error: "Invalid input" }); } db.run( "INSERT INTO computers (linked_user) VALUES (?)", [linked_user], function (err) { if (err) return res.status(400).json({ ok: false, error: err.message }); res.json({ ok: true, id: this.lastID }); }, ); }); // List all computers, or by user app.get("/api/computers", (req, res) => { const user = req.query.user; const sql = user ? "SELECT * FROM computers WHERE linked_user = ?" : "SELECT * FROM computers"; const params = user ? [user] : []; db.all(sql, params, (err, rows) => { if (err) return res.status(500).json({ ok: false, error: err.message }); res.json({ ok: true, computers: rows }); }); }); // Update a computer's linked user app.put("/api/computers/:id", express.json(), (req, res) => { const { linked_user } = req.body; if (typeof linked_user !== "string") { return res.status(400).json({ ok: false, error: "Invalid input" }); } db.run( "UPDATE computers SET linked_user = ? WHERE id = ?", [linked_user, req.params.id], function (err) { if (err) return res.status(400).json({ ok: false, error: err.message }); if (this.changes === 0) return res .status(404) .json({ ok: false, error: "Computer not found" }); res.json({ ok: true }); }, ); }); // Get all computers linked to a user app.get("/api/users/:username/computers", (req, res) => { db.all( "SELECT * FROM computers WHERE linked_user = ?", [req.params.username], (err, rows) => { if (err) return res.status(500).json({ ok: false, error: err.message }); res.json({ ok: true, computers: rows }); }, ); }); // Get the user linked to a computer app.get("/api/computers/:id/user", (req, res) => { db.get( "SELECT linked_user FROM computers WHERE id = ?", [req.params.id], (err, row) => { if (err || !row) return res .status(404) .json({ ok: false, error: "Computer not found" }); res.json({ ok: true, user: row.linked_user }); }, ); }); // --- SHOP ITEMS API --- // Get all enabled items (for homepage) app.get("/api/shop/items", (req, res) => { db.all( "SELECT id, name, price, quantity FROM shop_items WHERE enabled = 1", [], (err, rows) => { if (err) return res.status(500).json({ ok: false, error: err.message }); res.json({ ok: true, items: rows }); }, ); }); // Get all items (admin) app.get("/api/shop/items/all", (req, res) => { db.all("SELECT * FROM shop_items", [], (err, rows) => { if (err) return res.status(500).json({ ok: false, error: err.message }); res.json({ ok: true, items: rows }); }); }); // Add a new item (admin) app.post("/api/shop/items", express.json(), (req, res) => { const { name, price, enabled, quantity } = req.body; if ( typeof name !== "string" || typeof price !== "number" || typeof quantity !== "number" ) { return res.status(400).json({ ok: false, error: "Invalid input" }); } db.run( "INSERT INTO shop_items (name, price, enabled, quantity) VALUES (?, ?, ?, ?)", [name, price, enabled === 0 ? 0 : 1, quantity], function (err) { if (err) return res.status(400).json({ ok: false, error: err.message }); res.json({ ok: true, id: this.lastID }); }, ); }); // Update an item (admin) app.put("/api/shop/items/:id", express.json(), (req, res) => { const { name, price, enabled, quantity } = req.body; const updates = []; const params = []; if (typeof name === "string") { updates.push("name = ?"); params.push(name); } if (typeof price === "number") { updates.push("price = ?"); params.push(price); } if (typeof enabled === "number") { updates.push("enabled = ?"); params.push(enabled ? 1 : 0); } if (typeof quantity === "number") { updates.push("quantity = ?"); params.push(quantity); } if (updates.length === 0) { return res .status(400) .json({ ok: false, error: "No valid fields to update" }); } params.push(req.params.id); db.run( `UPDATE shop_items SET ${updates.join(", ")} WHERE id = ?`, params, function (err) { if (err) return res.status(400).json({ ok: false, error: err.message }); if (this.changes === 0) return res.status(404).json({ ok: false, error: "Item not found" }); res.json({ ok: true }); }, ); }); // Delete an item (admin) app.delete("/api/shop/items/:id", (req, res) => { db.run( "DELETE FROM shop_items WHERE id = ?", [req.params.id], function (err) { if (err) return res.status(400).json({ ok: false, error: err.message }); if (this.changes === 0) return res.status(404).json({ ok: false, error: "Item not found" }); res.json({ ok: true }); }, ); }); app.post("/api/shop/items/buy", async (req, res) => { const { userId, addressId, items } = req.body; if (!userId || !addressId || !Array.isArray(items)) { return res.status(400).json({ ok: false, error: "Missing or invalid parameters", }); } function getPrice(db, itemId) { return new Promise((resolve, reject) => { db.get( "SELECT price FROM shop_items WHERE name = ?", [itemId], (err, row) => { if (err) return reject({ status: 400, message: err.message }); if (!row) return reject({ status: 404, message: "Item not found" }); resolve(row.price); }, ); }); } function getBalance(db, username) { return new Promise((resolve, reject) => { db.get( "SELECT balance FROM users WHERE username = ?", [username], (err, row) => { if (err) return reject({ status: 400, message: err.message }); if (!row) return reject({ status: 404, message: "User not found" }); resolve(row.balance); }, ); }); } try { var shopList = {}; var total = 0; for (const item of items) { if (!item.id || !item.quantity) { return res.status(400).json({ ok: false, error: "Invalid item format", }); } const response = await axios.get( `http://localhost:3100/api/item/${item.id}`, ); const stockCount = response.data.item.count; if (stockCount < item.quantity) { return res.status(400).json({ ok: false, error: `Not enough stock for item ${item.id}`, }); } const price = await getPrice(db, item.id); const balance = await getBalance(db, userId); if (balance < price * item.quantity) { return res.status(400).json({ ok: false, error: `Insufficient balance for item ${item.id}`, }); } const shopId = response.data.shopId; if (!shopList[shopId]) { shopList[shopId] = []; } shopList[shopId].push({ id: item.id, count: item.quantity, }); total += price * item.quantity; } console.log("Shoplist"); console.log(shopList); Object.keys(shopList).forEach((shop) => { console.log("Shop"); console.log(shop); const request = { type: "request", address: addressId, to: parseInt(shop), items: shopList[shop], }; wss.clients.forEach((client) => { if (client.readyState === 1) { client.send(JSON.stringify(request)); } }); }); // remove total from db db.run( `UPDATE users SET balance = balance - ? WHERE username = ?`, [total, userId], (err) => { if (err) { console.error(err); return res.status(500).json({ ok: false, error: "Internal server error", }); } }, ); // add total to 'ZareMate' db.run( `UPDATE users SET balance = balance + ? WHERE username = 'ZareMate'`, [total], (err) => { if (err) { console.error(err); return res.status(500).json({ ok: false, error: "Internal server error", }); } }, ); res.status(200).json({ ok: true, message: `Successfully purchased items for user ${userId} at ${total}`, }); } catch (err) { console.error(err); return res.status(500).json({ ok: false, error: "Internal server error", }); } }); app.post("/api/users/transfer", (req, res) => { const { from, to, amount } = req.body; if (!from || !to || !amount) { return res.status(400).json({ ok: false, error: "Missing required fields", }); } if (amount <= 0) { return res.status(400).json({ ok: false, error: "Invalid amount", }); } const balance = getBalance(from); if (amount > balance) { return res.status(400).json({ ok: false, error: "Insufficient balance", }); } db.run( `UPDATE users SET balance = balance - ? WHERE username = ?`, [amount, from], (err) => { if (err) { console.error(err); return res.status(500).json({ ok: false, error: "Internal server error", }); } }, ); db.run( `UPDATE users SET balance = balance + ? WHERE username = ?`, [amount, to], (err) => { if (err) { console.error(err); return res.status(500).json({ ok: false, error: "Internal server error", }); } }, ); res.status(200).json({ ok: true, message: `Successfully transferred ${amount} from ${from} to ${to}`, }); }); };