From ac492b1301c564ba002bd104ada2e22c52da18dc Mon Sep 17 00:00:00 2001 From: ZareMate <0.zaremate@gmail.com> Date: Sun, 28 Dec 2025 14:20:04 +0100 Subject: [PATCH] move from SQLite to MariaDB --- .gitignore | 2 +- db.js | 11 + package-lock.json | 65 +++++ package.json | 1 + public/js/index.js | 2 +- server.js | 35 +-- users-api.js | 692 ++++++++++++++++++++++----------------------- 7 files changed, 421 insertions(+), 387 deletions(-) create mode 100644 db.js diff --git a/.gitignore b/.gitignore index 2d1a82fe..8574daca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /node_modules/ -/users.db cron_update.log +.env diff --git a/db.js b/db.js new file mode 100644 index 00000000..02a6f023 --- /dev/null +++ b/db.js @@ -0,0 +1,11 @@ +const mariadb = require("mariadb"); + +const pool = mariadb.createPool({ + host: "10.0.0.1", // "mariadb" if in Docker + user: "minecraft_shop_user", + password: "UsersBeliev3sNewsp4p3r", + database: "minecraft-shop", + connectionLimit: 5, +}); + +module.exports = pool; diff --git a/package-lock.json b/package-lock.json index e151f682..d7bc4fb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "axios": "^1.13.2", "express": "^5.2.1", + "mariadb": "^3.4.5", "sqlight": "^1.0.0-alpha.8", "sqlite3": "^5.1.7", "ws": "^8.18.3" @@ -98,6 +99,21 @@ "node": ">= 6" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/shortid": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", @@ -554,6 +570,15 @@ "license": "MIT", "optional": true }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1318,6 +1343,40 @@ "node": ">= 0.6" } }, + "node_modules/mariadb": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", + "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.16", + "@types/node": "^24.0.13", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.4.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mariadb/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mariadb/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2368,6 +2427,12 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", diff --git a/package.json b/package.json index 5a8b36a2..0c367c20 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dependencies": { "axios": "^1.13.2", "express": "^5.2.1", + "mariadb": "^3.4.5", "sqlight": "^1.0.0-alpha.8", "sqlite3": "^5.1.7", "ws": "^8.18.3" diff --git a/public/js/index.js b/public/js/index.js index b8b2b602..522f9ac1 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -699,7 +699,7 @@ function getCartItemCount(itemId) { //check if item can be added to cart (if item_stock > cart_item_count) async function canAddToCart(itemId) { - const item_stock = await getItemStock(itemId); + const item_stock = (await getItemStock(itemId)) || 0; const cart_item_count = getCartItemCount(itemId); return item_stock > cart_item_count; } diff --git a/server.js b/server.js index ec115267..81d5c230 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,7 @@ const express = require("express"); const http = require("http"); const WebSocket = require("ws"); const path = require("path"); -const sqlite3 = require("sqlite3").verbose(); +const pool = require("./db"); const app = express(); const PORT = process.env.PORT || 3100; @@ -44,38 +44,7 @@ function normalizeItems(arr) { const server = http.createServer(app); const wss = new WebSocket.Server({ server }); -const db = new sqlite3.Database(path.join(__dirname, "users.db")); - -// Create table if it doesn't exist -db.serialize(() => { - db.run(` - CREATE TABLE IF NOT EXISTS users ( - username TEXT PRIMARY KEY, - password TEXT NOT NULL, - balance REAL NOT NULL, - adresses TEXT NOT NULL - ) - `); - - db.run(` - CREATE TABLE IF NOT EXISTS computers ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - linked_user TEXT, - FOREIGN KEY(linked_user) REFERENCES users(username) - ) - `); - - db.run(` - CREATE TABLE IF NOT EXISTS shop_items ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT UNIQUE NOT NULL, - price REAL NOT NULL, - enabled INTEGER NOT NULL DEFAULT 1 - ) - `); -}); - -require("./users-api")(app, wss, db); +require("./users-api")(app, wss, pool); // Helper to send JSON to a ws client function sendJSON(ws, obj) { diff --git a/users-api.js b/users-api.js index 633e4932..c75ad235 100644 --- a/users-api.js +++ b/users-api.js @@ -1,27 +1,13 @@ /* 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) + User management API endpoints for Express with MariaDB pool. */ 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) { +// Helper to parse JSON fields +function parseJson(val) { try { const arr = JSON.parse(val); return Array.isArray(arr) ? arr : []; @@ -30,60 +16,72 @@ function parseLinkedComputers(val) { } } -// 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; +// 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(); + } } - // Create user - app.post("/api/users", express.json(), (req, res) => { + + // --- USERS --- + + app.post("/api/users", express.json(), async (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 }); - }, - ); + 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(); + } }); - // 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 }); - }, - ); + 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(); + } }); - // Update user balance - app.put("/api/users/:username", express.json(), (req, res) => { + app.put("/api/users/:username", express.json(), async (req, res) => { const { balance, adresses } = req.body; - let updates = []; - let params = []; + const updates = []; + const params = []; if (typeof balance === "number") { updates.push("balance = ?"); params.push(balance); @@ -94,140 +92,165 @@ module.exports = function (app, wss, db) { JSON.stringify(adresses.filter((a) => typeof a === "string")), ); } - if (updates.length === 0) { + 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 }); - }, - ); + 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(); + } }); - // 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 }); + app.get("/api/users", async (req, res) => { + const conn = await pool.getConnection(); + try { + const rows = await conn.query("SELECT * FROM users"); rows.forEach((row) => { - if (row && typeof row.adresses === "string") { - try { - row.adresses = JSON.parse(row.adresses); - } catch { - row.adresses = []; - } - } + row.adresses = parseJson(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" }); + } catch (err) { + res.status(500).json({ ok: false, error: err.message }); + } finally { + conn.release(); } - 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) => { + // --- 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] : []; - db.all(sql, params, (err, rows) => { - if (err) return res.status(500).json({ ok: false, error: err.message }); + + const conn = await pool.getConnection(); + try { + const rows = await conn.query(sql, params); 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" }); + } catch (err) { + res.status(500).json({ ok: false, error: err.message }); + } finally { + conn.release(); } - 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 }); - }, - ); + 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(); + } }); - // 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 }); - }, - ); + 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(); + } }); - // 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 }); + 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(); + } }); - // Add a new item (admin) - app.post("/api/shop/items", express.json(), (req, res) => { + 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" || @@ -236,18 +259,21 @@ module.exports = function (app, wss, db) { ) { 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 }); - }, - ); + 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(); + } }); - // Update an item (admin) - app.put("/api/shop/items/:id", express.json(), (req, res) => { + app.put("/api/shop/items/:id", express.json(), async (req, res) => { const { name, price, enabled, quantity } = req.body; const updates = []; const params = []; @@ -267,123 +293,108 @@ module.exports = function (app, wss, db) { updates.push("quantity = ?"); params.push(quantity); } - if (updates.length === 0) { + 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 }); - }, - ); + + 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(); + } }); - // 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.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", - }); - } - - 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); - }, - ); - }); + return res + .status(400) + .json({ ok: false, error: "Missing or invalid parameters" }); } + const conn = await pool.getConnection(); try { - var shopList = {}; - var total = 0; + 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", - }); - } + 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) + return res + .status(400) + .json({ + ok: false, + error: `Insufficient balance for item ${item.id}`, + }); - 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, - }); + if (!shopList[shopId]) shopList[shopId] = []; + shopList[shopId].push({ id: item.id, count: item.quantity }); total += price * item.quantity; } - console.log("Shoplist"); - console.log(shopList); + + // Send via WebSocket Object.keys(shopList).forEach((shop) => { - console.log("Shop"); - console.log(shop); const request = { type: "request", address: addressId, @@ -391,101 +402,78 @@ module.exports = function (app, wss, db) { items: shopList[shop], }; wss.clients.forEach((client) => { - if (client.readyState === 1) { - client.send(JSON.stringify(request)); - } + if (client.readyState === 1) client.send(JSON.stringify(request)); }); }); - // remove total from db - db.run( - `UPDATE users SET balance = balance - ? WHERE username = ?`, + + // Deduct total from user + await conn.query( + "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'`, + // Add total to 'ZareMate' + await conn.query( + "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}`, - }); + + 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); - return res.status(500).json({ - ok: false, - error: "Internal server error", - }); + res.status(500).json({ ok: false, error: "Internal server error" }); + } finally { + conn.release(); } }); - app.post("/api/users/transfer", (req, res) => { + + app.post("/api/users/transfer", async (req, res) => { const { from, to, amount } = req.body; - if (!from || !to || !amount) { - return res.status(400).json({ - ok: false, - error: "Missing required fields", - }); + 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(); } - 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}`, - }); }); };