From 5f598aff24b82442f20689966f6e20eb30136710 Mon Sep 17 00:00:00 2001 From: Artus Date: Mon, 1 Jul 2019 15:41:13 +0200 Subject: [PATCH] cleans up --- lootalot_db/src/lib.rs | 82 +++++++++++++++++++--- lootalot_db/src/models/player.rs | 37 +++++----- src/api.rs | 114 ------------------------------- src/main.rs | 1 - src/server.rs | 52 ++++++++++++-- 5 files changed, 136 insertions(+), 150 deletions(-) delete mode 100644 src/api.rs diff --git a/lootalot_db/src/lib.rs b/lootalot_db/src/lib.rs index cd3a06f..07db7c3 100644 --- a/lootalot_db/src/lib.rs +++ b/lootalot_db/src/lib.rs @@ -12,25 +12,85 @@ pub use diesel::query_dsl::RunQueryDsl; mod models; mod schema; -pub use models::Item; -pub use models::Player; - pub type DbConnection = SqliteConnection; pub type Pool = r2d2::Pool>; pub type QueryResult = Result; pub type ActionResult = QueryResult; -impl Player { - /// Query the list of all players - pub fn fetch_list(conn: &SqliteConnection) -> QueryResult> { - Ok( schema::players::table.load::(conn)? ) - } +/// 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() +/// v .as_player(player_id) -> AsPlayer<'q> +/// v .loot() -> List of items owned (Vec) +/// x .claim(item_id) -> Success status (bool) +/// x .unclaim(item_id) -> Success status (bool) +/// x .sell(item_id) -> Success status (bool, earned) +/// x .buy(inventory_item_id) -> Success status (bool, cost) +/// x .update_wealth(gold_pieces) -> Success status (bool, new_wealth) +/// x .as_admin() +/// x .add_loot([inventory_item_ids]) -> Success status +/// x .resolve_claims() +/// x .sell_loot([players], [excluded_item_ids]) -> Success status (bool, player_share) +/// +pub struct DbApi<'q>(&'q DbConnection); - pub fn action_claim_object(player_id: i32, item_id: i32, conn: &DbConnection) -> ActionResult { - Ok(false) +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)? + ) + } + /// 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 } + } +} - pub fn action_withdraw_claim(player_id: i32, item_id: i32, conn: &DbConnection) -> ActionResult { +/// 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)? + ) + } + /// Put a claim on a specific item + pub fn claim(self, item: i32) -> ActionResult { Ok(false) } } diff --git a/lootalot_db/src/models/player.rs b/lootalot_db/src/models/player.rs index b772cef..580456d 100644 --- a/lootalot_db/src/models/player.rs +++ b/lootalot_db/src/models/player.rs @@ -12,29 +12,28 @@ pub struct Player { pp: i32, } -/// The sign of the update -enum WealthUpdateKind { - Income, - Expense, -} - -/// Representation of wealth value -type WealthValues = (i32, i32, i32, i32); +/// Wealth represented as a single fractionnal amount of gold pieces +struct WealthInGold(f32); /// Data used to update wealth -struct WealthUpdate { - kind: WealthUpdateKind, - values: WealthValues, -} +struct WealthUpdate(WealthInGold); -impl WealthUpdate { - /// Create a new update - fn new(values: WealthValues, kind: WealthUpdateKind) -> Self { - WealthUpdate { kind, values } +impl WealthInGold { + /// Unpack individual pieces counts from gold value + fn unpack(self) -> (i32, i32, i32, i32) { + // TODO: 0,01 pp = 1 gp = 10 sp = 100 cp + (0,0,0,0) } +} + + +impl WealthUpdate { /// Apply the update to the specified player - fn apply_to(self, player_id: i32) {} + fn commit(self, player_id: i32) { + // Extract (cp, sp, gp, pp) from floating gold piece value + // Update record in db + } } /// Representation of a new player record @@ -49,8 +48,8 @@ pub struct NewPlayer<'a> { } impl<'a> NewPlayer<'a> { - fn new(name: &'a str, wealth: Option) -> Self { - let (cp, sp, gp, pp) = wealth.unwrap_or((0, 0, 0, 0)); + fn new(name: &'a str, wealth: Option) -> Self { + let (cp, sp, gp, pp) = wealth.map(|w| w.unpack()).unwrap_or((0, 0, 0, 0)); NewPlayer { name, cp, diff --git a/src/api.rs b/src/api.rs deleted file mode 100644 index 705727b..0000000 --- a/src/api.rs +++ /dev/null @@ -1,114 +0,0 @@ -use actix_web::{web, web::Json, Error, HttpResponse, Result}; -use futures::Future; - -use lootalot_db::{RunQueryDsl, Pool, DbConnection, QueryResult, ActionResult}; -use lootalot_db::{Item, Player}; - -/// A wrapper providing an API over the database -/// It offers a convenient way to deal with connection -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> { - Player::fetch_list(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> { - dbg!( Ok( Item::owned_by(self.id).load(self.conn)? ) ) - } - /// Put a claim on a specific item - pub fn claim(self, item: i32) -> ActionResult { - Player::action_claim_object(self.id, item, self.conn) - } -} - -// 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) -// .fetch_players() -// .with_player(player_id) -> AsPlayer<'q> -// .loot() -> List of items owned (Vec) -// .claim(item_id) -> Success status (bool) -// .unclaim(item_id) -> Success status (bool) -// .sell(item_id) -> Success status (bool, earned) -// .income(wealth_data) -> Success status (bool, new_wealth) -// .with_admin() -// .resolve_claims() -// - -/// Wraps call to the DbApi and process its result as a async HttpResponse -/// -/// Provides a convenient way to call the api inside a route definition. Given a connection pool, -/// access to the api is granted in a closure. The closure is called in a blocking way and should -/// return a QueryResult. -/// If the query succeeds, it's result is returned as JSON data. Otherwise, an InternalServerError -/// is returned. -/// -/// # Usage -/// ``` -/// (...) -/// .route("path/to/", -/// move |pool: web::Data| { -/// // user data can be processed here -/// // ... -/// db_call(pool, move |api| { -/// // ...do what you want with the api -/// } -/// } -/// ) -/// ``` -pub fn db_call< - J: serde::ser::Serialize + Send + 'static, - Q: Fn(DbApi) -> QueryResult + Send + 'static, ->( - pool: web::Data, - query: Q, -) -> impl Future { - let conn = pool.get().unwrap(); - web::block(move || { - let api = DbApi::with_conn(&conn); - query(api) - }) - .then(|res| match res { - Ok(players) => HttpResponse::Ok().json(players), - Err(e) => { - dbg!(&e); - HttpResponse::InternalServerError().finish() - } - }) -} diff --git a/src/main.rs b/src/main.rs index ff2c1f6..f3ebe6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ extern crate env_logger; extern crate actix_web; extern crate lootalot_db; -mod api; mod server; fn main() { diff --git a/src/server.rs b/src/server.rs index b1ee20e..c282bbf 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,11 +1,53 @@ use std::env; +use futures::Future; use actix_files as fs; -use actix_web::{web, App, HttpServer}; -use lootalot_db::Pool; -use crate::api; +use actix_web::{web, App, HttpServer, HttpResponse, Error}; +use lootalot_db::{Pool, DbApi, QueryResult}; type AppPool = web::Data; +/// Wraps call to the DbApi and process its result as a async HttpResponse +/// +/// Provides a convenient way to call the api inside a route definition. Given a connection pool, +/// access to the api is granted in a closure. The closure is called in a blocking way and should +/// return a QueryResult. +/// If the query succeeds, it's result is returned as JSON data. Otherwise, an InternalServerError +/// is returned. +/// +/// # Usage +/// ``` +/// (...) +/// .route("path/to/", +/// move |pool: web::Data| { +/// // user data can be processed here +/// // ... +/// db_call(pool, move |api| { +/// // ...do what you want with the api +/// } +/// } +/// ) +/// ``` +pub fn db_call< + J: serde::ser::Serialize + Send + 'static, + Q: Fn(DbApi) -> QueryResult + Send + 'static, +>( + pool: AppPool, + query: Q, +) -> impl Future { + let conn = pool.get().unwrap(); + web::block(move || { + let api = DbApi::with_conn(&conn); + query(api) + }) + .then(|res| match res { + Ok(players) => HttpResponse::Ok().json(players), + Err(e) => { + dbg!(&e); + HttpResponse::InternalServerError().finish() + } + }) +} + pub(crate) fn serve() -> std::io::Result<()> { let www_root: String = env::var("WWW_ROOT").expect("WWW_ROOT must be set"); dbg!(&www_root); @@ -17,14 +59,14 @@ pub(crate) fn serve() -> std::io::Result<()> { .route( "/players", web::get().to_async(move |pool: AppPool| { - api::db_call(pool, move |api| api.fetch_players()) + db_call(pool, move |api| api.fetch_players()) }), ) .route( "/loot/{player_id}", web::get().to_async(move |pool: AppPool, player_id: web::Path| { let player_id: i32 = *player_id; - api::db_call(pool, move |api| api.as_player(player_id).loot()) + db_call(pool, move |api| api.as_player(player_id).loot()) }), ) .service(fs::Files::new("/", www_root.clone()).index_file("index.html"))