extern crate dotenv; #[macro_use] extern crate diesel; #[macro_use] extern crate serde_derive; use diesel::prelude::*; use diesel::r2d2::{self, ConnectionManager}; use diesel::query_dsl::RunQueryDsl; mod models; mod schema; pub type DbConnection = SqliteConnection; pub type Pool = r2d2::Pool>; pub type QueryResult = Result; pub type ActionResult = QueryResult; /// A wrapper providing an API over the database /// It offers a convenient way to deal with connection /// /// # Todo list /// /// struct DbApi<'q>(&'q DbConnection); /// ::new() -> DbApi<'q> (Db finds a connection by itself, usefull for cli) /// ::with_conn(conn) -> DbApi<'q> (uses a user-defined connection) /// v .fetch_players() /// x .fetch_inventory() /// v .as_player(player_id) -> AsPlayer<'q> /// v .loot() -> List of items owned (Vec) /// v .claim(item_id) -> Success status (bool) /// v .unclaim(item_id) -> Success status (bool) /// x .sell(item_id) -> Success status (bool, earned) /// x .buy(inventory_item_id) -> Success status (bool, cost) /// v .update_wealth(gold_pieces) -> Success status (bool, new_wealth) /// x .as_admin() /// x .add_loot([inventory_item_ids]) -> Success status /// x .sell_loot([players], [excluded_item_ids]) -> Success status (bool, player_share) /// x .resolve_claims() /// x .add_player(player_data) /// pub struct DbApi<'q>(&'q DbConnection); impl<'q> DbApi<'q> { /// Returns a DbApi using the user given connection /// /// # Usage /// ``` /// let conn = DbConnection::establish(); /// let api = DbApi::with_conn(&conn); /// ``` pub fn with_conn(conn: &'q DbConnection) -> Self { Self(conn) } /// Fetch the list of all players /// /// This method consumes the DbApi object. pub fn fetch_players(self) -> QueryResult> { Ok( schema::players::table .load::(self.0)? ) } /// Fetch the inventory of items /// /// Consumes the DbApi instance pub fn fetch_inventory(self) -> QueryResult> { Ok( schema::items::table .load::(self.0)? ) } pub fn fetch_claims(self) -> QueryResult> { Ok( schema::claims::table .load::(self.0)? ) } /// Wrapper for acting as a specific player /// /// The DbApi is moved inside a new AsPlayer object. /// /// # Usage /// ``` /// let player_id: i32 = 1; // Id that references player in DB /// let player = api.as_player(player_id); /// ``` pub fn as_player(self, id: i32) -> AsPlayer<'q> { AsPlayer { id, conn: self.0 } } } /// A wrapper for interactions of players with the database /// Possible actions are exposed as methods pub struct AsPlayer<'q> { id: i32, conn: &'q DbConnection, } impl<'q> AsPlayer<'q> { /// Fetch the content of a player's chest pub fn loot(self) -> QueryResult> { Ok( models::Item::owned_by(self.id) .load(self.conn)? ) } pub fn update_wealth(self, value: f32) -> ActionResult { use schema::players::dsl::*; let current_wealth = players.find(self.id) .select((cp, sp, gp, pp)) .first::(self.conn)?; // TODO: improve this // should be move inside a WealthUpdate method let update = models::WealthUpdate::from_gp(current_wealth.to_gp() + value); diesel::update(players) .filter(id.eq(self.id)) .set(&update) .execute(self.conn) // TODO: need to work out what this boolean REALLY means .map(|r| match r { 1 => true, _ => false }) } /// Put a claim on a specific item pub fn claim(self, item: i32) -> ActionResult { let request = models::NewClaim { player_id: self.id, loot_id: item }; diesel::insert_into(schema::claims::table) .values(&request) .execute(self.conn) .map(|r| match r { 1 => true, _ => false }) } 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) .map(|r| match r { 1 => true, _ => false }) } } pub fn create_pool() -> Pool { let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL"); dbg!( &connspec ); let manager = ConnectionManager::::new(connspec); r2d2::Pool::builder() .build(manager) .expect("Failed to create pool.") } #[cfg(test)] mod tests { }