enhance responses from API, integrates with frontend

This commit is contained in:
2019-07-11 14:54:18 +02:00
parent bc65dfcd78
commit e08cd64743
9 changed files with 130 additions and 26 deletions

Binary file not shown.

View File

@@ -14,7 +14,30 @@ mod schema;
pub type DbConnection = SqliteConnection; pub type DbConnection = SqliteConnection;
pub type Pool = r2d2::Pool<ConnectionManager<DbConnection>>; pub type Pool = r2d2::Pool<ConnectionManager<DbConnection>>;
pub type QueryResult<T> = Result<T, diesel::result::Error>; pub type QueryResult<T> = Result<T, diesel::result::Error>;
pub type ActionResult = QueryResult<bool>; pub type ActionResult<R> = QueryResult<ActionStatus<R>>;
#[derive(Serialize, Debug)]
pub struct ActionStatus<R: serde::Serialize> {
/// 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 /// A wrapper providing an API over the database
/// It offers a convenient way to deal with connection /// 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. /// Adds the value in gold to the player's wealth.
/// ///
/// Value can be negative to substract 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<Option<(i32, i32, i32, i32)>> {
use schema::players::dsl::*; use schema::players::dsl::*;
let current_wealth = players let current_wealth = players
.find(self.id) .find(self.id)
@@ -126,30 +150,54 @@ impl<'q> AsPlayer<'q> {
let update = models::WealthUpdate::from_gp( let update = models::WealthUpdate::from_gp(
current_wealth.to_gp() + value_in_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) diesel::update(players)
.filter(id.eq(self.id)) .filter(id.eq(self.id))
.set(&update) .set(&update)
.execute(self.conn) .execute(self.conn)
// TODO: need to work out what this boolean REALLY means // 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 /// 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 }; let request = models::NewClaim { player_id: self.id, loot_id: item };
diesel::insert_into(schema::claims::table) diesel::insert_into(schema::claims::table)
.values(&request) .values(&request)
.execute(self.conn) .execute(self.conn)
.map(|r| match r { 1 => true, _ => false }) .map(|r| match r { 1 => ActionStatus::ok(), _ => ActionStatus::nop() })
} }
/// Withdraw claim /// Withdraw claim
pub fn unclaim(self, item: i32) -> ActionResult { pub fn unclaim(self, item: i32) -> ActionResult<()> {
use schema::claims::dsl::*; use schema::claims::dsl::*;
diesel::delete( diesel::delete(
claims claims
.filter(loot_id.eq(item)) .filter(loot_id.eq(item))
.filter(player_id.eq(self.id))) .filter(player_id.eq(self.id)))
.execute(self.conn) .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> { 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) diesel::insert_into(schema::players::table)
.values(&models::NewPlayer::create(&name, start_wealth)) .values(&models::NewPlayer::create(&name, start_wealth))
.execute(self.0) .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::<models::Claim>(self.0)?
.grouped_by(&loot);
// For each claimed item
let data = loot.into_iter().zip(claims).collect::<Vec<_>>();
dbg!(data);
// If mutiples claims -> find highest resolve, give to this player
// If only one claim -> give to claiming
Ok(ActionStatus::nop())
} }
} }

View File

@@ -1,7 +1,9 @@
use diesel::prelude::*; use diesel::prelude::*;
use crate::schema::claims; 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 { pub struct Claim {
id: i32, id: i32,
player_id: i32, player_id: i32,

View File

@@ -1,5 +1,6 @@
use diesel::prelude::*; 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::schema::{looted, items};
use crate::DbConnection; use crate::DbConnection;
@@ -33,8 +34,9 @@ type WithOwner = Eq<looted::owner_id, i32>;
type OwnedLoot = Filter<looted::table, WithOwner>; type OwnedLoot = Filter<looted::table, WithOwner>;
/// Represents an item that has been looted /// Represents an item that has been looted
#[derive(Debug, Queryable, Serialize)] #[derive(Identifiable, Debug, Queryable, Serialize)]
struct Loot { #[table_name="looted"]
pub(crate) struct Loot {
id: i32, id: i32,
name: String, name: String,
base_value: i32, base_value: i32,
@@ -43,10 +45,14 @@ struct Loot {
impl Loot { impl Loot {
/// A filter on Loot that is owned by given player /// 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 looted::table
.filter(looted::owner_id.eq(id)) .filter(looted::owner_id.eq(id))
} }
pub(crate) fn exists(id: i32) -> Exists<Find<looted::table, i32>> {
exists(looted::table.find(id))
}
} }
type ItemDesc<'a> = (&'a str, i32); type ItemDesc<'a> = (&'a str, i32);

View File

@@ -3,5 +3,6 @@ mod player;
mod claim; mod claim;
pub use item::Item; pub use item::Item;
pub(crate) use item::Loot;
pub use player::{Player, NewPlayer, WealthUpdate}; pub use player::{Player, NewPlayer, WealthUpdate};
pub use claim::{NewClaim, Claim}; pub use claim::{NewClaim, Claim};

View File

@@ -58,6 +58,10 @@ impl WealthUpdate{
let f = ( self.sp * 10 + self.cp ) as f32 / 100.0; let f = ( self.sp * 10 + self.cp ) as f32 / 100.0;
i as f32 + f i as f32 + f
} }
pub fn as_tuple(&self) -> (i32, i32, i32, i32) {
(self.cp, self.sp, self.gp, self.pp)
}
} }

