adds new 'shop' table
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
DROP TABLE items;
|
DROP TABLE items;
|
||||||
DROP TABLE looted;
|
DROP TABLE looted;
|
||||||
|
DROP TABLE shop;
|
||||||
|
|||||||
@@ -13,3 +13,10 @@ CREATE TABLE looted (
|
|||||||
owner_id INTEGER NOT NULL,
|
owner_id INTEGER NOT NULL,
|
||||||
FOREIGN KEY (owner_id) REFERENCES players(id)
|
FOREIGN KEY (owner_id) REFERENCES players(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- The items that are available in shop
|
||||||
|
CREATE TABLE shop (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
base_price INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ mod schema;
|
|||||||
pub use models::{
|
pub use models::{
|
||||||
claim::{Claim, Claims},
|
claim::{Claim, Claims},
|
||||||
history::{Event, UpdateList},
|
history::{Event, UpdateList},
|
||||||
item::{Inventory, Item, LootManager},
|
item::{Inventory, Shop, Item, LootManager},
|
||||||
player::{AsPlayer, Player, Players, Wealth},
|
player::{AsPlayer, Player, Players, Wealth},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -140,8 +140,8 @@ pub fn buy_item_from_inventory(
|
|||||||
let item = Inventory(conn).find(item_id)?;
|
let item = Inventory(conn).find(item_id)?;
|
||||||
let new_item = LootManager(conn, id).add_from(&item)?;
|
let new_item = LootManager(conn, id).add_from(&item)?;
|
||||||
let sell_price = match price_mod {
|
let sell_price = match price_mod {
|
||||||
Some(modifier) => item.value() as f64 * modifier,
|
Some(modifier) => item.value() * modifier,
|
||||||
None => item.value() as f64,
|
None => item.value(),
|
||||||
};
|
};
|
||||||
if let Update::Wealth(diff) = AsPlayer(conn, id).update_wealth(-sell_price)? {
|
if let Update::Wealth(diff) = AsPlayer(conn, id).update_wealth(-sell_price)? {
|
||||||
Ok((new_item, diff))
|
Ok((new_item, diff))
|
||||||
@@ -151,11 +151,31 @@ pub fn buy_item_from_inventory(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch all existing claims
|
pub fn buy_item_from_shop(
|
||||||
pub fn fetch_claims(conn: &DbConnection) -> QueryResult<Vec<models::Claim>> {
|
conn: &DbConnection,
|
||||||
schema::claims::table.load::<models::Claim>(conn)
|
id: i32,
|
||||||
|
item_id: i32,
|
||||||
|
price_mod: Option<f64>,
|
||||||
|
) -> QueryResult<(Update, Wealth)> {
|
||||||
|
conn.transaction(|| {
|
||||||
|
let shop = Shop(conn);
|
||||||
|
// Find item in inventory
|
||||||
|
let item = shop.get(item_id)?;
|
||||||
|
let new_item = LootManager(conn, id).add_from(&item)?;
|
||||||
|
let _deleted = shop.remove(item_id)?;
|
||||||
|
let sell_price = match price_mod {
|
||||||
|
Some(modifier) => item.value() * modifier,
|
||||||
|
None => item.value(),
|
||||||
|
};
|
||||||
|
if let Update::Wealth(diff) = AsPlayer(conn, id).update_wealth(-sell_price)? {
|
||||||
|
Ok((new_item, diff))
|
||||||
|
} else {
|
||||||
|
Err(diesel::result::Error::RollbackTransaction)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Resolve all pending claims and dispatch claimed items.
|
/// Resolve all pending claims and dispatch claimed items.
|
||||||
///
|
///
|
||||||
/// When a player gets an item, it's debt is increased by this item sell value
|
/// When a player gets an item, it's debt is increased by this item sell value
|
||||||
@@ -171,7 +191,7 @@ 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).update_debt(item.sell_value())?;
|
models::player::AsPlayer(conn, player_id).update_debt(item.sell_value() as i32)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ impl<'q> Claims<'q> {
|
|||||||
claims::table.load(self.0)
|
claims::table.load(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn by_player(&self, id: i32) -> QueryResult<Vec<Claim>> {
|
||||||
|
claims::table.filter(claims::dsl::player_id.eq(id))
|
||||||
|
.load(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Finds a single claim by association of player and loot ids.
|
/// Finds a single claim by association of player and loot ids.
|
||||||
pub fn find(&self, player_id: i32, loot_id: i32) -> QueryResult<Claim> {
|
pub fn find(&self, player_id: i32, loot_id: i32) -> QueryResult<Claim> {
|
||||||
claims::table
|
claims::table
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use diesel::dsl::{exists, Eq, Filter, Find, Select};
|
|||||||
use diesel::expression::exists::Exists;
|
use diesel::expression::exists::Exists;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::schema::{items, looted};
|
use crate::schema::{items, looted, shop};
|
||||||
use crate::{DbConnection, QueryResult, Update, UpdateResult, Claims };
|
use crate::{DbConnection, QueryResult, Update, UpdateResult, Claims };
|
||||||
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);
|
||||||
@@ -18,13 +18,13 @@ pub struct Item {
|
|||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
/// Returns this item value
|
/// Returns this item value
|
||||||
pub fn value(&self) -> i32 {
|
pub fn value(&self) -> f64 {
|
||||||
self.base_price
|
self.base_price as f64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this item sell value
|
/// Returns this item sell value
|
||||||
pub fn sell_value(&self) -> i32 {
|
pub fn sell_value(&self) -> f64 {
|
||||||
self.base_price / 2
|
self.base_price as f64 / 2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(self, conn: &DbConnection) -> UpdateResult {
|
pub fn remove(self, conn: &DbConnection) -> UpdateResult {
|
||||||
@@ -58,6 +58,47 @@ impl<'q> Inventory<'q> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Shop<'q>(pub &'q DbConnection);
|
||||||
|
|
||||||
|
impl<'q> Shop<'q> {
|
||||||
|
pub fn all(&self) -> QueryResult<Vec<Item>> {
|
||||||
|
shop::table.load(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: i32) -> QueryResult<Item> {
|
||||||
|
shop::table.find(&id).first::<Item>(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self, id: i32) -> QueryResult<()> {
|
||||||
|
diesel::delete(
|
||||||
|
shop::table.find(&id)
|
||||||
|
).execute(self.0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_list(&self, items: Vec<Item>) -> QueryResult<()> {
|
||||||
|
self.0.transaction(
|
||||||
|
|| -> QueryResult<()>
|
||||||
|
{
|
||||||
|
// Remove all content
|
||||||
|
diesel::delete(shop::table).execute(self.0)?;
|
||||||
|
// Adds new list
|
||||||
|
for item in &items {
|
||||||
|
let new_item = NewItem {
|
||||||
|
name: &item.name,
|
||||||
|
base_price: item.base_price,
|
||||||
|
};
|
||||||
|
diesel::insert_into(shop::table)
|
||||||
|
.values(&new_item)
|
||||||
|
.execute(self.0)?;
|
||||||
|
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type WithOwner = Eq<looted::owner_id, i32>;
|
type WithOwner = Eq<looted::owner_id, i32>;
|
||||||
type OwnedLoot = Filter<looted::table, WithOwner>;
|
type OwnedLoot = Filter<looted::table, WithOwner>;
|
||||||
|
|
||||||
@@ -169,3 +210,10 @@ struct NewLoot<'a> {
|
|||||||
base_price: i32,
|
base_price: i32,
|
||||||
owner_id: i32,
|
owner_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[table_name = "shop"]
|
||||||
|
struct NewItem<'a> {
|
||||||
|
name: &'a str,
|
||||||
|
base_price: i32,
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
shop (id) {
|
||||||
|
id -> Integer,
|
||||||
|
name -> Text,
|
||||||
|
base_price -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
joinable!(claims -> looted (loot_id));
|
joinable!(claims -> looted (loot_id));
|
||||||
joinable!(claims -> players (player_id));
|
joinable!(claims -> players (player_id));
|
||||||
joinable!(history -> players (player_id));
|
joinable!(history -> players (player_id));
|
||||||
@@ -67,4 +75,5 @@ allow_tables_to_appear_in_same_query!(
|
|||||||
looted,
|
looted,
|
||||||
notifications,
|
notifications,
|
||||||
players,
|
players,
|
||||||
|
shop,
|
||||||
);
|
);
|
||||||
|
|||||||
59
src/api.rs
59
src/api.rs
@@ -13,14 +13,12 @@ pub struct BuySellParams {
|
|||||||
global_mod: Option<f64>,
|
global_mod: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct NewGroupLoot {
|
pub struct NewGroupLoot {
|
||||||
source_name: String,
|
source_name: String,
|
||||||
pub items: ItemList,
|
pub items: ItemList,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A generic response for all queries
|
/// A generic response for all queries
|
||||||
#[derive(Serialize, Debug, Default)]
|
#[derive(Serialize, Debug, Default)]
|
||||||
pub struct ApiResponse {
|
pub struct ApiResponse {
|
||||||
@@ -70,10 +68,12 @@ pub enum ApiActions {
|
|||||||
// Application level
|
// Application level
|
||||||
FetchPlayers,
|
FetchPlayers,
|
||||||
FetchInventory,
|
FetchInventory,
|
||||||
|
FetchShopInventory,
|
||||||
FetchClaims,
|
FetchClaims,
|
||||||
CheckItemList(Vec<String>),
|
CheckItemList(Vec<String>),
|
||||||
// Player level
|
// Player level
|
||||||
FetchPlayer(i32),
|
FetchPlayer(i32),
|
||||||
|
FetchPlayerClaims(i32),
|
||||||
FetchNotifications(i32),
|
FetchNotifications(i32),
|
||||||
FetchLoot(i32),
|
FetchLoot(i32),
|
||||||
UpdateWealth(i32, f64),
|
UpdateWealth(i32, f64),
|
||||||
@@ -86,6 +86,7 @@ pub enum ApiActions {
|
|||||||
// Group level
|
// Group level
|
||||||
AddLoot(NewGroupLoot),
|
AddLoot(NewGroupLoot),
|
||||||
// Admin level
|
// Admin level
|
||||||
|
RefreshShopInventory(ItemList),
|
||||||
//AddPlayer(String, f64),
|
//AddPlayer(String, f64),
|
||||||
//AddInventoryItem(pub String, pub i32),
|
//AddInventoryItem(pub String, pub i32),
|
||||||
//ResolveClaims,
|
//ResolveClaims,
|
||||||
@@ -106,9 +107,7 @@ pub fn execute(
|
|||||||
let mut errors = String::new();
|
let mut errors = String::new();
|
||||||
let items = db::Inventory(conn).all()?;
|
let items = db::Inventory(conn).all()?;
|
||||||
for name in &names {
|
for name in &names {
|
||||||
if let Some(item) =
|
if let Some(item) = items.iter().filter(|i| &i.name == name).take(1).next() {
|
||||||
items.iter().filter(|i| &i.name == name).take(1).next()
|
|
||||||
{
|
|
||||||
found.push(item.clone())
|
found.push(item.clone())
|
||||||
} else {
|
} else {
|
||||||
errors.push_str(&format!("{},\n", name));
|
errors.push_str(&format!("{},\n", name));
|
||||||
@@ -130,14 +129,22 @@ pub fn execute(
|
|||||||
response.set_value(Value::ItemList(db::Inventory(conn).all()?));
|
response.set_value(Value::ItemList(db::Inventory(conn).all()?));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
ApiActions::FetchShopInventory => {
|
||||||
|
response.set_value(Value::ItemList(db::Shop(conn).all()?));
|
||||||
|
None
|
||||||
|
}
|
||||||
ApiActions::FetchClaims => {
|
ApiActions::FetchClaims => {
|
||||||
response.set_value(Value::ClaimList(db::fetch_claims(conn)?));
|
response.set_value(Value::ClaimList(db::Claims(conn).all()?));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
ApiActions::FetchPlayer(id) => {
|
ApiActions::FetchPlayer(id) => {
|
||||||
response.set_value(Value::Player(db::Players(conn).find(id)?));
|
response.set_value(Value::Player(db::Players(conn).find(id)?));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
ApiActions::FetchPlayerClaims(id) => {
|
||||||
|
response.set_value(Value::ClaimList(db::Claims(conn).by_player(id)?));
|
||||||
|
None
|
||||||
|
}
|
||||||
ApiActions::FetchNotifications(id) => {
|
ApiActions::FetchNotifications(id) => {
|
||||||
response.set_value(Value::Notifications(
|
response.set_value(Value::Notifications(
|
||||||
db::AsPlayer(conn, id).notifications()?,
|
db::AsPlayer(conn, id).notifications()?,
|
||||||
@@ -155,9 +162,13 @@ pub fn execute(
|
|||||||
}
|
}
|
||||||
ApiActions::BuyItems(id, params) => {
|
ApiActions::BuyItems(id, params) => {
|
||||||
// TODO: check that player has enough money !
|
// TODO: check that player has enough money !
|
||||||
|
let has_enough_gold = true;
|
||||||
|
|
||||||
|
if has_enough_gold {
|
||||||
let mut gains: Vec<db::Wealth> = Vec::with_capacity(params.items.len());
|
let mut gains: Vec<db::Wealth> = Vec::with_capacity(params.items.len());
|
||||||
for (item_id, price_mod) in params.items.into_iter() {
|
for (item_id, price_mod) in params.items.into_iter() {
|
||||||
if let Ok((item, diff)) = db::buy_item_from_inventory(conn, id, item_id, price_mod)
|
if let Ok((item, diff)) =
|
||||||
|
db::buy_item_from_shop(conn, id, item_id, price_mod)
|
||||||
{
|
{
|
||||||
response.push_update(item);
|
response.push_update(item);
|
||||||
gains.push(diff);
|
gains.push(diff);
|
||||||
@@ -176,6 +187,10 @@ pub fn execute(
|
|||||||
));
|
));
|
||||||
response.push_update(Update::Wealth(total_amount));
|
response.push_update(Update::Wealth(total_amount));
|
||||||
Some((id, "Achat d'objets"))
|
Some((id, "Achat d'objets"))
|
||||||
|
} else {
|
||||||
|
response.push_error("Vous n'avez pas assez d'argent !");
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Behavior differs if player is group or regular.
|
// Behavior differs if player is group or regular.
|
||||||
// Group sells item like players then split the total amount among players.
|
// Group sells item like players then split the total amount among players.
|
||||||
@@ -200,20 +215,15 @@ pub fn execute(
|
|||||||
let players = params
|
let players = params
|
||||||
.players
|
.players
|
||||||
.unwrap_or(db::Players(conn).all()?.into_iter().map(|p| p.id).collect());
|
.unwrap_or(db::Players(conn).all()?.into_iter().map(|p| p.id).collect());
|
||||||
let shared = db::split_and_share(conn, total_amount.to_gp() as i32, &players)?;
|
if let Update::Wealth(shared) =
|
||||||
let shared_amount = {
|
db::split_and_share(conn, total_amount.to_gp() as i32, &players)?
|
||||||
if let Update::Wealth(amount) = shared {
|
{
|
||||||
amount.to_gp()
|
|
||||||
} else {
|
|
||||||
panic!("cannot happen")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
response.notify(format!(
|
response.notify(format!(
|
||||||
"Les objets ont été vendus, les joueurs ont reçu (au total) {} po",
|
"Les objets ont été vendus, les joueurs ont reçu (au total) {} po",
|
||||||
shared_amount
|
shared.to_gp()
|
||||||
));
|
));
|
||||||
response.push_update(Update::Wealth(total_amount));
|
response.push_update(Update::Wealth(db::Wealth::from_gp(total_amount.to_gp() - shared.to_gp())));
|
||||||
response.push_update(shared);
|
};
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
response.notify(format!(
|
response.notify(format!(
|
||||||
@@ -238,8 +248,12 @@ pub fn execute(
|
|||||||
}
|
}
|
||||||
ApiActions::ClaimItems(id, items) => {
|
ApiActions::ClaimItems(id, items) => {
|
||||||
conn.transaction(|| -> Result<Option<(i32, &str)>, diesel::result::Error> {
|
conn.transaction(|| -> Result<Option<(i32, &str)>, diesel::result::Error> {
|
||||||
let current_claims: HashSet<i32> =
|
let current_claims: HashSet<i32> = db::Claims(conn)
|
||||||
db::Claims(conn).all()?.iter().filter(|c| c.player_id == id).map(|c| c.loot_id).collect();
|
.all()?
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.player_id == id)
|
||||||
|
.map(|c| c.loot_id)
|
||||||
|
.collect();
|
||||||
let new_claims: HashSet<i32> = items.into_iter().collect();
|
let new_claims: HashSet<i32> = items.into_iter().collect();
|
||||||
// Claims to delete
|
// Claims to delete
|
||||||
for item in current_claims.difference(&new_claims) {
|
for item in current_claims.difference(&new_claims) {
|
||||||
@@ -284,6 +298,11 @@ pub fn execute(
|
|||||||
};
|
};
|
||||||
Some((0, "Nouveau loot"))
|
Some((0, "Nouveau loot"))
|
||||||
}
|
}
|
||||||
|
// Admin actions
|
||||||
|
ApiActions::RefreshShopInventory(items) => {
|
||||||
|
db::Shop(conn).replace_list(items)?;
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store the event if it can be undone.
|
// Store the event if it can be undone.
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ fn configure_api(config: &mut web::ServiceConfig) {
|
|||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/claims")
|
web::resource("/claims")
|
||||||
//.route(web::get().to_async(endpoints::player_claims))
|
.route(web::get().to_async(|pool, player: PlayerId| {
|
||||||
|
db_call(pool, Q::FetchPlayerClaims(*player))
|
||||||
|
}))
|
||||||
.route(web::post().to_async(
|
.route(web::post().to_async(
|
||||||
|pool, (player, data): (PlayerId, IdList)| {
|
|pool, (player, data): (PlayerId, IdList)| {
|
||||||
db_call(pool, Q::ClaimItems(*player, data.into_inner()))
|
db_call(pool, Q::ClaimItems(*player, data.into_inner()))
|
||||||
@@ -188,6 +190,15 @@ fn configure_api(config: &mut web::ServiceConfig) {
|
|||||||
"/claims",
|
"/claims",
|
||||||
web::get().to_async(|pool| db_call(pool, Q::FetchClaims)),
|
web::get().to_async(|pool| db_call(pool, Q::FetchClaims)),
|
||||||
)
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/shop")
|
||||||
|
.route(web::get().to_async(|pool| db_call(pool, Q::FetchShopInventory)))
|
||||||
|
.route(
|
||||||
|
web::post().to_async(|pool, items: web::Json<api::ItemList>| {
|
||||||
|
db_call(pool, Q::RefreshShopInventory(items.into_inner()))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/items")
|
web::resource("/items")
|
||||||
.route(
|
.route(
|
||||||
|
|||||||
Reference in New Issue
Block a user