adds buy/sell endpoints
This commit is contained in:
@@ -121,14 +121,22 @@ impl<'q> AsPlayer<'q> {
|
|||||||
pub fn loot(self) -> QueryResult<Vec<models::Item>> {
|
pub fn loot(self) -> QueryResult<Vec<models::Item>> {
|
||||||
Ok(models::Item::owned_by(self.id).load(self.conn)?)
|
Ok(models::Item::owned_by(self.id).load(self.conn)?)
|
||||||
}
|
}
|
||||||
/// Buy an item and add it to this player chest
|
/// Buy a batch of items and add them to this player chest
|
||||||
/// TODO: Items should be picked from a custom list
|
///
|
||||||
|
/// Items can only be bought from inventory. Hence, the use
|
||||||
|
/// of the entity's id in 'items' table.
|
||||||
|
///
|
||||||
|
/// # Params
|
||||||
|
/// List of (Item's id in inventory, Option<Price modifier>)
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// Result containing the difference in coins after operation
|
/// Result containing the difference in coins after operation
|
||||||
pub fn buy<'a>(self, name: &'a str, price: i32) -> ActionResult<(i32, i32, i32, i32)> {
|
pub fn buy<'a>(self, params: &Vec<(i32, Option<f32>)>) -> ActionResult<(i32, i32, i32, i32)> {
|
||||||
self.conn.transaction(|| {
|
let mut all_results: Vec<(i32, i32, i32, i32)> = Vec::with_capacity(params.len());
|
||||||
let new_item = models::item::NewLoot::to_player(self.id, (name, price));
|
for (item_id, price_mod) in params.into_iter() {
|
||||||
|
let res = self.conn.transaction(|| {
|
||||||
|
let item = schema::items::table.find(item_id).first::<models::Item>(self.conn)?;
|
||||||
|
let new_item = models::item::NewLoot::to_player(self.id, (&item.name, item.base_price));
|
||||||
let _item_added = diesel::insert_into(schema::looted::table)
|
let _item_added = diesel::insert_into(schema::looted::table)
|
||||||
.values(&new_item)
|
.values(&new_item)
|
||||||
.execute(self.conn)
|
.execute(self.conn)
|
||||||
@@ -136,19 +144,29 @@ impl<'q> AsPlayer<'q> {
|
|||||||
1 => (),
|
1 => (),
|
||||||
_ => panic!("RuntimeError: Buy made no changes at all"),
|
_ => panic!("RuntimeError: Buy made no changes at all"),
|
||||||
})?;
|
})?;
|
||||||
self.update_wealth(-(price as f32))
|
let sell_price = match price_mod {
|
||||||
})
|
Some(modifier) => item.base_price as f32 * modifier,
|
||||||
|
None => item.base_price as f32
|
||||||
|
};
|
||||||
|
DbApi::with_conn(self.conn).as_player(self.id).update_wealth(-sell_price)
|
||||||
|
});
|
||||||
|
if let Ok(diff) = res { all_results.push(diff); }
|
||||||
}
|
}
|
||||||
/// Sell an item from this player chest
|
Ok(all_results.into_iter().fold((0,0,0,0), |sum, diff| {
|
||||||
|
(sum.0 + diff.0, sum.1 + diff.1, sum.2 + diff.2, sum.3 + diff.3)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/// Sell a set of items from this player chest
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// Result containing the difference in coins after operation
|
/// Result containing the difference in coins after operation
|
||||||
pub fn sell(
|
pub fn sell(
|
||||||
self,
|
self,
|
||||||
loot_id: i32,
|
params: &Vec<(i32, Option<f32>)>,
|
||||||
price_mod: Option<f32>,
|
|
||||||
) -> ActionResult<(i32, i32, i32, i32)> {
|
) -> ActionResult<(i32, i32, i32, i32)> {
|
||||||
self.conn.transaction(|| {
|
let mut all_results: Vec<(i32, i32, i32, i32)> = Vec::with_capacity(params.len());
|
||||||
|
for (loot_id, price_mod) in params.into_iter() {
|
||||||
|
let res = self.conn.transaction(|| {
|
||||||
use schema::looted::dsl::*;
|
use schema::looted::dsl::*;
|
||||||
let loot = looted
|
let loot = looted
|
||||||
.find(loot_id)
|
.find(loot_id)
|
||||||
@@ -158,14 +176,24 @@ impl<'q> AsPlayer<'q> {
|
|||||||
// it can't be what we're looking for
|
// it can't be what we're looking for
|
||||||
return Err(diesel::result::Error::NotFound);
|
return Err(diesel::result::Error::NotFound);
|
||||||
}
|
}
|
||||||
let mut sell_value = (loot.base_price / 2) as f32;
|
let mut sell_value = loot.base_price as f32 / 2.0;
|
||||||
if let Some(modifier) = price_mod {
|
if let Some(modifier) = price_mod {
|
||||||
sell_value *= modifier;
|
sell_value *= modifier;
|
||||||
}
|
}
|
||||||
let _deleted = diesel::delete(looted.find(loot_id))
|
let _deleted = diesel::delete(looted.find(loot_id))
|
||||||
.execute(self.conn)?;
|
.execute(self.conn)?;
|
||||||
self.update_wealth(sell_value)
|
DbApi::with_conn(self.conn).as_player(self.id).update_wealth(sell_value)
|
||||||
})
|
});
|
||||||
|
if let Ok(diff) = res {
|
||||||
|
all_results.push(diff)
|
||||||
|
} else {
|
||||||
|
// TODO: need to find a better way to deal with errors
|
||||||
|
return Err(diesel::result::Error::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(all_results.into_iter().fold((0,0,0,0), |sum, diff| {
|
||||||
|
(sum.0 + diff.0, sum.1 + diff.1, sum.2 + diff.2, sum.3 + diff.3)
|
||||||
|
}))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +271,13 @@ impl<'q> AsAdmin<'q> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a list of items to the group loot
|
/// Adds a list of items to the group loot
|
||||||
|
///
|
||||||
|
/// This offers complete control other created items, so that unique
|
||||||
|
/// items can be easily added. A user interface shall deal with
|
||||||
|
/// filling theses values for known items in inventory.
|
||||||
|
///
|
||||||
|
/// # Params
|
||||||
|
/// List of (name, base_price) values for the new items
|
||||||
pub fn add_loot(self, items: Vec<(&str, i32)>) -> ActionResult<()> {
|
pub fn add_loot(self, items: Vec<(&str, i32)>) -> ActionResult<()> {
|
||||||
for item_desc in items.into_iter() {
|
for item_desc in items.into_iter() {
|
||||||
let new_item = models::item::NewLoot::to_group(item_desc);
|
let new_item = models::item::NewLoot::to_group(item_desc);
|
||||||
@@ -457,6 +492,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn as_player_simple_buy_sell() {
|
fn as_player_simple_buy_sell() {
|
||||||
let conn = test_connection();
|
let conn = test_connection();
|
||||||
|
// Adds a sword into inventory
|
||||||
|
{
|
||||||
|
use schema::items::dsl::*;
|
||||||
|
diesel::insert_into(items)
|
||||||
|
.values((name.eq("Sword"), base_price.eq(800)))
|
||||||
|
.execute(&conn)
|
||||||
|
.expect("Could not set up items table");
|
||||||
|
}
|
||||||
DbApi::with_conn(&conn)
|
DbApi::with_conn(&conn)
|
||||||
.as_admin()
|
.as_admin()
|
||||||
.add_player("Player", 1000.0)
|
.add_player("Player", 1000.0)
|
||||||
@@ -464,7 +507,7 @@ mod tests {
|
|||||||
// Buy an item
|
// Buy an item
|
||||||
let bought = DbApi::with_conn(&conn)
|
let bought = DbApi::with_conn(&conn)
|
||||||
.as_player(1)
|
.as_player(1)
|
||||||
.buy("Sword", 800);
|
.buy(&vec![(1, None)]);
|
||||||
assert_eq!(bought.ok(), Some((0, 0, 0, -8))); // Returns diff of player wealth ?
|
assert_eq!(bought.ok(), Some((0, 0, 0, -8))); // Returns diff of player wealth ?
|
||||||
let chest = DbApi::with_conn(&conn).as_player(1).loot().unwrap();
|
let chest = DbApi::with_conn(&conn).as_player(1).loot().unwrap();
|
||||||
assert_eq!(chest.len(), 1);
|
assert_eq!(chest.len(), 1);
|
||||||
@@ -475,10 +518,10 @@ mod tests {
|
|||||||
let player = players.get(1).unwrap();
|
let player = players.get(1).unwrap();
|
||||||
assert_eq!(player.pp, 2);
|
assert_eq!(player.pp, 2);
|
||||||
// A player cannot sell loot from an other's chest
|
// A player cannot sell loot from an other's chest
|
||||||
let result = DbApi::with_conn(&conn).as_player(0).sell(loot.id, None);
|
let result = DbApi::with_conn(&conn).as_player(0).sell(&vec![(loot.id, None)]);
|
||||||
assert_eq!(result.is_ok(), false);
|
assert_eq!(result.is_ok(), false);
|
||||||
// Sell back
|
// Sell back
|
||||||
let sold = DbApi::with_conn(&conn).as_player(1).sell(loot.id, None);
|
let sold = DbApi::with_conn(&conn).as_player(1).sell(&vec![(loot.id, None)]);
|
||||||
assert_eq!(sold.ok(), Some((0, 0, 0, 4)));
|
assert_eq!(sold.ok(), Some((0, 0, 0, 4)));
|
||||||
let chest = DbApi::with_conn(&conn).as_player(1).loot().unwrap();
|
let chest = DbApi::with_conn(&conn).as_player(1).loot().unwrap();
|
||||||
assert_eq!(chest.len(), 0);
|
assert_eq!(chest.len(), 0);
|
||||||
|
|||||||
@@ -44,7 +44,16 @@ export const Api = {
|
|||||||
updateWealth (player_id, value_in_gp) {
|
updateWealth (player_id, value_in_gp) {
|
||||||
const payload = { player_id, value_in_gp: Number(value_in_gp) };
|
const payload = { player_id, value_in_gp: Number(value_in_gp) };
|
||||||
return this.__doFetch("players/update-wealth", 'PUT', payload);
|
return this.__doFetch("players/update-wealth", 'PUT', payload);
|
||||||
}
|
},
|
||||||
|
buyItems (player_id, items) {
|
||||||
|
const payload = { player_id, items };
|
||||||
|
return this.__doFetch("players/buy", 'POST', payload);
|
||||||
|
},
|
||||||
|
sellItems (player_id, items) {
|
||||||
|
const payload = { player_id, items };
|
||||||
|
return this.__doFetch("players/sell", 'POST', payload);
|
||||||
|
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -117,15 +126,15 @@ export const AppStorage = {
|
|||||||
},
|
},
|
||||||
updatePlayerWealth (goldValue) {
|
updatePlayerWealth (goldValue) {
|
||||||
return Api.updateWealth(this.state.player_id, goldValue)
|
return Api.updateWealth(this.state.player_id, goldValue)
|
||||||
.then(response => {
|
.then(diff => this.__updatePlayerWealth(diff));
|
||||||
// Update player wealth
|
},
|
||||||
var diff = response;
|
// TODO: Weird private name denotes a conflict
|
||||||
|
__updatePlayerWealth (diff) {
|
||||||
if (this.debug) console.log('updatePlayerWealth', diff)
|
if (this.debug) console.log('updatePlayerWealth', diff)
|
||||||
this.state.player_list[this.state.player_id].cp += diff[0];
|
this.state.player_list[this.state.player_id].cp += diff[0];
|
||||||
this.state.player_list[this.state.player_id].sp += diff[1];
|
this.state.player_list[this.state.player_id].sp += diff[1];
|
||||||
this.state.player_list[this.state.player_id].gp += diff[2];
|
this.state.player_list[this.state.player_id].gp += diff[2];
|
||||||
this.state.player_list[this.state.player_id].pp += diff[3];
|
this.state.player_list[this.state.player_id].pp += diff[3];
|
||||||
});
|
|
||||||
},
|
},
|
||||||
// Put a claim on an item from group chest.
|
// Put a claim on an item from group chest.
|
||||||
putRequest (itemId) {
|
putRequest (itemId) {
|
||||||
@@ -136,6 +145,22 @@ export const AppStorage = {
|
|||||||
this.state.player_claims[playerId].push(itemId);
|
this.state.player_claims[playerId].push(itemId);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
buyItems (items) {
|
||||||
|
return Api.buyItems(this.state.player_id, items)
|
||||||
|
.then(diff => this.__updatePlayerWealth(diff))
|
||||||
|
.then(() => {
|
||||||
|
// Add items to the player loot
|
||||||
|
console.log(items);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sellItems (items) {
|
||||||
|
return Api.sellItems(this.state.player_id, items)
|
||||||
|
.then(diff => this.__updatePlayerWealth(diff))
|
||||||
|
.then(() => {
|
||||||
|
// Remove items from player chest
|
||||||
|
console.log(items);
|
||||||
|
});
|
||||||
|
},
|
||||||
// Withdraws a claim.
|
// Withdraws a claim.
|
||||||
cancelRequest(itemId) {
|
cancelRequest(itemId) {
|
||||||
const playerId = this.state.player_id
|
const playerId = this.state.player_id
|
||||||
|
|||||||
@@ -89,8 +89,12 @@
|
|||||||
methods: {
|
methods: {
|
||||||
buySelectedItems () {
|
buySelectedItems () {
|
||||||
const items = this.items.filter(i => this.selected_items.includes(i.id));
|
const items = this.items.filter(i => this.selected_items.includes(i.id));
|
||||||
this.$emit("buy", items);
|
var payload = [];
|
||||||
this.selected_items.length = 0;
|
items.forEach(item => {
|
||||||
|
payload.push([item.id, null]);
|
||||||
|
});
|
||||||
|
this.$emit("buy", payload);
|
||||||
|
this.selected_items = [];
|
||||||
},
|
},
|
||||||
sellSelectedItems () {
|
sellSelectedItems () {
|
||||||
if (!this.is_selling) {
|
if (!this.is_selling) {
|
||||||
@@ -99,7 +103,11 @@
|
|||||||
this.is_selling = false;
|
this.is_selling = false;
|
||||||
if (this.selected_items.length > 0) {
|
if (this.selected_items.length > 0) {
|
||||||
const items = this.items.filter(i => this.selected_items.includes(i.id));
|
const items = this.items.filter(i => this.selected_items.includes(i.id));
|
||||||
this.$emit("sell", items);
|
var payload = [];
|
||||||
|
items.forEach(item => {
|
||||||
|
payload.push([item.id, null]);
|
||||||
|
});
|
||||||
|
this.$emit("sell", payload);
|
||||||
this.selected_items = [];
|
this.selected_items = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
buyItems(items) {
|
buyItems(items) {
|
||||||
this.notifications.push(`Would buy ${items.length} items`);
|
AppStorage.buyItems(items)
|
||||||
|
.then(_ => this.notifications.push(`Bought ${items.length} items`))
|
||||||
},
|
},
|
||||||
sellItems (items) {
|
sellItems (items) {
|
||||||
this.notifications.push(`Would sell ${items.length} items`);
|
AppStorage.sellItems(items)
|
||||||
|
.then(_ => this.notifications.push(`Sold ${items.length} items`))
|
||||||
},
|
},
|
||||||
parseLoot (items) {
|
parseLoot (items) {
|
||||||
this.loot = [];
|
this.loot = [];
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ struct NewPlayer {
|
|||||||
wealth: f32,
|
wealth: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
struct LootUpdate {
|
||||||
|
player_id: i32,
|
||||||
|
items: Vec<(i32, Option<f32>)>,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn serve() -> std::io::Result<()> {
|
pub(crate) fn serve() -> std::io::Result<()> {
|
||||||
let www_root: String = env::var("WWW_ROOT").expect("WWW_ROOT must be set");
|
let www_root: String = env::var("WWW_ROOT").expect("WWW_ROOT must be set");
|
||||||
dbg!(&www_root);
|
dbg!(&www_root);
|
||||||
@@ -109,6 +115,24 @@ pub(crate) fn serve() -> std::io::Result<()> {
|
|||||||
.update_wealth(data.value_in_gp))
|
.update_wealth(data.value_in_gp))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/buy",
|
||||||
|
web::post().to_async(move |pool: AppPool, data: web::Json<LootUpdate>| {
|
||||||
|
db_call(pool, move |api| api
|
||||||
|
.as_player(data.player_id)
|
||||||
|
.buy(&data.items)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/sell",
|
||||||
|
web::post().to_async(move |pool: AppPool, data: web::Json<LootUpdate>| {
|
||||||
|
db_call(pool, move |api| api
|
||||||
|
.as_player(data.player_id)
|
||||||
|
.sell(&data.items)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/claims")
|
web::resource("/claims")
|
||||||
|
|||||||
Reference in New Issue
Block a user