diff --git a/lootalot_db/migrations/2019-06-10-123922_create_items/down.sql b/lootalot_db/migrations/2019-06-10-123922_create_items/down.sql index f7fc944..63b3efc 100644 --- a/lootalot_db/migrations/2019-06-10-123922_create_items/down.sql +++ b/lootalot_db/migrations/2019-06-10-123922_create_items/down.sql @@ -1,3 +1,2 @@ DROP TABLE items; -DROP TABLE looted; -DROP TABLE shop; +DROP TABLE loot; diff --git a/lootalot_db/migrations/2019-06-10-123922_create_items/up.sql b/lootalot_db/migrations/2019-06-10-123922_create_items/up.sql index 3809eff..0dd3ce5 100644 --- a/lootalot_db/migrations/2019-06-10-123922_create_items/up.sql +++ b/lootalot_db/migrations/2019-06-10-123922_create_items/up.sql @@ -5,8 +5,8 @@ CREATE TABLE items ( base_price INTEGER NOT NULL ); --- The items that have been looted -CREATE TABLE looted ( +-- The loot +CREATE TABLE loot ( id INTEGER PRIMARY KEY NOT NULL, name VARCHAR NOT NULL, base_price INTEGER NOT NULL, @@ -14,9 +14,3 @@ CREATE TABLE looted ( 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 -); diff --git a/lootalot_db/src/lib.rs b/lootalot_db/src/lib.rs index 484062d..91403d1 100644 --- a/lootalot_db/src/lib.rs +++ b/lootalot_db/src/lib.rs @@ -29,7 +29,7 @@ pub type DbConnection = SqliteConnection; pub type Pool = r2d2::Pool>; /// The result of a query on DB pub type QueryResult = Result; -pub type UpdateResult = QueryResult; +pub type UpdateResult = QueryResult>; /// Sets up a connection pool and returns it. /// Uses the DATABASE_URL environment variable (must be set) @@ -60,8 +60,8 @@ impl Update { Update::ItemAdded(item) => LootManager(conn, id).find(item.id)?.remove(conn)?, Update::ItemRemoved(item) => LootManager(conn, id).add_from(&item)?, // Unused for now - Update::ClaimAdded(claim) => Update::ClaimRemoved(*claim), - Update::ClaimRemoved(claim) => Update::ClaimAdded(*claim), + Update::ClaimAdded(claim) => vec!(Update::ClaimRemoved(*claim)), + Update::ClaimRemoved(claim) => vec!(Update::ClaimAdded(*claim)), }) } } @@ -99,82 +99,6 @@ impl serde::Serialize for Value { } } -/// Sells a single item inside a transaction -/// -/// # Returns -/// The deleted entity and the updated Wealth (as a difference from previous value) -pub fn sell_item_transaction( - conn: &DbConnection, - id: i32, - loot_id: i32, - price_mod: Option, -) -> QueryResult<(Update, Wealth)> { - conn.transaction(|| { - let to_delete = LootManager(conn, id).find(loot_id)?; - let mut sell_value = to_delete.sell_value() as f64; - if let Some(modifier) = price_mod { - sell_value *= modifier; - } - let deleted = to_delete.remove(conn)?; - if let Update::Wealth(wealth) = AsPlayer(conn, id).update_wealth(sell_value)? { - Ok((deleted, wealth)) - } else { - Err(diesel::result::Error::RollbackTransaction) - } - }) -} - -/// Buys a single item, copied from inventory. -/// Runs inside a transaction -/// -/// # Returns -/// The created entity and the updated Wealth (as a difference from previous value) -pub fn buy_item_from_inventory( - conn: &DbConnection, - id: i32, - item_id: i32, - price_mod: Option, -) -> QueryResult<(Update, Wealth)> { - conn.transaction(|| { - // Find item in inventory - let item = Inventory(conn).find(item_id)?; - let new_item = LootManager(conn, id).add_from(&item)?; - 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) - } - }) -} - -pub fn buy_item_from_shop( - conn: &DbConnection, - id: i32, - item_id: i32, - price_mod: Option, -) -> 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. /// @@ -239,7 +163,7 @@ pub fn split_and_share( } } } - Ok(Update::Wealth(Wealth::from_gp(shared_total))) + Ok(vec!(Update::Wealth(Wealth::from_gp(shared_total)))) }) } diff --git a/lootalot_db/src/models/claim.rs b/lootalot_db/src/models/claim.rs index 91cc0a4..0d9b591 100644 --- a/lootalot_db/src/models/claim.rs +++ b/lootalot_db/src/models/claim.rs @@ -73,22 +73,22 @@ impl<'q> Claims<'q> { .values(&claim) .execute(self.0)?; // Return the created claim - Ok( + Ok(vec!( Update::ClaimAdded( claims::table .order(claims::dsl::id.desc()) .first::(self.0)? ) - ) + )) } /// Removes a claim from database, returning it pub fn remove(self, player_id: i32, loot_id: i32) -> UpdateResult { let claim = self.find(player_id, loot_id)?; claim.remove(self.0)?; - Ok( + Ok(vec!( Update::ClaimRemoved(claim) - ) + )) } pub fn filtered_by_loot(&self, loot_id: i32) -> QueryResult> { diff --git a/lootalot_db/src/models/item.rs b/lootalot_db/src/models/item.rs index 0e53614..b1b9099 100644 --- a/lootalot_db/src/models/item.rs +++ b/lootalot_db/src/models/item.rs @@ -2,10 +2,10 @@ use diesel::dsl::{exists, Eq, Filter, Find, Select}; use diesel::expression::exists::Exists; use diesel::prelude::*; -use crate::schema::{items, looted, shop}; +use crate::schema::{items, loot}; use crate::{DbConnection, QueryResult, Update, UpdateResult, Claims }; -type ItemColumns = (looted::id, looted::name, looted::base_price); -const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price); +type ItemColumns = (loot::id, loot::name, loot::base_price); +const ITEM_COLUMNS: ItemColumns = (loot::id, loot::name, loot::base_price); type OwnedBy = Select; /// Represents a basic item @@ -27,57 +27,46 @@ impl Item { self.base_price as f64 / 2.0 } - pub fn remove(self, conn: &DbConnection) -> UpdateResult { - conn.transaction( - || -> UpdateResult - { - Claims(conn).delete_for_loot(self.id)?; - diesel::delete(looted::table.find(self.id)).execute(conn)?; - - Ok(Update::ItemRemoved(self)) - } - ) - } - fn owned_by(player: i32) -> OwnedBy { Loot::owned_by(player).select(ITEM_COLUMNS) } } -const OF_GROUP = 0; -const OF_SHOP = -1; -const SOLD = -2; +// Owner status +const OF_GROUP: i32 = 0; +const OF_SHOP: i32 = -1; +const SOLD: i32 = -2; -type WithOwner = Eq; -type OwnedLoot = Filter; +type WithOwner = Eq; +type OwnedLoot = Filter; /// An owned item /// /// The owner is a Player, the Group, the Merchant /// OR the SOLD state. #[derive(Identifiable, Debug, Queryable)] -#[table_name = "looted"] +#[table_name = "loot"] pub(super) struct Loot { id: i32, name: String, base_price: i32, - owner: i32, + owner_id: i32, } impl Loot { /// A filter on Loot that is owned by given player pub(super) fn owned_by(id: i32) -> OwnedLoot { - looted::table.filter(looted::owner_id.eq(id)) + loot::table.filter(loot::owner_id.eq(id)) } - fn exists(id: i32) -> Exists> { - exists(looted::table.find(id)) + fn exists(id: i32) -> Exists> { + exists(loot::table.find(id)) } pub(super) fn set_owner(&self, owner: i32, conn: &DbConnection) -> QueryResult<()> { - diesel::update(looted::table.find(self.id)) - .set(looted::dsl::owner_id.eq(owner)) + diesel::update(loot::table.find(self.id)) + .set(loot::dsl::owner_id.eq(owner)) .execute(conn)?; Ok(()) } @@ -90,8 +79,8 @@ impl Loot { } } - pub(super) fn find(id: i32) -> Find { - looted::table.find(id) + pub(super) fn find(id: i32) -> Find { + loot::table.find(id) } } @@ -107,6 +96,28 @@ impl<'q> Inventory<'q> { pub fn find(&self, item_id: i32) -> QueryResult { items::table.find(item_id).first::(self.0) } + + pub fn find_by_name(&self, item_name: &str) -> QueryResult { + Ok(items::table.filter(items::dsl::name.like(item_name)).first(self.0)?) + } + + /// Check the inventory against a list of item names + /// + /// # Returns + /// + /// A tuple of found items and errors + pub fn check_list(&self, item_names: Vec) -> QueryResult<(Vec, String)> { + let all_items = self.all()?; + let mut found = Vec::new(); + let mut errors = String::new(); + for name in &item_names { + match self.find_by_name(name) { + Ok(item) => found.push(item), + Err(_) => errors.push_str(&format!("{},\n", name)), + } + } + Ok((found, errors)) + } } /// The shop resource @@ -115,39 +126,62 @@ pub struct Shop<'q>(pub &'q DbConnection); impl<'q> Shop<'q> { // Rename to list pub fn all(&self) -> QueryResult> { - // Loot::owned_by(OF_SHOP).load(self.0) - shop::table.load(self.0) + Item::owned_by(OF_SHOP).load(self.0) } - // pub fn buy(&self, items: Vec<(i32, f64)>, buyer: i32) -> UpdateResult {} - - pub fn get(&self, id: i32) -> QueryResult { - shop::table.find(&id).first::(self.0) + fn buy_single(&self, buyer: i32, item_id: i32, price_mod: Option) -> UpdateResult { + use crate::AsPlayer; + let item = self.get(item_id)?; + let sell_price = match price_mod { + Some(modifier) => item.base_price as f64 * modifier, + None => item.base_price as f64, + }; + self.0.transaction(|| { + let mut updates = AsPlayer(self.0, buyer).update_wealth(-sell_price)?; + item.set_owner(buyer, self.0)?; + updates.push(Update::ItemAdded(item.into_item())); + Ok(updates) + }) } - pub fn remove(&self, id: i32) -> QueryResult<()> { - diesel::delete( - shop::table.find(&id) - ).execute(self.0)?; - Ok(()) + pub fn buy(&self, items: Vec<(i32, Option)>, buyer: i32) -> UpdateResult { + // TODO: check that player has enough money ! + let has_enough_gold = true; + + if has_enough_gold { + let mut updates = Vec::new(); + for (item_id, price_mod) in items.into_iter() { + updates.append( + &mut self.buy_single(buyer, item_id, price_mod)? + ) + } + Ok(updates) + } else { + unimplemented!(); + } + } + + fn get(&self, id: i32) -> QueryResult { + Loot::owned_by(OF_SHOP).find(&id).first::(self.0) } pub fn replace_list(&self, items: Vec) -> QueryResult<()> { + use self::loot::dsl::*; self.0.transaction( || -> QueryResult<()> { // Remove all content - diesel::delete(shop::table).execute(self.0)?; + diesel::delete(Loot::owned_by(OF_SHOP)).execute(self.0)?; // Adds new list for item in &items { - let new_item = NewItem { + let new_item = NewLoot { name: &item.name, base_price: item.base_price, + owner_id: OF_SHOP, }; - diesel::insert_into(shop::table) + diesel::insert_into(loot) .values(&new_item) .execute(self.0)?; - } Ok(()) } @@ -164,28 +198,50 @@ impl<'q> LootManager<'q> { Ok(Item::owned_by(self.1).load(self.0)?) } + fn get(&self, item_id: i32) -> QueryResult { + Ok(Loot::owned_by(self.1).find(item_id).first(self.0)?) + } + /// Finds an item by id /// /// Returns a NotFound error if an item is found by it /// does not belong to this player pub fn find(&self, loot_id: i32) -> QueryResult { - Ok(Loot::find(loot_id).first(self.0).and_then(|loot: Loot| { - if loot.owner != self.1 { - Err(diesel::result::Error::NotFound) - } else { - Ok(Item { - id: loot.id, - name: loot.name, - base_price: loot.base_price, - }) - } - })?) + Ok(self.get(loot_id)?.into_item()) } + fn sell_single(&self, loot_id: i32, price_mod: Option) -> UpdateResult { + let to_sell = self.get(loot_id)?; + let mut sell_value = to_sell.base_price as f64 / 2.0; + if let Some(modifier) = price_mod { + sell_value *= modifier; + } + self.0.transaction(|| { + let mut updates = + crate::AsPlayer(self.0, self.1) + .update_wealth(sell_value)?; + to_sell.set_owner(SOLD, self.0)?; + updates.push(Update::ItemRemoved(to_sell.into_item())); + Ok(updates) + }) + } + + pub fn sell(&self, items: Vec<(i32, Option)>) -> UpdateResult { + self.0.transaction(|| { + let mut updates = Vec::new(); + for (loot_id, price_mod) in items.into_iter() { + updates.append(&mut self.sell_single(loot_id, price_mod)?); + } + Ok(updates) + }) + } + + + /// The last item added to the chest pub fn last(&self) -> QueryResult { Ok(Item::owned_by(self.1) - .order(looted::dsl::id.desc()) + .order(loot::dsl::id.desc()) .first(self.0)?) } @@ -204,19 +260,19 @@ impl<'q> LootManager<'q> { base_price: item.base_price, owner_id: self.1, }; - diesel::insert_into(looted::table) + diesel::insert_into(loot::table) .values(&new_item) .execute(self.0)?; - Ok(Update::ItemAdded(self.last()?)) + Ok(vec!(Update::ItemAdded(self.last()?))) } } -/// An item being looted or bought. +/// An item being loot or bought. /// /// The owner is set to 0 in case of looting, /// to the id of buying player otherwise. #[derive(Insertable)] -#[table_name = "looted"] +#[table_name = "loot"] struct NewLoot<'a> { name: &'a str, base_price: i32, @@ -224,7 +280,7 @@ struct NewLoot<'a> { } #[derive(Insertable)] -#[table_name = "shop"] +#[table_name = "items"] struct NewItem<'a> { name: &'a str, base_price: i32, diff --git a/lootalot_db/src/models/player/mod.rs b/lootalot_db/src/models/player/mod.rs index 7232c88..171a88b 100644 --- a/lootalot_db/src/models/player/mod.rs +++ b/lootalot_db/src/models/player/mod.rs @@ -89,7 +89,7 @@ impl<'q> AsPlayer<'q> { .filter(id.eq(self.1)) .set(&updated_wealth) .execute(self.0)?; - Ok(Update::Wealth(updated_wealth - current_wealth)) + Ok(vec!(Update::Wealth(updated_wealth - current_wealth))) } /// Updates this player's debt diff --git a/lootalot_db/src/schema.rs b/lootalot_db/src/schema.rs index 106ee28..4d802f2 100644 --- a/lootalot_db/src/schema.rs +++ b/lootalot_db/src/schema.rs @@ -26,7 +26,7 @@ table! { } table! { - looted (id) { + loot (id) { id -> Integer, name -> Text, base_price -> Integer, @@ -54,26 +54,16 @@ table! { } } -table! { - shop (id) { - id -> Integer, - name -> Text, - base_price -> Integer, - } -} - -joinable!(claims -> looted (loot_id)); joinable!(claims -> players (player_id)); joinable!(history -> players (player_id)); -joinable!(looted -> players (owner_id)); +joinable!(loot -> players (owner_id)); joinable!(notifications -> players (player_id)); allow_tables_to_appear_in_same_query!( claims, history, items, - looted, + loot, notifications, players, - shop, ); diff --git a/src/api.rs b/src/api.rs index 4fd3a0a..47d9c28 100644 --- a/src/api.rs +++ b/src/api.rs @@ -40,11 +40,11 @@ pub struct ApiResponse { } impl ApiResponse { - fn push_update(&mut self, update: db::Update) { + fn push_updates(&mut self, updates: Vec) { if let Some(v) = self.updates.as_mut() { - v.push(update); + v.append(&mut updates); } else { - self.updates = Some(vec![update]); + self.updates = Some(updates); } } @@ -107,6 +107,8 @@ pub enum ApiEndpoints { BuyItems(i32, BuySellParams), // db::Shop(conn)::buy(params) RefreshShop(ItemList), // db::Shop(conn)::replace_list(items) + ClaimsList, + // db::Players::get returns AsPlayer<'q> PlayerList, //db::Players(conn)::list() PlayerAdd(NewPlayer), //db::Players(conn)::add(player) @@ -122,150 +124,111 @@ pub enum ApiEndpoints { ResolveClaims, // db::Group(conn)::resolve_claims() } +static ErrorMsg : &str = "Une erreur est survenue"; pub fn execute( conn: &DbConnection, - query: ApiActions, + query: ApiEndpoints, ) -> Result { let mut response = ApiResponse::default(); // Return an Option that describes what happened. // If there is some value, store the actions in db so that it can be reversed. let action_text: Option<(i32, &str)> = match query { - ApiActions::CheckItemList(names) => { - let (items, errors) = { - let mut found = Vec::new(); - let mut errors = String::new(); - let items = db::Inventory(conn).all()?; - for name in &names { - if let Some(item) = items.iter().filter(|i| &i.name == name).take(1).next() { - found.push(item.clone()) - } else { - errors.push_str(&format!("{},\n", name)); - } - } - (found, errors) - }; - - response.set_value(Value::ItemList(items)); - response.push_error(errors); - dbg!(&names, &response); + // Inventory + ApiEndpoints::FetchInventory => { + match db::Inventory(conn).all() { + Ok(items) => response.set_value(Value::ItemList(items)), + Err(_) => response.set_error(ErrorMsg), + } None } - ApiActions::FetchPlayers => { + ApiEnpoints::InventoryCheck(names) => { + match db::Inventory(conn).check_list(names) { + Ok((items, errors)) => { + response.set_value(Value::ItemList(items)); + response.push_error(errors); + } + Err(_) => response.set_error(ErrorMsg), + } + None + } + ApiEndpoints::InventoryAdd((name, value)) => { + response.set_error("Not implemented"); + None + } + //Shop + ApiEnpoints::ShopList => { + match db::Shop(conn).all() { + Ok(items) => response.set_value(Value::ItemList(items)), + Err(_) => response.set_error(ErrorMsg), + } + None + } + ApiEndpoints::BuyItems(buyer, params) => { + match db::Shop(conn).buy(params, buyer) { + Ok(updates) => { + response.notifiy("Objets achetés !"); + response.push_updates(updates); + Some((id, "Achat d'objets")) + }, + Err(_) => { + response.set_error(ErrorMsg); + None + } + } + } + // Players + ApiEndpoints::PlayerList => { response.set_value(Value::PlayerList(db::Players(conn).all_except_group()?)); None } - ApiActions::FetchInventory => { - response.set_value(Value::ItemList(db::Inventory(conn).all()?)); - None - } - ApiActions::FetchShopInventory => { - response.set_value(Value::ItemList(db::Shop(conn).all()?)); - None - } - ApiActions::FetchClaims => { + ApiEndpoint::ClaimList => { response.set_value(Value::ClaimList(db::Claims(conn).all()?)); None } - ApiActions::FetchPlayer(id) => { - response.set_value(Value::Players(conn)(db::Players(conn).find(id)?)); - None - } - ApiActions::FetchPlayerClaims(id) => { + ApiEndpoint::PlayerClaims(id) => { response.set_value(Value::ClaimList(db::Claims(conn).by_player(id)?)); None } - ApiActions::FetchNotifications(id) => { + ApiEndpoint::PlayerNotifications(id) => { response.set_value(Value::Notifications( db::AsPlayer(conn, id).notifications()?, )); None } - ApiActions::FetchLoot(id) => { + ApiEndpoint::PlayerLoot(id) => { response.set_value(Value::ItemList(db::LootManager(conn, id).all()?)); None } - ApiActions::UpdateWealth(id, amount) => { - response.push_update(db::AsPlayer(conn, id).update_wealth(amount)?); + ApiEndpoint::PlayerUpdateWealth(id, amount) => { + response.push_updates(db::AsPlayer(conn, id).update_wealth(amount)?); response.notify(format!("Mis à jour ({}po)!", amount)); Some((id, "Argent mis à jour")) } - ApiActions::BuyItems(id, params) => { - // TODO: check that player has enough money ! - let has_enough_gold = true; - - if has_enough_gold { - let mut gains: Vec = Vec::with_capacity(params.items.len()); - for (item_id, price_mod) in params.items.into_iter() { - if let Ok((item, diff)) = db::buy_item_from_shop(conn, id, item_id, price_mod) { - response.push_update(item); - gains.push(diff); - } else { - response.push_error(format!("Error adding {}", item_id)); - } - } - let added_items = gains.len(); - let total_amount = gains - .into_iter() - .fold(db::Wealth::from_gp(0.0), |acc, i| acc + i); - response.notify(format!( - "{} objets achetés pour {}po", - added_items, - total_amount.to_gp() - )); - response.push_update(Update::Wealth(total_amount)); - 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. // Group sells item like players then split the total amount among players. - ApiActions::SellItems(id, params) => { + ApiEndpoint::SellItems(id, params) => { conn.transaction(|| -> Result, diesel::result::Error> { - let mut gains: Vec = Vec::with_capacity(params.items.len()); - for (loot_id, price_mod) in params.items.iter() { - if let Ok((deleted, diff)) = - db::sell_item_transaction(conn, id, *loot_id, *price_mod) - { - response.push_update(deleted); - gains.push(diff); - } else { - response - .push_error(format!("Erreur lors de la vente (loot_id : {})", loot_id)); - } - } - let sold_items = gains.len(); - let total_amount = gains - .into_iter() - .fold(db::Wealth::from_gp(0.0), |acc, i| acc + i); + let sold = LootManager(conn, id).sell(params.items)?; match id { 0 => { let players = params.players.unwrap_or_default(); - if let Update::Wealth(shared) = + updates.push( db::split_and_share(conn, total_amount.to_gp() as i32, players)? - { - response.notify(format!( - "Les objets ont été vendus, les joueurs ont reçu (au total) {} po", - shared.to_gp() - )); - response.push_update(Update::Wealth(total_amount - shared)); + ); + response.notify("Les objets ont été vendus"); + response.push_updates(updates); }; } _ => { - response.notify(format!( - "{} objet(s) vendu(s) pour {} po", - sold_items, - total_amount.to_gp() - )); - response.push_update(Update::Wealth(total_amount)); + response.notify("Objets vendus !"); + response.push_updates(updates); } } Ok(Some((id, "Vente d'objets"))) })? } - ApiActions::ClaimItems(id, items) => { + ApiEndpoint::ClaimItems(id, items) => { conn.transaction(|| -> Result, diesel::result::Error> { let current_claims: HashSet = db::Claims(conn) .all()? @@ -285,7 +248,7 @@ pub fn execute( Ok(None) })? } - ApiActions::UndoLastAction(id) => { + ApiEndpoint::UndoLastAction(id) => { if let Ok(event) = db::models::history::get_last_of_player(conn, id) { let name = String::from(event.name()); for undone in event.undo(conn)?.into_inner().into_iter() { @@ -298,7 +261,7 @@ pub fn execute( None } // Group actions - ApiActions::AddLoot(data) => { + ApiEndpoint::AddLoot(data) => { let mut added_items = 0; for item in data.items.into_iter() { if let Ok(added) = db::LootManager(conn, 0).add_from(&item) { @@ -317,13 +280,17 @@ pub fn execute( }; Some((0, "Nouveau loot")) } + ApiEndpoints::ResolveClaims => { + response.push_error("Not implemented!"); + None + } // Admin actions - ApiActions::RefreshShopInventory(items) => { + ApiEndpoints::RefreshShop(items) => { db::Shop(conn).replace_list(items)?; response.notify("Inventaire du marchand renouvelé !"); None } - ApiActions::AddPlayer(data) => { + ApiEnpoints::AddPlayer(data) => { db::Players(conn).add(&data.name, data.wealth)?; response.notify("Joueur ajouté !"); None