moves db logic inside model's managers

This commit is contained in:
2019-10-13 16:02:47 +02:00
parent 068b2e7169
commit 0df875d6a6
6 changed files with 183 additions and 116 deletions

View File

@@ -70,7 +70,7 @@ impl<'q> DbApi<'q> {
///
/// TODO: remove limit used for debug
pub fn fetch_inventory(self) -> QueryResult<Vec<models::Item>> {
Ok(schema::items::table.limit(100).load::<models::Item>(self.0)?)
models::item::Inventory(self.0).all()
}
/// Fetch all existing claims
pub fn fetch_claims(self) -> QueryResult<Vec<models::Claim>> {
@@ -120,7 +120,7 @@ impl<'q> AsPlayer<'q> {
/// assert_eq!(format!("{:?}", loot), "[]".to_string());
/// ```
pub fn loot(self) -> QueryResult<Vec<models::Item>> {
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<models::Item> = 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::<models::Item>(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::<models::Loot>(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::<models::Wealth>(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<models::item::Item>) -> 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::<models::Claim>(self.0)?
.grouped_by(&loot);

View File

@@ -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<Claim> {
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<Claim> {
// 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::<Claim>(self.0)?)
}
/// Removes a claim from database, returning it
pub fn remove(self, req_player_id: i32, req_loot_id: i32) -> QueryResult<Claim> {
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 {

View File

@@ -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<OwnedLoot, ItemColumns>;
@@ -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<Vec<Item>> {
items::table.load::<Item>(self.0)
}
pub fn find(&self, item_id: i32) -> QueryResult<Item> {
items::table
.find(item_id)
.first::<Item>(self.0)
}
}
type WithOwner = Eq<looted::owner_id, i32>;
type OwnedLoot = Filter<looted::table, WithOwner>;
/// 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<Find<OwnedLoot, i32>> {
fn owns(player: i32, item: i32) -> Exists<Find<OwnedLoot, i32>> {
exists(Loot::owned_by(player).find(item))
}
pub(crate) fn exists(id: i32) -> Exists<Find<looted::table, i32>> {
pub(super) fn exists(id: i32) -> Exists<Find<looted::table, i32>> {
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<Vec<Item>> {
Ok(Item::owned_by(self.1).load(self.0)?)
}
/// Finds an item by id
pub fn find(&self, loot_id: i32) -> QueryResult<Item> {
Ok(
looted::table
.find(loot_id)
.first::<Loot>(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<Item> {
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<Item> {
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<Item> {
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,
}
}
}

View File

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

View File

@@ -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<Wealth> {
use crate::schema::players::dsl::*;
let current_wealth = players
.find(self.1)
.select((cp, sp, gp, pp))
.first::<Wealth>(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
///