492 lines
14 KiB
JavaScript
492 lines
14 KiB
JavaScript
/*
|
|
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}`,
|
|
});
|
|
});
|
|
};
|