From 0df875d6a6ff4a25a7ecb50e4d869255610427e5 Mon Sep 17 00:00:00 2001 From: Artus Date: Sun, 13 Oct 2019 16:02:47 +0200 Subject: [PATCH] moves db logic inside model's managers --- lootalot_db/src/lib.rs | 109 ++++++++---------------------- lootalot_db/src/models/claim.rs | 43 +++++++++++- lootalot_db/src/models/item.rs | 111 +++++++++++++++++++++++-------- lootalot_db/src/models/mod.rs | 7 +- lootalot_db/src/models/player.rs | 28 ++++++++ src/server.rs | 1 - 6 files changed, 183 insertions(+), 116 deletions(-) diff --git a/lootalot_db/src/lib.rs b/lootalot_db/src/lib.rs index da41e8d..4c0bba4 100644 --- a/lootalot_db/src/lib.rs +++ b/lootalot_db/src/lib.rs @@ -70,7 +70,7 @@ impl<'q> DbApi<'q> { /// /// TODO: remove limit used for debug pub fn fetch_inventory(self) -> QueryResult> { - Ok(schema::items::table.limit(100).load::(self.0)?) + models::item::Inventory(self.0).all() } /// Fetch all existing claims pub fn fetch_claims(self) -> QueryResult> { @@ -120,7 +120,7 @@ impl<'q> AsPlayer<'q> { /// assert_eq!(format!("{:?}", loot), "[]".to_string()); /// ``` pub fn loot(self) -> QueryResult> { - Ok(models::Item::owned_by(self.id).load(self.conn)?) + models::item::LootManager(self.conn, self.id).all() } /// Buy a batch of items and add them to this player chest /// @@ -137,23 +137,17 @@ impl<'q> AsPlayer<'q> { let mut added_items: Vec = Vec::with_capacity(params.len()); for (item_id, price_mod) in params.into_iter() { if let Ok((item, diff)) = self.conn.transaction(|| { - use schema::looted::dsl::*; - let item = schema::items::table.find(item_id).first::(self.conn)?; - let new_item = models::item::NewLoot::to_player(self.id, &item); - diesel::insert_into(schema::looted::table) - .values(&new_item) - .execute(self.conn)?; - let added_item = models::Item::owned_by(self.id) - .order(id.desc()) - .first(self.conn)?; + // Find item in inventory + let item = models::item::Inventory(self.conn).find(*item_id)?; + let new_item = models::item::LootManager(self.conn, self.id) + .add_from(&item)?; 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) + models::player::AsPlayer(self.conn, self.id) .update_wealth(-sell_price) - .map(|diff| (added_item, diff)) + .map(|diff| (new_item, diff.as_tuple())) }) { cumulated_diff.push(diff); added_items.push(item); @@ -175,25 +169,15 @@ impl<'q> AsPlayer<'q> { 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::*; - let loot = looted - .find(loot_id) - .first::(self.conn)?; - if loot.owner != self.id { - // If the item does not belong to player, - // it can't be what we're looking for - return Err(diesel::result::Error::NotFound); - } - let mut sell_value = loot.base_price as f32 / 2.0; + let deleted = models::item::LootManager(self.conn, self.id).remove(*loot_id)?; + let mut sell_value = deleted.base_price as f32 / 2.0; if let Some(modifier) = price_mod { sell_value *= modifier; } - let _deleted = diesel::delete(looted.find(loot_id)) - .execute(self.conn)?; - DbApi::with_conn(self.conn).as_player(self.id).update_wealth(sell_value) + models::player::AsPlayer(self.conn, self.id).update_wealth(sell_value) }); if let Ok(diff) = res { - all_results.push(diff) + all_results.push(diff.as_tuple()) } else { // TODO: need to find a better way to deal with errors return Err(diesel::result::Error::NotFound) @@ -209,54 +193,27 @@ impl<'q> AsPlayer<'q> { /// /// Value can be negative to substract wealth. pub fn update_wealth(self, value_in_gp: f32) -> ActionResult<(i32, i32, i32, i32)> { - use schema::players::dsl::*; - let current_wealth = players - .find(self.id) - .select((cp, sp, gp, pp)) - .first::(self.conn)?; - // TODO: improve thisdiesel dependant transaction - // should be move inside a WealthUpdate method - let updated_wealth = models::Wealth::from_gp(current_wealth.to_gp() + value_in_gp); - // Difference in coins that is sent back - let (old, new) = (current_wealth.as_tuple(), updated_wealth.as_tuple()); - let diff = (new.0 - old.0, new.1 - old.1, new.2 - old.2, new.3 - old.3); - diesel::update(players) - .filter(id.eq(self.id)) - .set(&updated_wealth) - .execute(self.conn) - .map(|r| match r { - 1 => diff, - _ => panic!("RuntimeError: UpdateWealth did no changes at all!"), - }) + models::player::AsPlayer(self.conn, self.id) + .update_wealth(value_in_gp) + .map(|w| w.as_tuple()) + } /// Put a claim on a specific item pub fn claim(self, item: i32) -> ActionResult<()> { - let exists: bool = diesel::select(models::Loot::exists(item)).get_result(self.conn)?; - if !exists { - return Err(diesel::result::Error::NotFound); - }; - let claim = models::claim::NewClaim::new(self.id, item); - diesel::insert_into(schema::claims::table) - .values(&claim) - .execute(self.conn) - .map(|rows_updated| match rows_updated { - 1 => (), - _ => panic!("RuntimeError: Claim did no change at all!"), + models::claim::Claims(self.conn) + .add(self.id, item) + .map(|claim| { + dbg!("created"); + dbg!(claim); }) } /// Withdraw claim pub fn unclaim(self, item: i32) -> ActionResult<()> { - use schema::claims::dsl::*; - diesel::delete( - claims - .filter(loot_id.eq(item)) - .filter(player_id.eq(self.id)), - ) - .execute(self.conn) - .and_then(|rows_updated| match rows_updated { - 1 => Ok(()), - 0 => Err(diesel::result::Error::NotFound), - _ => panic!("RuntimeError: UnclaimItem did not make expected changes"), + models::claim::Claims(self.conn) + .remove(self.id, item) + .map(|c| { + dbg!("deleted"); + dbg!(c); }) } } @@ -279,19 +236,9 @@ impl<'q> AsAdmin<'q> { } /// 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) -> ActionResult<()> { for item_desc in items.iter() { - let new_item = models::item::NewLoot::to_group(item_desc); - diesel::insert_into(schema::looted::table) - .values(&new_item) - .execute(self.0)?; + models::item::LootManager(self.0, 0).add_from(item_desc)?; } Ok(()) } @@ -301,7 +248,7 @@ impl<'q> AsAdmin<'q> { /// When a player gets an item, it's debt is increased by this item sell value pub fn resolve_claims(self) -> ActionResult<()> { // Fetch all claims, grouped by items. - let loot = models::Loot::owned_by(0).load(self.0)?; + let loot = models::item::Loot::owned_by(0).load(self.0)?; let claims = schema::claims::table .load::(self.0)? .grouped_by(&loot); diff --git a/lootalot_db/src/models/claim.rs b/lootalot_db/src/models/claim.rs index ad785e5..e0f21b9 100644 --- a/lootalot_db/src/models/claim.rs +++ b/lootalot_db/src/models/claim.rs @@ -1,4 +1,7 @@ -use crate::models::item::Loot; +use diesel::prelude::*; +use crate::{DbConnection, QueryResult}; + +use crate::models::{self, item::Loot}; use crate::schema::claims; /// A Claim is a request by a single player on an item from group chest. @@ -15,6 +18,44 @@ pub struct Claim { pub resolve: i32, } +pub struct Claims<'q>(pub &'q DbConnection); + +impl<'q> Claims<'q> { + + /// Finds a single claim by id. + pub fn find(&self, player_id: i32, loot_id: i32) -> QueryResult { + Ok( + claims::table + .filter(claims::dsl::player_id.eq(player_id)) + .filter(claims::dsl::loot_id.eq(loot_id)) + .first(self.0)? + ) + } + + /// Adds a claim in database and returns it + pub fn add(self, player_id: i32, loot_id: i32) -> QueryResult { + // We need to validate that the claimed item exists, and is actually owned by group (id 0) + let exists: bool = diesel::select(Loot::exists(loot_id)).get_result(self.0)?; + if !exists { + return Err(diesel::result::Error::NotFound); + }; + let claim = NewClaim::new(player_id, loot_id); + diesel::insert_into(claims::table) + .values(&claim) + .execute(self.0)?; + // Return the created claim + Ok(claims::table.order(claims::dsl::id.desc()).first::(self.0)?) + } + + /// Removes a claim from database, returning it + pub fn remove(self, req_player_id: i32, req_loot_id: i32) -> QueryResult { + let claim = self.find(req_player_id, req_loot_id)?; + diesel::delete(claims::table.find(claim.id)) + .execute(self.0)?; + Ok(claim) + } +} + #[derive(Insertable, Debug)] #[table_name = "claims"] pub(crate) struct NewClaim { diff --git a/lootalot_db/src/models/item.rs b/lootalot_db/src/models/item.rs index 5389d4e..0c9a891 100644 --- a/lootalot_db/src/models/item.rs +++ b/lootalot_db/src/models/item.rs @@ -1,8 +1,11 @@ -use crate::schema::looted; + + use diesel::dsl::{exists, Eq, Filter, Find, Select}; use diesel::expression::exists::Exists; use diesel::prelude::*; +use crate::{DbConnection, QueryResult}; +use crate::schema::{items, looted}; type ItemColumns = (looted::id, looted::name, looted::base_price); const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price); type OwnedBy = Select; @@ -21,67 +24,117 @@ pub struct Item { impl Item { /// Public proxy for Loot::owned_by that selects only Item fields - pub fn owned_by(player: i32) -> OwnedBy { + fn owned_by(player: i32) -> OwnedBy { Loot::owned_by(player).select(ITEM_COLUMNS) } } +pub struct Inventory<'q>(pub &'q DbConnection); + +impl<'q> Inventory<'q> { + + pub fn all(&self) -> QueryResult> { + items::table.load::(self.0) + } + + pub fn find(&self, item_id: i32) -> QueryResult { + items::table + .find(item_id) + .first::(self.0) + } +} + type WithOwner = Eq; type OwnedLoot = Filter; /// Represents an item that has been looted #[derive(Identifiable, Debug, Queryable, Serialize)] #[table_name = "looted"] -pub(crate) struct Loot { +pub(super) struct Loot { id: i32, name: String, - pub(crate) base_price: i32, - pub(crate) owner: i32, + base_price: i32, + owner: i32, } impl Loot { /// A filter on Loot that is owned by given player - pub(crate) fn owned_by(id: i32) -> OwnedLoot { + pub(super) fn owned_by(id: i32) -> OwnedLoot { looted::table.filter(looted::owner_id.eq(id)) } - pub(crate) fn owns(player: i32, item: i32) -> Exists> { + fn owns(player: i32, item: i32) -> Exists> { exists(Loot::owned_by(player).find(item)) } - pub(crate) fn exists(id: i32) -> Exists> { + pub(super) fn exists(id: i32) -> Exists> { exists(looted::table.find(id)) } } +/// Manager for a player's loot +pub struct LootManager<'q>(pub &'q DbConnection, pub i32); + +impl<'q> LootManager<'q> { + /// All items from this player chest + pub fn all(&self) -> QueryResult> { + Ok(Item::owned_by(self.1).load(self.0)?) + } + + /// Finds an item by id + pub fn find(&self, loot_id: i32) -> QueryResult { + Ok( + looted::table + .find(loot_id) + .first::(self.0) + .and_then(|loot| { + if loot.owner != self.1 { + Err(diesel::result::Error::NotFound) + } else { + Ok( Item { id: loot.id, name: loot.name, base_price: loot.base_price } ) + } + })? + ) + + } + + /// The last item added to the chest + pub fn last(&self) -> QueryResult { + Ok( + Item::owned_by(self.1) + .order(looted::dsl::id.desc()) + .first(self.0)? + ) + } + + /// Adds a copy of the given item inside player chest + pub fn add_from(self, item: &Item) -> QueryResult { + let new_item = NewLoot { + name: &item.name, + base_price: item.base_price, + owner_id: self.1, + }; + diesel::insert_into(looted::table) + .values(&new_item) + .execute(self.0)?; + self.last() + } + + pub fn remove(self, item_id: i32) -> QueryResult { + let deleted = self.find(item_id)?; + diesel::delete(looted::table.find(deleted.id)).execute(self.0)?; + Ok(deleted) + } +} + /// An item being looted or bought. /// /// The owner is set to 0 in case of looting, /// to the id of buying player otherwise. #[derive(Insertable)] #[table_name = "looted"] -pub(crate) struct NewLoot<'a> { +struct NewLoot<'a> { name: &'a str, base_price: i32, owner_id: i32, } - -impl<'a> NewLoot<'a> { - /// A new loot going to the group (loot procedure) - pub(crate) fn to_group(desc: &'a Item) -> Self { - Self { - name: &desc.name, - base_price: desc.base_price, - owner_id: 0, - } - } - - /// A new loot going to a specific player (buy procedure) - pub(crate) fn to_player(player: i32, desc: &'a Item) -> Self { - Self { - name: &desc.name, - base_price: desc.base_price, - owner_id: player, - } - } -} diff --git a/lootalot_db/src/models/mod.rs b/lootalot_db/src/models/mod.rs index 33147a4..286f11d 100644 --- a/lootalot_db/src/models/mod.rs +++ b/lootalot_db/src/models/mod.rs @@ -1,8 +1,7 @@ -pub(super) mod claim; -pub(super) mod item; -pub(super) mod player; +pub mod claim; +pub mod item; +pub mod player; pub use claim::Claim; pub use item::{Item}; -pub(crate) use item::Loot; pub use player::{Player, Wealth}; diff --git a/lootalot_db/src/models/player.rs b/lootalot_db/src/models/player.rs index 83f42d8..38efb9a 100644 --- a/lootalot_db/src/models/player.rs +++ b/lootalot_db/src/models/player.rs @@ -1,3 +1,5 @@ +use diesel::prelude::*; +use crate::{DbConnection, QueryResult}; use crate::schema::players; /// Representation of a player in database @@ -20,6 +22,32 @@ pub struct Player { pub pp: i32, } +pub struct AsPlayer<'q>(pub &'q DbConnection, pub i32); + +impl<'q> AsPlayer<'q> { + pub fn update_wealth(&self, value_in_gp: f32) -> QueryResult { + use crate::schema::players::dsl::*; + let current_wealth = players + .find(self.1) + .select((cp, sp, gp, pp)) + .first::(self.0)?; + let updated_wealth = Wealth::from_gp(current_wealth.to_gp() + value_in_gp); + // Difference in coins that is sent back + let difference = Wealth { + cp: updated_wealth.cp - current_wealth.cp, + sp: updated_wealth.sp - current_wealth.sp, + gp: updated_wealth.gp - current_wealth.gp, + pp: updated_wealth.pp - current_wealth.pp, + }; + diesel::update(players) + .filter(id.eq(self.1)) + .set(&updated_wealth) + .execute(self.0)?; + Ok(difference) + } +} + + /// Unpack a floating value of gold pieces to integer /// values of copper, silver, gold and platinum pieces /// diff --git a/src/server.rs b/src/server.rs index 2dde379..67ea8d3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -35,7 +35,6 @@ where J: serde::ser::Serialize + Send + 'static, Q: Fn(DbApi) -> QueryResult + Send + 'static, { - dbg!("db_call"); let conn = pool.get().unwrap(); web::block(move || { let api = DbApi::with_conn(&conn);