Create-CC-Shop/users-api.js

474 lines
14 KiB
JavaScript

/*
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)
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();
}
});
};