adds buy/sell endpoints

This commit is contained in:
2019-08-07 15:34:08 +02:00
parent 6e7a0f6211
commit cb98b97126
5 changed files with 149 additions and 47 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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 = [];
} }
} }

View File

@@ -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 = [];

View File

@@ -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")