From 4f60df88d7572f78e3426a81774bb5df73edd4b4 Mon Sep 17 00:00:00 2001 From: Artus Date: Sat, 20 Jul 2019 15:49:51 +0200 Subject: [PATCH] moves player actions code inside UserAction trait impls --- lootalot_db/src/actions.rs | 241 +++++++++++++++++++++++++++++++++++++ lootalot_db/src/lib.rs | 141 ++++++---------------- 2 files changed, 279 insertions(+), 103 deletions(-) create mode 100644 lootalot_db/src/actions.rs diff --git a/lootalot_db/src/actions.rs b/lootalot_db/src/actions.rs new file mode 100644 index 0000000..cdf3218 --- /dev/null +++ b/lootalot_db/src/actions.rs @@ -0,0 +1,241 @@ +//! TODO: +//! Extract actions provided by API into their dedicated module. +//! Will allow more flexibilty to combinate them inside API methods. +//! Should make it easier to add a new feature : Reverting an action +//! +use crate::models; +use crate::schema; +use crate::DbConnection; +use diesel::prelude::*; +// TODO: revertable actions : +// - Buy +// - Sell +// - UpdateWealth + +pub trait UserAction { + type Params; + type Response: serde::Serialize; + + fn execute<'q>( + self, + conn: &'q DbConnection, + params: Self::Params, + ) -> ActionResult; +} + +/// The result of an action provided by DbApi +pub type ActionResult = QueryResult>; +/// Return status of an Action +#[derive(Serialize, Debug)] +pub struct ActionStatus { + /// Has the action made changes ? + pub executed: bool, + /// Response payload + pub response: R, +} + +impl ActionStatus<()> { + pub fn was_updated(updated_lines: usize) -> Self { + match updated_lines { + 1 => Self::ok(), + _ => Self::nop(), + } + } + pub fn ok() -> ActionStatus<()> { + Self { + executed: true, + response: (), + } + } +} + +impl ActionStatus { + pub fn nop() -> ActionStatus { + Self { + executed: false, + response: Default::default(), + } + } +} +// Or a module ? +pub(crate) mod player { + use super::*; + + pub struct AddLootParams { + pub player_id: i32, + pub loot_name: String, + pub loot_price: i32, + } + pub struct Buy; + + impl UserAction for Buy { + type Params = AddLootParams; + type Response = Option<(i32, i32, i32, i32)>; + + fn execute<'q>( + self, + conn: &'q DbConnection, + params: Self::Params, + ) -> ActionResult { + let new_item = models::item::NewLoot::to_player( + params.player_id, + (¶ms.loot_name, params.loot_price), + ); + diesel::insert_into(schema::looted::table) + .values(&new_item) + .execute(conn) + .and_then(|r| match r { + 1 => Ok(UpdateWealth + .execute( + conn, + WealthParams { + player_id: params.player_id, + value_in_gp: -(params.loot_price as f32), + }, + ) + .unwrap()), + _ => Ok(ActionStatus::nop()), + }) + } + } + + pub struct LootParams { + pub player_id: i32, + pub loot_id: i32, + } + pub struct Sell; + impl UserAction for Sell { + type Params = LootParams; + type Response = Option<(i32, i32, i32, i32)>; + fn execute<'q>( + self, + conn: &DbConnection, + params: Self::Params, + ) -> ActionResult { + // Check that the item belongs to player + let exists_and_owned: bool = + diesel::select(models::Loot::owns(params.player_id, params.loot_id)) + .get_result(conn)?; + if !exists_and_owned { + return Ok(ActionStatus::nop()); + } + use schema::looted::dsl::*; + let loot_value = looted + .find(params.loot_id) + .select(base_price) + .first::(conn)?; + let sell_value = (loot_value / 2) as f32; + diesel::delete(looted.find(params.loot_id)) + .execute(conn) + .and_then(|r| match r { + // On deletion, update this player wealth + 1 => Ok(UpdateWealth + .execute( + conn, + WealthParams { + player_id: params.player_id, + value_in_gp: sell_value as f32, + }, + ) + .unwrap()), + _ => Ok(ActionStatus { + executed: false, + response: None, + }), + }) + } + } + + pub struct PutClaim; + impl UserAction for PutClaim { + type Params = LootParams; + type Response = (); + fn execute<'q>( + self, + conn: &DbConnection, + params: Self::Params, + ) -> ActionResult { + let exists: bool = + diesel::select(models::Loot::exists(params.loot_id)).get_result(conn)?; + if !exists { + return Ok(ActionStatus::nop()); + }; + let claim = models::claim::NewClaim::new(params.player_id, params.loot_id); + diesel::insert_into(schema::claims::table) + .values(&claim) + .execute(conn) + .map(ActionStatus::was_updated) + } + } + + pub struct WithdrawClaim; + impl UserAction for WithdrawClaim { + type Params = LootParams; + type Response = (); + fn execute<'q>( + self, + conn: &DbConnection, + params: Self::Params, + ) -> ActionResult { + use schema::claims::dsl::*; + diesel::delete( + claims + .filter(loot_id.eq(params.loot_id)) + .filter(player_id.eq(params.player_id)), + ) + .execute(conn) + .map(ActionStatus::was_updated) + } + } + + pub struct WealthParams { + pub player_id: i32, + pub value_in_gp: f32, + } + pub struct UpdateWealth; + + impl UserAction for UpdateWealth { + type Params = WealthParams; + type Response = Option<(i32, i32, i32, i32)>; + + fn execute<'q>( + self, + conn: &'q DbConnection, + params: WealthParams, + ) -> ActionResult { + use schema::players::dsl::*; + let current_wealth = players + .find(params.player_id) + .select((cp, sp, gp, pp)) + .first::(conn)?; + // TODO: improve thisdiesel dependant transaction + // should be move inside a WealthUpdate method + let updated_wealth = + models::Wealth::from_gp(current_wealth.to_gp() + params.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(params.player_id)) + .set(&updated_wealth) + .execute(conn) + .map(|r| match r { + 1 => ActionStatus { + executed: true, + response: Some(diff), + }, + _ => ActionStatus::nop(), + }) + } + } +} + + +pub(crate) mod admin { + pub struct AddPlayer; + pub struct AddLoot; + pub struct SellLoot; + pub struct ResolveClaims; + + +} diff --git a/lootalot_db/src/lib.rs b/lootalot_db/src/lib.rs index 304cdcb..e2ec5ac 100644 --- a/lootalot_db/src/lib.rs +++ b/lootalot_db/src/lib.rs @@ -13,8 +13,10 @@ use diesel::prelude::*; use diesel::query_dsl::RunQueryDsl; use diesel::r2d2::{self, ConnectionManager}; +mod actions; pub mod models; mod schema; +use actions::{ActionResult, ActionStatus, UserAction}; /// The connection used pub type DbConnection = SqliteConnection; @@ -22,41 +24,7 @@ pub type DbConnection = SqliteConnection; pub type Pool = r2d2::Pool>; /// The result of a query on DB pub type QueryResult = Result; -/// The result of an action provided by DbApi -pub type ActionResult = QueryResult>; -/// Return status of an API Action -#[derive(Serialize, Debug)] -pub struct ActionStatus { - /// Has the action made changes ? - pub executed: bool, - /// Response payload - pub response: R, -} - -impl ActionStatus<()> { - fn was_updated(updated_lines: usize) -> Self { - match updated_lines { - 1 => Self::ok(), - _ => Self::nop(), - } - } - fn ok() -> ActionStatus<()> { - Self { - executed: true, - response: (), - } - } -} - -impl ActionStatus { - fn nop() -> ActionStatus { - Self { - executed: false, - response: Default::default(), - } - } -} /// A wrapper providing an API over the database /// It offers a convenient way to deal with connection. /// @@ -160,15 +128,15 @@ impl<'q> AsPlayer<'q> { /// /// This currently panics if player wealth fails to be updated, as this is /// a serious error. TODO: handle deletion of bought item in case of wealth update failure. - pub fn buy<'a>(self, name: &'a str, price: i32) -> ActionResult> { - let new_item = models::item::NewLoot::to_player(self.id, (name, price)); - diesel::insert_into(schema::looted::table) - .values(&new_item) - .execute(self.conn) - .and_then(|r| match r { - 1 => Ok(self.update_wealth(-(price as f32)).unwrap()), - _ => Ok(ActionStatus::nop()), - }) + pub fn buy>(self, name: S, price: i32) -> ActionResult> { + actions::player::Buy.execute( + self.conn, + actions::player::AddLootParams { + player_id: self.id, + loot_name: name.into(), + loot_price: price, + }, + ) } /// Sell an item from this player chest /// @@ -181,79 +149,46 @@ impl<'q> AsPlayer<'q> { loot_id: i32, _price_mod: Option, ) -> ActionResult> { - // Check that the item belongs to player - let exists_and_owned: bool = - diesel::select(models::Loot::owns(self.id, loot_id)).get_result(self.conn)?; - if !exists_and_owned { - return Ok(ActionStatus::nop()); - } - use schema::looted::dsl::*; - let loot_value = looted - .find(loot_id) - .select(base_price) - .first::(self.conn)?; - let sell_value = (loot_value / 2) as f32; - diesel::delete(looted.find(loot_id)) - .execute(self.conn) - .and_then(|r| match r { - // On deletion, update this player wealth - 1 => Ok(self.update_wealth(sell_value).unwrap()), - _ => Ok(ActionStatus { - executed: false, - response: None, - }), - }) + actions::player::Sell.execute( + self.conn, + actions::player::LootParams { + player_id: self.id, + loot_id, + }, + ) } /// Adds the value in gold to the player's wealth. /// /// Value can be negative to substract wealth. pub fn update_wealth(self, value_in_gp: f32) -> ActionResult> { - 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 => ActionStatus { - executed: true, - response: Some(diff), - }, - _ => ActionStatus::nop(), - }) + actions::player::UpdateWealth.execute( + self.conn, + actions::player::WealthParams { + player_id: self.id, + value_in_gp, + }, + ) } /// 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 Ok(ActionStatus::nop()); - }; - let claim = models::claim::NewClaim::new(self.id, item); - diesel::insert_into(schema::claims::table) - .values(&claim) - .execute(self.conn) - .map(ActionStatus::was_updated) + actions::player::PutClaim.execute( + self.conn, + actions::player::LootParams { + player_id: self.id, + loot_id: item, + }, + ) } /// 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)), + actions::player::WithdrawClaim.execute( + self.conn, + actions::player::LootParams { + player_id: self.id, + loot_id: item, + }, ) - .execute(self.conn) - .map(ActionStatus::was_updated) } }