moves db logic inside model's managers
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
///
|
||||
|
||||
@@ -35,7 +35,6 @@ where
|
||||
J: serde::ser::Serialize + Send + 'static,
|
||||
Q: Fn(DbApi) -> QueryResult<J> + Send + 'static,
|
||||
{
|
||||
dbg!("db_call");
|
||||
let conn = pool.get().unwrap();
|
||||
web::block(move || {
|
||||
let api = DbApi::with_conn(&conn);
|
||||
|
||||
Reference in New Issue
Block a user