View File

@@ -16,7 +16,6 @@ $danger: $red;
$table-cell-border: 1px solid $dark-red; $table-cell-border: 1px solid $dark-red;
$table-striped-row-even-background-color: $yellow-light; $table-striped-row-even-background-color: $yellow-light;
$button-padding-horizontal: 1em; $button-padding-horizontal: 1em;
@import "../node_modules/bulma/bulma.sass"; @import "../node_modules/bulma/bulma.sass";

View File

@@ -22,12 +22,14 @@ const Api = {
.catch(e => console.error("Fetch error", e)); .catch(e => console.error("Fetch error", e));
}, },
putClaim (playerId, itemId) { putClaim (playerId, itemId) {
console.log('newRequest from', playerId, 'on', itemId); return fetch(API_ENDPOINT(playerId + "/claim/" + itemId))
return Promise.resolve(true); .then(r => r.json())
.catch(e => console.error("Fetch error", e));
}, },
unClaim (playerId, itemId) { unClaim (playerId, itemId) {
console.log('cancelRequest of', playerId, 'on', itemId); return fetch(API_ENDPOINT(playerId + "/unclaim/" + itemId))
return Promise.resolve(true); .then(r => r.json())
.catch(e => console.error("Fetch error", e));
}, },
updateWealth (playerId, goldValue) { updateWealth (playerId, goldValue) {
return fetch(API_ENDPOINT(playerId + "/update-wealth/" + goldValue)) return fetch(API_ENDPOINT(playerId + "/update-wealth/" + goldValue))
@@ -57,9 +59,15 @@ export const AppStorage = {
.then(data => { .then(data => {
const [players, claims] = data; const [players, claims] = data;
this.__initPlayerList(players); 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) { __initPlayerList(data) {
for (var idx in data) { for (var idx in data) {
var playerDesc = data[idx]; var playerDesc = data[idx];
@@ -105,14 +113,18 @@ export const AppStorage = {
}, },
updatePlayerWealth (goldValue) { updatePlayerWealth (goldValue) {
if (this.debug) console.log('updatePlayerWealth', goldValue, this.state.player_id)
return Api.updateWealth(this.state.player_id, goldValue) return Api.updateWealth(this.state.player_id, goldValue)
.then(done => { .then(done => {
if (done) { if (done.executed) {
// Update player wealth // 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. // Put a claim on an item from group chest.
@@ -120,7 +132,7 @@ export const AppStorage = {
const playerId = this.state.player_id const playerId = this.state.player_id
Api.putClaim(playerId, itemId) Api.putClaim(playerId, itemId)
.then(done => { .then(done => {
if (done) { if (done.executed) {
// Update cliend-side state // Update cliend-side state
this.state.player_claims[playerId].push(itemId); this.state.player_claims[playerId].push(itemId);
} else { } else {
@@ -133,7 +145,7 @@ export const AppStorage = {
const playerId = this.state.player_id const playerId = this.state.player_id
Api.unClaim(playerId, itemId) Api.unClaim(playerId, itemId)
.then(done => { .then(done => {
if (done) { if (done.executed) {
var idx = this.state.player_claims[playerId].indexOf(itemId); var idx = this.state.player_claims[playerId].indexOf(itemId);
if (idx > -1) { if (idx > -1) {
this.state.player_claims[playerId].splice(idx, 1); this.state.player_claims[playerId].splice(idx, 1);

View File

@@ -85,6 +85,24 @@ pub(crate) fn serve() -> std::io::Result<()> {
db_call(pool, move |api| api.as_player(*player_id).loot()) 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( .route(
"/api/admin/add-player/{name}/{wealth}", "/api/admin/add-player/{name}/{wealth}",
web::get().to_async(move |pool: AppPool, data: web::Path<(String, f32)>| { web::get().to_async(move |pool: AppPool, data: web::Path<(String, f32)>| {