/* users-api.js User management API endpoints for Express with MariaDB pool. */ const express = require("express"); const axios = require("axios"); // Helper to parse JSON fields function parseJson(val) { try { const arr = JSON.parse(val); return Array.isArray(arr) ? arr : []; } catch { return []; } } // Export function to register routes module.exports = function (app, wss, pool) { // Helper to get balance async function getBalance(username) { const conn = await pool.getConnection(); try { const rows = await conn.query( "SELECT balance FROM users WHERE username = ?", [username], ); return rows[0]?.balance || 0; } finally { conn.release(); } } // --- USERS --- app.post("/api/users", express.json(), async (req, res) => { const { username, password, adresses } = req.body; const balance = 1000; if (typeof username !== "string" || typeof password !== "string") { return res.status(400).json({ ok: false, error: "Invalid input" }); } const adressesArr = Array.isArray(adresses) ? adresses.filter((a) => typeof a === "string") : []; const conn = await pool.getConnection(); try { await conn.query( "INSERT INTO users (username, password, balance, adresses) VALUES (?, ?, ?, ?)", [username, password, balance, JSON.stringify(adressesArr)], ); res.json({ ok: true }); } catch (err) { res.status(400).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.get("/api/users/:username", async (req, res) => { const conn = await pool.getConnection(); try { const rows = await conn.query("SELECT * FROM users WHERE username = ?", [ req.params.username, ]); const user = rows[0]; if (!user) return res.status(404).json({ ok: false, error: "User not found" }); user.adresses = parseJson(user.adresses); res.json({ ok: true, user }); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.put("/api/users/:username", express.json(), async (req, res) => { const { balance, adresses } = req.body; const updates = []; const 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); const conn = await pool.getConnection(); try { const result = await conn.query( `UPDATE users SET ${updates.join(", ")} WHERE username = ?`, params, ); if (result.affectedRows === 0) return res.status(404).json({ ok: false, error: "User not found" }); res.json({ ok: true }); } catch (err) { res.status(400).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.get("/api/users", async (req, res) => { const conn = await pool.getConnection(); try { const rows = await conn.query("SELECT * FROM users"); rows.forEach((row) => { row.adresses = parseJson(row.adresses); }); res.json({ ok: true, users: rows }); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } finally { conn.release(); } }); // --- COMPUTERS --- app.post("/api/computers", express.json(), async (req, res) => { const { linked_user } = req.body; if (typeof linked_user !== "string") return res.status(400).json({ ok: false, error: "Invalid input" }); const conn = await pool.getConnection(); try { const result = await conn.query( "INSERT INTO computers (linked_user) VALUES (?)", [linked_user], ); res.json({ ok: true, id: result.insertId }); } catch (err) { res.status(400).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.get("/api/computers", async (req, res) => { const user = req.query.user; const sql = user ? "SELECT * FROM computers WHERE linked_user = ?" : "SELECT * FROM computers"; const params = user ? [user] : []; const conn = await pool.getConnection(); try { const rows = await conn.query(sql, params); res.json({ ok: true, computers: rows }); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.put("/api/computers/:id", express.json(), async (req, res) => { const { linked_user } = req.body; if (typeof linked_user !== "string") return res.status(400).json({ ok: false, error: "Invalid input" }); const conn = await pool.getConnection(); try { const result = await conn.query( "UPDATE computers SET linked_user = ? WHERE id = ?", [linked_user, req.params.id], ); if (result.affectedRows === 0) return res.status(404).json({ ok: false, error: "Computer not found" }); res.json({ ok: true }); } catch (err) { res.status(400).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.get("/api/users/:username/computers", async (req, res) => { const conn = await pool.getConnection(); try { const rows = await conn.query( "SELECT * FROM computers WHERE linked_user = ?", [req.params.username], ); res.json({ ok: true, computers: rows }); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.get("/api/computers/:id/user", async (req, res) => { const conn = await pool.getConnection(); try { const rows = await conn.query( "SELECT linked_user FROM computers WHERE id = ?", [req.params.id], ); const user = rows[0]?.linked_user; if (!user) return res.status(404).json({ ok: false, error: "Computer not found" }); res.json({ ok: true, user }); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } finally { conn.release(); } }); // --- SHOP ITEMS --- app.get("/api/shop/items", async (req, res) => { const conn = await pool.getConnection(); try { const rows = await conn.query( "SELECT id, name, price, quantity FROM shop_items WHERE enabled = 1", ); res.json({ ok: true, items: rows }); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.get("/api/shop/items/all", async (req, res) => { const conn = await pool.getConnection(); try { const rows = await conn.query("SELECT * FROM shop_items"); res.json({ ok: true, items: rows }); } catch (err) { res.status(500).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.post("/api/shop/items", express.json(), async (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" }); } const conn = await pool.getConnection(); try { const result = await conn.query( "INSERT INTO shop_items (name, price, enabled, quantity) VALUES (?, ?, ?, ?)", [name, price, enabled ? 1 : 0, quantity], ); res.json({ ok: true, id: result.insertId }); } catch (err) { res.status(400).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.put("/api/shop/items/:id", express.json(), async (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); const conn = await pool.getConnection(); try { const result = await conn.query( `UPDATE shop_items SET ${updates.join(", ")} WHERE id = ?`, params, ); if (result.affectedRows === 0) return res.status(404).json({ ok: false, error: "Item not found" }); res.json({ ok: true }); } catch (err) { res.status(400).json({ ok: false, error: err.message }); } finally { conn.release(); } }); app.delete("/api/shop/items/:id", async (req, res) => { const conn = await pool.getConnection(); try { const result = await conn.query("DELETE FROM shop_items WHERE id = ?", [ req.params.id, ]); if (result.affectedRows === 0) return res.status(404).json({ ok: false, error: "Item not found" }); res.json({ ok: true }); } catch (err) { res.status(400).json({ ok: false, error: err.message }); } finally { conn.release(); } }); // --- SHOP BUY & USER TRANSFER --- 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" }); } const conn = await pool.getConnection(); try { await conn.beginTransaction(); let total = 0; const shopList = {}; 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 [priceRow] = await conn.query( "SELECT price FROM shop_items WHERE name = ?", [item.id], ); if (!priceRow) return res .status(404) .json({ ok: false, error: `Item not found ${item.id}` }); const price = priceRow.price; const [userRow] = await conn.query( "SELECT balance FROM users WHERE username = ?", [userId], ); if (!userRow) return res.status(404).json({ ok: false, error: "User not found" }); if (userRow.balance < price * (item.quantity / item.sellQuantity)) 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({ name: item.id, _requestCount: item.quantity }); total += price * item.quantity; } // Send via WebSocket Object.keys(shopList).forEach((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)); }); }); // Deduct total from user await conn.query( "UPDATE users SET balance = balance - ? WHERE username = ?", [total, userId], ); // Add total to 'ZareMate' await conn.query( "UPDATE users SET balance = balance + ? WHERE username = 'ZareMate'", [total], ); await conn.commit(); res.status(200).json({ ok: true, message: `Successfully purchased items for user ${userId} at ${total}`, }); } catch (err) { await conn.rollback(); console.error(err); res.status(500).json({ ok: false, error: "Internal server error" }); } finally { conn.release(); } }); app.post("/api/users/transfer", async (req, res) => { const { from, to, amount } = req.body; if (!from || !to || !amount || amount <= 0) return res.status(400).json({ ok: false, error: "Invalid input" }); const conn = await pool.getConnection(); try { await conn.beginTransaction(); const [fromRow] = await conn.query( "SELECT balance FROM users WHERE username = ?", [from], ); if (!fromRow) return res.status(404).json({ ok: false, error: "Sender not found" }); if (fromRow.balance < amount) return res .status(400) .json({ ok: false, error: "Insufficient balance" }); await conn.query( "UPDATE users SET balance = balance - ? WHERE username = ?", [amount, from], ); await conn.query( "UPDATE users SET balance = balance + ? WHERE username = ?", [amount, to], ); await conn.commit(); res.status(200).json({ ok: true, message: `Successfully transferred ${amount} from ${from} to ${to}`, }); } catch (err) { await conn.rollback(); console.error(err); res.status(500).json({ ok: false, error: "Internal server error" }); } finally { conn.release(); } }); };