diff --git a/lootalot_db/db.sqlite3 b/lootalot_db/db.sqlite3 index 1835813..23a58d8 100644 Binary files a/lootalot_db/db.sqlite3 and b/lootalot_db/db.sqlite3 differ diff --git a/lootalot_db/src/lib.rs b/lootalot_db/src/lib.rs index 54184b5..5bfa01e 100644 --- a/lootalot_db/src/lib.rs +++ b/lootalot_db/src/lib.rs @@ -14,7 +14,30 @@ mod schema; pub type DbConnection = SqliteConnection; pub type Pool = r2d2::Pool>; pub type QueryResult = Result; -pub type ActionResult = QueryResult; +pub type ActionResult = QueryResult>; + +#[derive(Serialize, Debug)] +pub struct ActionStatus { + /// Has the action made changes ? + executed: bool, + /// Response payload + response: R, +} + +impl ActionStatus<()> { + fn ok() -> ActionStatus<()> { + Self { + executed: true, + response: (), + } + } + fn nop() -> ActionStatus<()> { + Self { + executed: false, + response: (), + } + } +} /// A wrapper providing an API over the database /// It offers a convenient way to deal with connection @@ -115,7 +138,8 @@ impl<'q> AsPlayer<'q> { /// Adds the value in gold to the player's wealth. /// /// Value can be negative to substract wealth. - pub fn update_wealth(self, value_in_gp: f32) -> ActionResult { + pub fn update_wealth(self, value_in_gp: f32) + -> ActionResult> { use schema::players::dsl::*; let current_wealth = players .find(self.id) @@ -126,30 +150,54 @@ impl<'q> AsPlayer<'q> { let update = models::WealthUpdate::from_gp( current_wealth.to_gp() + value_in_gp ); + // Difference in coins that is sent back + let (old, new) = + (current_wealth.as_tuple(), update.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(&update) .execute(self.conn) // TODO: need to work out what this boolean REALLY means - .map(|r| match r { 1 => true, _ => false }) + .map(|r| match r { + 1 => ActionStatus { + executed: true, + response: Some(diff), + }, + _ => ActionStatus { + executed: false, + response: None, + }, }) } /// Put a claim on a specific item - pub fn claim(self, item: i32) -> ActionResult { + pub fn claim(self, item: i32) -> ActionResult<()> { + // TODO: check that looted item exists + let exists: bool = + diesel::select(models::Loot::exists(item)) + .get_result(self.conn)?; + if !exists { + return Ok(ActionStatus::nop()) + }; 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 }) + .map(|r| match r { 1 => ActionStatus::ok(), _ => ActionStatus::nop() }) } /// Withdraw claim - pub fn unclaim(self, item: i32) -> ActionResult { + 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 }) + .map(|r| match r { 1 => ActionStatus::ok(), _ => ActionStatus::nop() }) } } @@ -158,11 +206,25 @@ pub struct AsAdmin<'q>(&'q DbConnection); impl<'q> AsAdmin<'q> { - pub fn add_player(self, name: String, start_wealth: f32) -> ActionResult { + pub fn add_player(self, name: String, start_wealth: f32) -> ActionResult<()> { diesel::insert_into(schema::players::table) .values(&models::NewPlayer::create(&name, start_wealth)) .execute(self.0) - .map(|r| match r { 1 => true, _ => false }) + .map(|r| match r { 1 => ActionStatus::ok(), _ => ActionStatus::nop() }) + } + + pub fn resolve_claims(self) -> ActionResult<()> { + // Fetch all claims, grouped by items. + let loot = models::Loot::owned_by(0).load(self.0)?; + let claims = schema::claims::table + .load::(self.0)? + .grouped_by(&loot); + // For each claimed item + let data = loot.into_iter().zip(claims).collect::>(); + dbg!(data); + // If mutiples claims -> find highest resolve, give to this player + // If only one claim -> give to claiming + Ok(ActionStatus::nop()) } } diff --git a/lootalot_db/src/models/claim.rs b/lootalot_db/src/models/claim.rs index 7cea712..c6cc4af 100644 --- a/lootalot_db/src/models/claim.rs +++ b/lootalot_db/src/models/claim.rs @@ -1,7 +1,9 @@ use diesel::prelude::*; use crate::schema::claims; +use crate::models::item::Loot; -#[derive(Queryable, Serialize, Debug)] +#[derive(Identifiable, Queryable, Associations, Serialize, Debug)] +#[belongs_to(Loot)] pub struct Claim { id: i32, player_id: i32, diff --git a/lootalot_db/src/models/item.rs b/lootalot_db/src/models/item.rs index 9a52074..aa70606 100644 --- a/lootalot_db/src/models/item.rs +++ b/lootalot_db/src/models/item.rs @@ -1,5 +1,6 @@ use diesel::prelude::*; -use diesel::dsl::{Eq, Filter, Select}; +use diesel::dsl::{Eq, Filter, Select, exists, Find }; +use diesel::expression::exists::Exists; use crate::schema::{looted, items}; use crate::DbConnection; @@ -33,8 +34,9 @@ type WithOwner = Eq; type OwnedLoot = Filter; /// Represents an item that has been looted -#[derive(Debug, Queryable, Serialize)] -struct Loot { +#[derive(Identifiable, Debug, Queryable, Serialize)] +#[table_name="looted"] +pub(crate) struct Loot { id: i32, name: String, base_value: i32, @@ -43,10 +45,14 @@ struct Loot { impl Loot { /// A filter on Loot that is owned by given player - fn owned_by(id: i32) -> OwnedLoot { + pub(crate) fn owned_by(id: i32) -> OwnedLoot { looted::table .filter(looted::owner_id.eq(id)) } + + pub(crate) fn exists(id: i32) -> Exists> { + exists(looted::table.find(id)) + } } type ItemDesc<'a> = (&'a str, i32); diff --git a/lootalot_db/src/models/mod.rs b/lootalot_db/src/models/mod.rs index aeb019d..0c80723 100644 --- a/lootalot_db/src/models/mod.rs +++ b/lootalot_db/src/models/mod.rs @@ -3,5 +3,6 @@ mod player; mod claim; pub use item::Item; +pub(crate) use item::Loot; pub use player::{Player, NewPlayer, WealthUpdate}; pub use claim::{NewClaim, Claim}; diff --git a/lootalot_db/src/models/player.rs b/lootalot_db/src/models/player.rs index 302ca54..9da8474 100644 --- a/lootalot_db/src/models/player.rs +++ b/lootalot_db/src/models/player.rs @@ -58,6 +58,10 @@ impl WealthUpdate{ let f = ( self.sp * 10 + self.cp ) as f32 / 100.0; i as f32 + f } + + pub fn as_tuple(&self) -> (i32, i32, i32, i32) { + (self.cp, self.sp, self.gp, self.pp) + } } diff --git a/lootalot_front/sass/scroll.scss b/lootalot_front/sass/scroll.scss index 23b0d6a..243dfe4 100644 --- a/lootalot_front/sass/scroll.scss +++ b/lootalot_front/sass/scroll.scss @@ -16,7 +16,6 @@ $danger: $red; $table-cell-border: 1px solid $dark-red; $table-striped-row-even-background-color: $yellow-light; - $button-padding-horizontal: 1em; @import "../node_modules/bulma/bulma.sass"; diff --git a/lootalot_front/src/AppStorage.js b/lootalot_front/src/AppStorage.js index f5bec72..be2b4dc 100644 --- a/lootalot_front/src/AppStorage.js +++ b/lootalot_front/src/AppStorage.js @@ -22,12 +22,14 @@ const Api = { .catch(e => console.error("Fetch error", e)); }, putClaim (playerId, itemId) { - console.log('newRequest from', playerId, 'on', itemId); - return Promise.resolve(true); + return fetch(API_ENDPOINT(playerId + "/claim/" + itemId)) + .then(r => r.json()) + .catch(e => console.error("Fetch error", e)); }, unClaim (playerId, itemId) { - console.log('cancelRequest of', playerId, 'on', itemId); - return Promise.resolve(true); + return fetch(API_ENDPOINT(playerId + "/unclaim/" + itemId)) + .then(r => r.json()) + .catch(e => console.error("Fetch error", e)); }, updateWealth (playerId, goldValue) { return fetch(API_ENDPOINT(playerId + "/update-wealth/" + goldValue)) @@ -57,9 +59,15 @@ export const AppStorage = { .then(data => { const [players, claims] = data; this.__initPlayerList(players); - console.log("claims", claims); + this.__initClaimsStore(claims); }); }, + __initClaimsStore(data) { + for (var idx in data) { + var claimDesc = data[idx]; + this.state.player_claims[claimDesc.player_id].push(claimDesc.loot_id); + } + }, __initPlayerList(data) { for (var idx in data) { var playerDesc = data[idx]; @@ -105,14 +113,18 @@ export const AppStorage = { }, updatePlayerWealth (goldValue) { - if (this.debug) console.log('updatePlayerWealth', goldValue, this.state.player_id) return Api.updateWealth(this.state.player_id, goldValue) .then(done => { - if (done) { + if (done.executed) { // Update player wealth - this.state.player_list[this.state.player_id].cp += 1; + var diff = done.response; + if (this.debug) console.log('updatePlayerWealth', diff) + this.state.player_list[this.state.player_id].cp += diff[0]; + this.state.player_list[this.state.player_id].sp += diff[1]; + this.state.player_list[this.state.player_id].gp += diff[2]; + this.state.player_list[this.state.player_id].pp += diff[3]; } - return done; + return done.executed; }); }, // Put a claim on an item from group chest. @@ -120,7 +132,7 @@ export const AppStorage = { const playerId = this.state.player_id Api.putClaim(playerId, itemId) .then(done => { - if (done) { + if (done.executed) { // Update cliend-side state this.state.player_claims[playerId].push(itemId); } else { @@ -133,7 +145,7 @@ export const AppStorage = { const playerId = this.state.player_id Api.unClaim(playerId, itemId) .then(done => { - if (done) { + if (done.executed) { var idx = this.state.player_claims[playerId].indexOf(itemId); if (idx > -1) { this.state.player_claims[playerId].splice(idx, 1); diff --git a/src/server.rs b/src/server.rs index 0634b37..704057b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -85,6 +85,24 @@ pub(crate) fn serve() -> std::io::Result<()> { db_call(pool, move |api| api.as_player(*player_id).loot()) }), ) + .route( + "/api/{player_id}/claim/{item_id}", + web::get().to_async(move |pool: AppPool, data: web::Path<(i32, i32)>| { + db_call(pool, move |api| api.as_player(data.0).claim(data.1)) + }) + ) + .route( + "/api/{player_id}/unclaim/{item_id}", + web::get().to_async(move |pool: AppPool, data: web::Path<(i32, i32)>| { + db_call(pool, move |api| api.as_player(data.0).unclaim(data.1)) + }) + ) + .route( + "/api/admin/resolve-claims", + web::get().to_async(move |pool: AppPool| { + db_call(pool, move |api| api.as_admin().resolve_claims()) + }), + ) .route( "/api/admin/add-player/{name}/{wealth}", web::get().to_async(move |pool: AppPool, data: web::Path<(String, f32)>| {