use diesel::dsl::{exists, Eq, Filter, Find, Select}; use diesel::expression::exists::Exists; use diesel::prelude::*; use crate::schema::{items, looted}; use crate::{DbConnection, QueryResult, Update, UpdateResult, Claims }; type ItemColumns = (looted::id, looted::name, looted::base_price); const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price); type OwnedBy = Select; /// Represents a basic item #[derive(Debug, Queryable, Serialize, Deserialize, Clone)] pub struct Item { pub id: i32, pub name: String, base_price: i32, } impl Item { /// Returns this item value pub fn value(&self) -> i32 { self.base_price } /// Returns this item sell value pub fn sell_value(&self) -> i32 { self.base_price / 2 } pub fn remove(self, conn: &DbConnection) -> UpdateResult { conn.transaction( || -> UpdateResult { Claims(conn).delete_for_loot(self.id)?; diesel::delete(looted::table.find(self.id)).execute(conn)?; Ok(Update::ItemRemoved(self)) } ) } fn owned_by(player: i32) -> OwnedBy { Loot::owned_by(player).select(ITEM_COLUMNS) } } pub struct Inventory<'q>(pub &'q DbConnection); impl<'q> Inventory<'q> { /// Get all items from Inventory pub fn all(&self) -> QueryResult> { items::table.load::(self.0) } /// Find an item in Inventory 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, /// hence has an owner. #[derive(Identifiable, Debug, Queryable)] #[table_name = "looted"] pub(super) struct Loot { id: i32, name: String, base_price: i32, owner: i32, } impl Loot { /// A filter on Loot that is owned by given player pub(super) fn owned_by(id: i32) -> OwnedLoot { looted::table.filter(looted::owner_id.eq(id)) } fn exists(id: i32) -> Exists> { exists(looted::table.find(id)) } pub(super) fn set_owner(&self, owner: i32, conn: &DbConnection) -> QueryResult<()> { diesel::update(looted::table.find(self.id)) .set(looted::dsl::owner_id.eq(owner)) .execute(conn)?; Ok(()) } pub(super) fn into_item(self) -> Item { Item { id: self.id, name: self.name, base_price: self.base_price, } } pub(super) fn find(id: i32) -> Find { looted::table.find(id) } } /// Manager for a *single* player 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 /// /// Returns a NotFound error if an item is found by it /// does not belong to this player pub fn find(&self, loot_id: i32) -> QueryResult { Ok(Loot::find(loot_id).first(self.0).and_then(|loot: 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)?) } pub(crate) fn add>(self, name: S, base_price: i32) -> UpdateResult { self.add_from(&Item { id: 0, name: name.into(), base_price, }) } /// Adds a copy of the given item inside player chest pub fn add_from(self, item: &Item) -> UpdateResult { 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)?; Ok(Update::ItemAdded(self.last()?)) } } /// 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"] struct NewLoot<'a> { name: &'a str, base_price: i32, owner_id: i32, }