Create app module
This commit is contained in:
parent
0161108e87
commit
9c344bc8e5
56
src/app.js
Normal file
56
src/app.js
Normal file
@ -0,0 +1,56 @@
|
||||
const express = require('express');
|
||||
const cookieParser = require("cookie-parser");
|
||||
const sessions = require('express-session');
|
||||
const Request = require('./request');
|
||||
|
||||
module.exports = function App(secret, DB) {
|
||||
|
||||
const app = express();
|
||||
//session middleware
|
||||
app.use(sessions({
|
||||
secret: secret,
|
||||
saveUninitialized: true,
|
||||
resave: false,
|
||||
name: "session"
|
||||
}));
|
||||
// parsing the incoming data
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
//serving public file
|
||||
app.use(express.static(PUBLIC_DIR));
|
||||
// cookie parser middleware
|
||||
app.use(cookieParser());
|
||||
|
||||
//Initital page loading
|
||||
app.get('/', (req, res) => res.sendFile('home.html', {
|
||||
root: PUBLIC_DIR
|
||||
}));
|
||||
|
||||
//signup request
|
||||
app.post('/signup', Request.SignUp);
|
||||
|
||||
//login request
|
||||
app.post('/login', Request.Login);
|
||||
|
||||
//logout request
|
||||
app.get('/logout', Request.Logout);
|
||||
|
||||
//place sell or buy order
|
||||
app.post('/buy', Request.PlaceBuyOrder);
|
||||
app.post('/sell', Request.PlaceSellOrder);
|
||||
|
||||
//list sell or buy order
|
||||
app.get('/list-sellorders', Request.ListSellOrders);
|
||||
app.get('/list-buyorders', Request.ListBuyOrders);
|
||||
|
||||
//list all process transactions
|
||||
app.get('/list-transactions', Request.ListTransactions);
|
||||
|
||||
//get account details
|
||||
app.get('/account', Request.Account);
|
||||
|
||||
Request.DB = DB;
|
||||
return app;
|
||||
}
|
||||
247
src/market.js
Normal file
247
src/market.js
Normal file
@ -0,0 +1,247 @@
|
||||
var net_FLO_price; //container for FLO price (from API or by model)
|
||||
var DB; //container for database
|
||||
|
||||
function addSellOrder(floID, quantity, min_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT SUM(quantity) as total FROM Vault WHERE floID=?", [floID]).then(result => {
|
||||
let total = result.pop()["total"];
|
||||
if (total < quantity)
|
||||
return reject(INVALID("Insufficient FLO"));
|
||||
DB.query("SELECT SUM(quantity) as locked FROM SellOrder WHERE floID=?", [floID]).then(result => {
|
||||
let locked = result.pop()["locked"] || 0;
|
||||
let available = total - locked;
|
||||
console.debug(total, locked, available);
|
||||
if (available < quantity)
|
||||
return reject(INVALID("Insufficient FLO (Some FLO are locked in another sell order)"));
|
||||
DB.query("INSERT INTO SellOrder(floID, quantity, minPrice) VALUES (?, ?, ?)", [floID, quantity, min_price])
|
||||
.then(result => resolve("Added SellOrder to DB"))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
/*
|
||||
function addSellOrder(floID, quantity, min_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT id, base, (quantity - locked) as available FROM Vault WHERE floID=? ORDER BY base", [floID]).then(result => {
|
||||
console.debug(result);
|
||||
let rem = quantity,
|
||||
sell_base = 0,
|
||||
txQueries = [];
|
||||
for (let i = 0; i < result.length && rem > 0; i++) {
|
||||
var lock = (rem < result[i].available ? rem : result[i].available);
|
||||
rem -= lock;
|
||||
sell_base += (lock * result[i].base);
|
||||
txQueries.push(["UPDATE Vault SET locked=locked-? WHERE id=?", [lock, result[i].id]]);
|
||||
}
|
||||
if (rem > 0)
|
||||
return reject(INVALID("Insufficient FLO"));
|
||||
sell_base = sell_base / quantity;
|
||||
Promise.all(txQueries.map(a => DB.query(a[0], a[1]))).then(results => {
|
||||
DB.query("INSERT INTO SellOrder(floID, quantity, minPrice, sellBase) VALUES (?, ?, ?)", [floID, quantity, min_price, sell_base])
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
function addBuyOrder(floID, quantity, max_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT rupeeBalance FROM Users WHERE floID=?", [floID]).then(result => {
|
||||
let total = result.pop()["rupeeBalance"];
|
||||
if (total < quantity * max_price)
|
||||
return reject(INVALID("Insufficient Rupee balance"));
|
||||
DB.query("SELECT SUM(maxPrice * quantity) as locked FROM BuyOrder WHERE floID=?", [floID]).then(result => {
|
||||
let locked = result.pop()["locked"] || 0;
|
||||
let available = total - locked;
|
||||
console.debug(total, locked, available);
|
||||
if (available < quantity * max_price)
|
||||
return reject(INVALID("Insufficient Rupee balance (Some rupee tokens are locked in another buy order)"));
|
||||
DB.query("INSERT INTO BuyOrder(floID, quantity, maxPrice) VALUES (?, ?, ?)", [floID, quantity, max_price])
|
||||
.then(result => resolve("Added BuyOrder to DB"))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
function matchBuyAndSell() {
|
||||
let cur_price = net_FLO_price;
|
||||
//get the best buyer
|
||||
getBestBuyer(cur_price).then(buyer_best => {
|
||||
//get the best seller
|
||||
getBestSeller(buyer_best.quantity, cur_price).then(result => {
|
||||
let seller_best = result.sellOrder,
|
||||
txQueries = result.txQueries;
|
||||
//process the Txn
|
||||
var tx_quantity;
|
||||
if (seller_best.quantity > buyer_best.quantity)
|
||||
tx_quantity = processBuyOrder(seller_best, buyer_best, txQueries);
|
||||
else if (seller_best.quantity < buyer_best.quantity)
|
||||
tx_quantity = processSellOrder(seller_best, buyer_best, txQueries);
|
||||
else
|
||||
tx_quantity = processBuyAndSellOrder(seller_best, buyer_best, txQueries);
|
||||
updateBalance(seller_best, buyer_best, txQueries, cur_price, tx_quantity);
|
||||
//process txn query in SQL
|
||||
DB.TxQuery(txQueries).then(results => {
|
||||
console.log(`Transaction was successful! BuyOrder:${buyer_best.id}| SellOrder:${seller_best.id}`);
|
||||
//Since a tx was successful, match again
|
||||
matchBuyAndSell();
|
||||
}).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function getBestBuyer(cur_price, n = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT * FROM BuyOrder WHERE maxPrice >= ? ORDER BY time_placed LIMIT=?,1", [cur_price, n]).then(result => {
|
||||
let buyOrder = result.shift();
|
||||
if (!buyOrder)
|
||||
return reject("No valid buyers available");
|
||||
DB.query("SELECT rupeeBalance as bal FROM Users WHERE floID=?", [buyOrder.floID]).then(result => {
|
||||
if (result[0].bal < cur_price * buyOrder.quantity) {
|
||||
//This should not happen unless a buy order is placed when user doesnt have enough rupee balance
|
||||
console.warn(`Buy order ${buyOrder.id} is active, but rupee# is insufficient`);
|
||||
getBestBuyer(cur_price, n + 1)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
} else
|
||||
resolve(buyOrder);
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
function getBestSeller(maxQuantity, cur_price, n = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//TODO: Add order conditions for priority.
|
||||
DB.query("SELECT * FROM SellOrder WHERE minPrice <=? ORDER BY time_placed LIMIT=?,1", [cur_price, n]).then(result => {
|
||||
let sellOrder = result.shift();
|
||||
if (!sellOrder)
|
||||
return reject("No valid sellers available");
|
||||
DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY base", [sellOrder.floID]).then(result => {
|
||||
let rem = Math.min(sellOrder.quantity, maxQuantity),
|
||||
sell_base = 0,
|
||||
base_quantity = 0,
|
||||
txQueries = [];
|
||||
for (let i = 0; i < result.length && rem > 0; i++) {
|
||||
if (rem < result[i].quantity) {
|
||||
txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, result[i].id]]);
|
||||
if (result[i].base) {
|
||||
sell_base += (rem * result[i].base);
|
||||
base_quantity += rem
|
||||
}
|
||||
rem = 0;
|
||||
} else {
|
||||
txQueries.push(["DELETE FROM Vault WHERE id=?", [result[i].id]]);
|
||||
if (result[i].base) {
|
||||
sell_base += (result[i].quantity * result[i].base);
|
||||
base_quantity += result[i].quantity
|
||||
}
|
||||
rem -= result[i].quantity;
|
||||
}
|
||||
}
|
||||
if (base_quantity)
|
||||
sell_base = sell_base / base_quantity;
|
||||
if (rem > 0 || sell_base > cur_price) {
|
||||
//1st condition (rem>0) should not happen (sell order placement was success when insufficient FLO).
|
||||
if (rem > 0)
|
||||
console.warn(`Sell order ${sellOrder.id} is active, but FLO is insufficient`);
|
||||
getBestSeller(maxQuantity, cur_price, n + 1)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
} else
|
||||
resolve({
|
||||
sellOrder,
|
||||
txQueries
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
function processBuyOrder(seller_best, buyer_best, txQueries) {
|
||||
let quantity = buyer_best.quantity;
|
||||
//Buy order is completed, sell order is partially done.
|
||||
txQueries.push(["DELETE FROM BuyOrder WHERE id=?", [buyer_best.id]]);
|
||||
txQueries.push(["UPDATE SellOrder SET quantity=quantity-? WHERE id=?", [quantity, seller_best.id]]);
|
||||
return quantity;
|
||||
}
|
||||
|
||||
function processSellOrder(seller_best, buyer_best, txQueries) {
|
||||
let quantity = buyer_best.quantity;
|
||||
//Sell order is completed, buy order is partially done.
|
||||
txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]);
|
||||
txQueries.push(["UPDATE BuyOrder SET quantity=quantity-? WHERE id=?", [quantity, buyer_best.id]]);
|
||||
return quantity;
|
||||
}
|
||||
|
||||
function processBuyAndSellOrder(seller_best, buyer_best, txQueries) {
|
||||
//Both sell order and buy order is completed
|
||||
txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]);
|
||||
txQueries.push(["DELETE FROM BuyOrder WHERE id=?", [buyer_best.id]]);
|
||||
return seller_best.quantity;
|
||||
}
|
||||
|
||||
function updateBalance(seller_best, buyer_best, txQueries, cur_price, quantity) {
|
||||
//Update rupee balance for seller and buyer
|
||||
let totalAmount = cur_price * quantity;
|
||||
txQueries.push(["UPDATE Users SET rupeeBalance=rupeeBalance+? WHERE floID=?", [totalAmount, seller_best.floID]]);
|
||||
txQueries.push(["UPDATE Users SET rupeeBalance=rupeeBalance-? WHERE floID=?", [totalAmount, buyer_best.floID]]);
|
||||
//Add coins to Buyer
|
||||
txQueries.push(["INSERT INTO Vault(floID, base, quantity) VALUES (?, ?, ?)", [buyer_best.floID, cur_price, quantity]])
|
||||
//Record transaction
|
||||
txQueries.push(["INSERT INTO Transactions (seller, buyer, quantity, unitValue) VALUES (?, ?, ?)", [seller_best.floID, buyer_best.floID, quantity, cur_price]]);
|
||||
return;
|
||||
}
|
||||
|
||||
function getAccountDetails(floID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let select = [];
|
||||
select.push(["rupeeBalance", "Users"]);
|
||||
select.push(["base, quantity", "Vault"]);
|
||||
select.push(["id, quantity, minPrice, time_placed", "SellOrder"]);
|
||||
select.push(["id, quantity, maxPrice, time_placed", "BuyOrder"]);
|
||||
let promises = select.map(a => DB.query("SELECT " + a[0] + " FROM " + a[1] + " WHERE floID=?", [floID]));
|
||||
Promise.allSettled(promises).then(results => {
|
||||
let response = {
|
||||
floID: floID,
|
||||
time: Date.now()
|
||||
};
|
||||
results.forEach((a, i) => {
|
||||
if (a.status === "rejected")
|
||||
console.error(a.reason);
|
||||
else
|
||||
switch (i) {
|
||||
case 0:
|
||||
response.rupee_total = a.value[0].rupeeBalance;
|
||||
break;
|
||||
case 1:
|
||||
response.coins = a.value;
|
||||
break;
|
||||
case 2:
|
||||
response.sellOrders = a.value;
|
||||
break;
|
||||
case 3:
|
||||
response.buyOrders = a.value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
DB.query("SELECT * FROM Transactions WHERE seller=? OR buyer=?", [floID, floID])
|
||||
.then(result => response.transactions = result)
|
||||
.catch(error => console.error(error))
|
||||
.finally(_ => resolve(response));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addBuyOrder,
|
||||
addSellOrder,
|
||||
getAccountDetails,
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
}
|
||||
};
|
||||
166
src/request.js
Normal file
166
src/request.js
Normal file
@ -0,0 +1,166 @@
|
||||
const market = require("./market");
|
||||
var DB; //container for database
|
||||
|
||||
global.INVALID = function(message) {
|
||||
if (!(this instanceof INVALID))
|
||||
return new INVALID(message);
|
||||
this.message = message;
|
||||
}
|
||||
INVALID.e_code = 400;
|
||||
|
||||
global.INTERNAL = function INTERNAL(message) {
|
||||
if (!(this instanceof INTERNAL))
|
||||
return new INTERNAL(message);
|
||||
this.message = message;
|
||||
}
|
||||
INTERNAL.e_code = 500;
|
||||
|
||||
// creating 24 hours from milliseconds
|
||||
const oneDay = 1000 * 60 * 60 * 24;
|
||||
const maxSessionTimeout = 60 * oneDay;
|
||||
|
||||
function SignUp(req, res) {
|
||||
let data = req.body,
|
||||
session = req.session;
|
||||
console.debug(session.random, data);
|
||||
if (floCrypto.getFloID(data.pubKey) !== data.floID)
|
||||
res.status(INVALID.e_code).send("Invalid Public Key");
|
||||
if (!session.random)
|
||||
res.status(INVALID.e_code).send("Invalid Session");
|
||||
else if (!floCrypto.verifySign(session.random, data.sign, data.pubKey))
|
||||
res.status(INVALID.e_code).send("Invalid Signature");
|
||||
else {
|
||||
DB.query("INSERT INTO Users(floID, pubKey, session_time) VALUES (?, ?, NULL)", [data.floID, data.pubKey])
|
||||
.then(_ => res.send("Account Created")).catch(error => {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send("Account creation failed! Try Again Later!");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function Login(req, res) {
|
||||
let data = req.body,
|
||||
session = req.session;
|
||||
if (floCrypto.getFloID(data.pubKey) !== data.floID)
|
||||
res.status(INVALID.e_code).send("Invalid Public Key");
|
||||
if (!session.random)
|
||||
res.status(INVALID.e_code).send("Invalid Session");
|
||||
else if (!floCrypto.verifySign(session.random, data.sign, data.pubKey))
|
||||
res.status(INVALID.e_code).send("Invalid Signature");
|
||||
else {
|
||||
if (data.saveSession) {
|
||||
DB.query("UPDATE Users SET session_id=?, session_time=DEFAULT WHERE floID=?", [req.sessionID, data.floID])
|
||||
.then(_ => session.cookie.maxAge = maxSessionTimeout)
|
||||
.catch(e => console.error(e)).finally(_ => {
|
||||
session.user_id = data.floID;
|
||||
res.send("Login Successful");
|
||||
});
|
||||
} else {
|
||||
session.user_id = data.floID;
|
||||
res.send("Login Successful");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Logout(req, res) {
|
||||
let session = req.session;
|
||||
DB.query("UPDATE Users SET session_id=NULL, session_time=NULL WHERE floID=?", [session.user_id])
|
||||
.then(_ => null).catch(e => console.log(e)).finally(_ => {
|
||||
session.destroy();
|
||||
res.send('Logout successful')
|
||||
});
|
||||
}
|
||||
|
||||
function PlaceSellOrder(req, res) {
|
||||
let data = req.body,
|
||||
session = req.session;
|
||||
market.addSellOrder(session.user_id, data.quantity, data.min_price)
|
||||
.then(result => res.send('Sell Order placed successfully'))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.message);
|
||||
else
|
||||
res.status(INTERNAL.e_code).send("Order placement failed! Try again later!");
|
||||
});
|
||||
}
|
||||
|
||||
function PlaceBuyOrder(req, res) {
|
||||
let data = req.body,
|
||||
session = req.session;
|
||||
market.addBuyOrder(session.user_id, data.quantity, data.max_price)
|
||||
.then(result => res.send('Buy Order placed successfully'))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.message);
|
||||
else
|
||||
res.status(INTERNAL.e_code).send("Order placement failed! Try again later!");
|
||||
});
|
||||
}
|
||||
|
||||
function ListSellOrders(req, res) {
|
||||
//TODO: Limit size (best)
|
||||
DB.query("SELECT * FROM SellOrder ORDER BY time_placed")
|
||||
.then(result => res.send(result))
|
||||
.catch(error => res.status(INTERNAL.e_code).send("Try again later!"));
|
||||
}
|
||||
|
||||
function ListBuyOrders(req, res) {
|
||||
//TODO: Limit size (best)
|
||||
DB.query("SELECT * FROM BuyOrder ORDER BY time_placed")
|
||||
.then(result => res.send(result))
|
||||
.catch(error => res.status(INTERNAL.e_code).send("Try again later!"));
|
||||
}
|
||||
|
||||
function ListTransactions(req, res) {
|
||||
//TODO: Limit size (recent)
|
||||
DB.query("SELECT * FROM Transactions ORDER BY tx_time DESC")
|
||||
.then(result => res.send(result))
|
||||
.catch(error => res.status(INTERNAL.e_code).send("Try again later!"));
|
||||
}
|
||||
|
||||
function Account(req, res) {
|
||||
const setLogin = function(message) {
|
||||
let randID = floCrypto.randString(16, true);
|
||||
req.session.random = randID;
|
||||
res.status(INVALID.e_code).send({
|
||||
message,
|
||||
sid: randID
|
||||
});
|
||||
}
|
||||
if (!req.session.user_id)
|
||||
setLogin("Login required");
|
||||
else {
|
||||
DB.query("SELECT session_id, session_time FROM Users WHERE floID=?", [req.session.user_id]).then(result => {
|
||||
let {
|
||||
session_id,
|
||||
session_time
|
||||
} = result.pop();
|
||||
if (!session_id || session_id != req.sessionID || session_time + maxSessionTimeout < Date.now())
|
||||
setLogin("Session Expired! Re-login required");
|
||||
else {
|
||||
let floID = req.session.user_id;
|
||||
res.cookie('floID', floID);
|
||||
market.getAccountDetails(floID)
|
||||
.then(result => res.send(result));
|
||||
}
|
||||
}).catch(_ => res.status(INTERNAL.e_code).send("Try again later!"));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SignUp,
|
||||
Login,
|
||||
Logout,
|
||||
PlaceBuyOrder,
|
||||
PlaceSellOrder,
|
||||
ListSellOrders,
|
||||
ListBuyOrders,
|
||||
ListTransactions,
|
||||
Account,
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
market.DB = db;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user