Compare commits

2 Commits

Author SHA1 Message Date
ee0b7b2b7a adds UpdateResult class of queries 2019-10-29 16:19:00 +01:00
089aaf9a6d fixes api 2019-10-29 15:12:56 +01:00
6 changed files with 77 additions and 62 deletions

View File

@@ -18,9 +18,9 @@ mod schema;
pub use models::{ pub use models::{
claim::{Claim, Claims}, claim::{Claim, Claims},
item::{Item, LootManager, Inventory},
player::{Player, Wealth, Players, AsPlayer},
history::{Event, UpdateList}, history::{Event, UpdateList},
item::{Inventory, Item, LootManager},
player::{AsPlayer, Player, Players, Wealth},
}; };
/// The connection used /// The connection used
@@ -29,6 +29,7 @@ pub type DbConnection = SqliteConnection;
pub type Pool = r2d2::Pool<ConnectionManager<DbConnection>>; pub type Pool = r2d2::Pool<ConnectionManager<DbConnection>>;
/// The result of a query on DB /// The result of a query on DB
pub type QueryResult<T> = Result<T, diesel::result::Error>; pub type QueryResult<T> = Result<T, diesel::result::Error>;
pub type UpdateResult = QueryResult<Update>;
/// Sets up a connection pool and returns it. /// Sets up a connection pool and returns it.
/// Uses the DATABASE_URL environment variable (must be set) /// Uses the DATABASE_URL environment variable (must be set)
@@ -41,7 +42,6 @@ pub fn create_pool() -> Pool {
.expect("Failed to create pool.") .expect("Failed to create pool.")
} }
/// Every possible update which can happen during a query /// Every possible update which can happen during a query
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub enum Update { pub enum Update {
@@ -54,24 +54,25 @@ pub enum Update {
impl Update { impl Update {
/// Change back what has been updated /// Change back what has been updated
fn undo(&self, conn: &DbConnection, id: i32) -> QueryResult<Self> { fn undo(&self, conn: &DbConnection, id: i32) -> UpdateResult {
Ok(match self { Ok(match self {
Update::Wealth(diff) => { Update::Wealth(diff) => AsPlayer(conn, id).update_wealth(-diff.to_gp())?,
Update::Wealth(AsPlayer(conn, id).update_wealth(-diff.to_gp())?) Update::ItemAdded(item) => LootManager(conn, id).find(item.id)?.remove(conn)?,
}, Update::ItemRemoved(item) => LootManager(conn, id).add_from(&item)?,
Update::ItemAdded(item) => {
Update::ItemRemoved(LootManager(conn, id).remove(item.id)?)
},
Update::ItemRemoved(item) => {
Update::ItemAdded(LootManager(conn, id).add_from(&item)?)
},
// Unused for now // Unused for now
Update::ClaimAdded(claim) => { Update::ClaimRemoved(*claim) }, Update::ClaimAdded(claim) => Update::ClaimRemoved(*claim),
Update::ClaimRemoved(claim) => { Update::ClaimAdded(*claim) }, Update::ClaimRemoved(claim) => Update::ClaimAdded(*claim),
}) })
} }
} }
/// TODO: use this to wrap update in UpdateResult, allowing unified interface
/// whether a query makes multiple updates or just one.
enum OneOrMore {
One(Update),
More(Vec<Update>),
}
/// Every value which can be queried /// Every value which can be queried
#[derive(Debug)] #[derive(Debug)]
pub enum Value { pub enum Value {
@@ -107,17 +108,19 @@ pub fn sell_item_transaction(
id: i32, id: i32,
loot_id: i32, loot_id: i32,
price_mod: Option<f64>, price_mod: Option<f64>,
) -> QueryResult<(Item, Wealth)> { ) -> QueryResult<(Update, Wealth)> {
conn.transaction(|| { conn.transaction(|| {
let deleted = LootManager(conn, id) let to_delete = LootManager(conn, id).find(loot_id)?;
.remove(loot_id)?; let mut sell_value = to_delete.sell_value() as f64;
let mut sell_value = deleted.sell_value() as f64;
if let Some(modifier) = price_mod { if let Some(modifier) = price_mod {
sell_value *= modifier; sell_value *= modifier;
} }
let wealth = AsPlayer(conn, id) let deleted = to_delete.remove(conn)?;
.update_wealth(sell_value)?; if let Update::Wealth(wealth) = AsPlayer(conn, id).update_wealth(sell_value)? {
Ok((deleted, wealth)) Ok((deleted, wealth))
} else {
Err(diesel::result::Error::RollbackTransaction)
}
}) })
} }
@@ -131,7 +134,7 @@ pub fn buy_item_from_inventory(
id: i32, id: i32,
item_id: i32, item_id: i32,
price_mod: Option<f64>, price_mod: Option<f64>,
) -> QueryResult<(Item, Wealth)> { ) -> QueryResult<(Update, Wealth)> {
conn.transaction(|| { conn.transaction(|| {
// Find item in inventory // Find item in inventory
let item = Inventory(conn).find(item_id)?; let item = Inventory(conn).find(item_id)?;
@@ -140,9 +143,11 @@ pub fn buy_item_from_inventory(
Some(modifier) => item.value() as f64 * modifier, Some(modifier) => item.value() as f64 * modifier,
None => item.value() as f64, None => item.value() as f64,
}; };
AsPlayer(conn, id) if let Update::Wealth(diff) = AsPlayer(conn, id).update_wealth(-sell_price)? {
.update_wealth(-sell_price) Ok((new_item, diff))
.map(|diff| (new_item, diff)) } else {
Err(diesel::result::Error::RollbackTransaction)
}
}) })
} }
@@ -166,30 +171,34 @@ pub fn resolve_claims(conn: &DbConnection) -> QueryResult<()> {
let winner = claims.get(0).expect("Claims should not be empty !"); let winner = claims.get(0).expect("Claims should not be empty !");
let player_id = winner.player_id; let player_id = winner.player_id;
winner.resolve_claim(conn)?; winner.resolve_claim(conn)?;
models::player::AsPlayer(conn, player_id) models::player::AsPlayer(conn, player_id).update_debt(item.sell_value())?;
.update_debt(item.sell_value())?;
} }
Ok(()) Ok(())
}) })
} }
/// Split up and share group money among selected players /// Split up and share group money among selected players
pub fn split_and_share(conn: &DbConnection, amount: i32, players: Vec<i32>) -> QueryResult<Wealth> { pub fn split_and_share(
conn: &DbConnection,
amount: i32,
players: &Vec<i32>,
) -> QueryResult<Wealth> {
let share = ( let share = (
amount / (players.len() + 1) as i32 // +1 share for the group amount / (players.len() + 1) as i32
// +1 share for the group
) as f64; ) as f64;
conn.transaction(|| { conn.transaction(|| {
for p in players.into_iter() { for p in players {
let player = Players(conn).find(p)?; let player = Players(conn).find(*p)?;
// Take debt into account // Take debt into account
match share - player.debt as f64 { match share - player.debt as f64 {
rest if rest > 0.0 => { rest if rest > 0.0 => {
AsPlayer(conn, p).update_debt(-player.debt)?; AsPlayer(conn, *p).update_debt(-player.debt)?;
AsPlayer(conn, p).update_wealth(rest)?; AsPlayer(conn, *p).update_wealth(rest)?;
AsPlayer(conn, 0).update_wealth(-rest)?; AsPlayer(conn, 0).update_wealth(-rest)?;
}, }
_ => { _ => {
AsPlayer(conn, p).update_debt(-share as i32)?; AsPlayer(conn, *p).update_debt(-share as i32)?;
} }
} }
} }

View File

@@ -3,7 +3,7 @@ use diesel::expression::exists::Exists;
use diesel::prelude::*; use diesel::prelude::*;
use crate::schema::{items, looted}; use crate::schema::{items, looted};
use crate::{DbConnection, QueryResult}; use crate::{DbConnection, QueryResult, Update, UpdateResult };
type ItemColumns = (looted::id, looted::name, looted::base_price); type ItemColumns = (looted::id, looted::name, looted::base_price);
const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price); const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price);
type OwnedBy = Select<OwnedLoot, ItemColumns>; type OwnedBy = Select<OwnedLoot, ItemColumns>;
@@ -27,6 +27,11 @@ impl Item {
self.base_price / 2 self.base_price / 2
} }
pub fn remove(self, conn: &DbConnection) -> UpdateResult {
diesel::delete(looted::table.find(self.id)).execute(conn)?;
Ok(Update::ItemRemoved(self))
}
fn owned_by(player: i32) -> OwnedBy { fn owned_by(player: i32) -> OwnedBy {
Loot::owned_by(player).select(ITEM_COLUMNS) Loot::owned_by(player).select(ITEM_COLUMNS)
} }
@@ -124,7 +129,7 @@ impl<'q> LootManager<'q> {
.first(self.0)?) .first(self.0)?)
} }
pub(crate) fn add<S: Into<String>>(self, name: S, base_price: i32) -> QueryResult<Item> { pub(crate) fn add<S: Into<String>>(self, name: S, base_price: i32) -> UpdateResult {
self.add_from(&Item { self.add_from(&Item {
id: 0, id: 0,
name: name.into(), name: name.into(),
@@ -133,7 +138,7 @@ impl<'q> LootManager<'q> {
} }
/// Adds a copy of the given item inside player chest /// Adds a copy of the given item inside player chest
pub fn add_from(self, item: &Item) -> QueryResult<Item> { pub fn add_from(self, item: &Item) -> UpdateResult {
let new_item = NewLoot { let new_item = NewLoot {
name: &item.name, name: &item.name,
base_price: item.base_price, base_price: item.base_price,
@@ -142,13 +147,7 @@ impl<'q> LootManager<'q> {
diesel::insert_into(looted::table) diesel::insert_into(looted::table)
.values(&new_item) .values(&new_item)
.execute(self.0)?; .execute(self.0)?;
self.last() Ok(Update::ItemAdded(self.last()?))
}
pub fn remove(self, item_id: i32) -> QueryResult<Item> {
let deleted = self.find(item_id)?;
diesel::delete(looted::table.find(deleted.id)).execute(self.0)?;
Ok(deleted)
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::schema::players; use crate::schema::players;
use crate::{DbConnection, QueryResult}; use crate::{DbConnection, QueryResult, Update, UpdateResult};
use diesel::prelude::*; use diesel::prelude::*;
mod notification; mod notification;
@@ -50,10 +50,7 @@ impl<'q> Players<'q> {
/// Notify all players of an event /// Notify all players of an event
pub fn notifiy_all(&self, text: &str) -> QueryResult<()> { pub fn notifiy_all(&self, text: &str) -> QueryResult<()> {
for id in self.all()? for id in self.all()?.into_iter().map(|p| p.id) {
.into_iter()
.map(|p| p.id)
{
self.notify(id, text); self.notify(id, text);
} }
Ok(()) Ok(())
@@ -75,7 +72,7 @@ impl<'q> AsPlayer<'q> {
notification::pop_all_for(self.1, self.0) notification::pop_all_for(self.1, self.0)
} }
/// Updates this player's wealth, returning the difference /// Updates this player's wealth, returning the difference
pub fn update_wealth(&self, value_in_gp: f64) -> QueryResult<Wealth> { pub fn update_wealth(&self, value_in_gp: f64) -> UpdateResult {
use crate::schema::players::dsl::*; use crate::schema::players::dsl::*;
let current_wealth = players let current_wealth = players
.find(self.1) .find(self.1)
@@ -86,7 +83,7 @@ impl<'q> AsPlayer<'q> {
.filter(id.eq(self.1)) .filter(id.eq(self.1))
.set(&updated_wealth) .set(&updated_wealth)
.execute(self.0)?; .execute(self.0)?;
Ok(updated_wealth - current_wealth) Ok(Update::Wealth(updated_wealth - current_wealth))
} }
/// Updates this player's debt /// Updates this player's debt

View File

@@ -1,6 +1,7 @@
<template> <template>
<PlayerView <PlayerView
:id="player_id" :id="player_id"
:players="playersId"
v-slot="{ player, loot, notifications, actions, claims }" v-slot="{ player, loot, notifications, actions, claims }"
> >
<main id="app" class="container"> <main id="app" class="container">
@@ -142,7 +143,7 @@ export default {
api.fetch("items", "GET", null), api.fetch("items", "GET", null),
]) ])
.then(([players, loot, items]) => { .then(([players, loot, items]) => {
this.playerList = players.value; this.$set(this, 'playerList', players.value);
this.groupLoot = loot.value; this.groupLoot = loot.value;
this.itemsInventory = items.value; this.itemsInventory = items.value;
}) })
@@ -177,6 +178,7 @@ export default {
showPlayerChest () { return this.activeView == 'player' }, showPlayerChest () { return this.activeView == 'player' },
isAdding () { return this.activeView == 'adding' }, isAdding () { return this.activeView == 'adding' },
playerIsGroup () { return this.player_id == 0 }, playerIsGroup () { return this.player_id == 0 },
playersId () { return this.playerList.map(p => p.id).filter(i => i != 0); }
} }
} }
</script> </script>

View File

@@ -1,7 +1,7 @@
import { api } from '../lootalot.js' import { api } from '../lootalot.js'
export default { export default {
props: ["id"], props: ["id", "players"],
data () { return { data () { return {
player: { player: {
name: "Loading", name: "Loading",
@@ -76,7 +76,15 @@ export default {
putClaim (itemId) { this.call("claims", "PUT", itemId) }, putClaim (itemId) { this.call("claims", "PUT", itemId) },
withdrawClaim (itemId) { this.call("claims", "DELETE", itemId) }, withdrawClaim (itemId) { this.call("claims", "DELETE", itemId) },
buyItems(items) { this.call("loot", "PUT", items) }, buyItems(items) { this.call("loot", "PUT", items) },
sellItems (items) { this.call("loot", "DELETE", { items, global_mod: null, players: null }) }, sellItems (items) {
let players;
if (this.player.id == 0) {
players = this.players;
} else {
players = null;
}
this.call("loot", "DELETE", { items, global_mod: null, players })
},
undoLastAction () { this.call("events/last", "DELETE", null) }, undoLastAction () { this.call("events/last", "DELETE", null) },
}, },
watch: { watch: {

View File

@@ -113,9 +113,7 @@ pub fn execute(
None None
} }
ApiActions::UpdateWealth(id, amount) => { ApiActions::UpdateWealth(id, amount) => {
response.push_update(Update::Wealth( response.push_update(db::AsPlayer(conn, id).update_wealth(amount)?);
db::AsPlayer(conn, id).update_wealth(amount)?,
));
response.notify(format!("Mis à jour ({}po)!", amount)); response.notify(format!("Mis à jour ({}po)!", amount));
Some((id, "Argent mis à jour")) Some((id, "Argent mis à jour"))
} }
@@ -165,15 +163,17 @@ pub fn execute(
.fold(db::Wealth::from_gp(0.0), |acc, i| acc + i); .fold(db::Wealth::from_gp(0.0), |acc, i| acc + i);
match id { match id {
0 => { 0 => {
let players = params.players.expect("The player list should be passed in !");
let share = db::split_and_share( let share = db::split_and_share(
conn, conn,
total_amount.to_gp() as i32, total_amount.to_gp() as i32,
params.players.expect("Should not be None"), &players,
)?; )?;
response.notify(format!( response.notify(format!(
"Les objets ont été vendus, chaque joueur a reçu {} po", "Les objets ont été vendus, chaque joueur a reçu {} po",
share.to_gp() share.to_gp()
)); ));
//response.push_update(Update::GroupShare(players, share));
response.push_update(Update::Wealth(share)); response.push_update(Update::Wealth(share));
} }
_ => { _ => {