use lootalot_db::{self as db, DbConnection, Update, Value}; pub type ItemListWithMods = Vec<(i32, Option)>; #[derive(Serialize, Deserialize, Debug)] pub struct SellParams { pub items: ItemListWithMods, players: Option>, global_mod: Option, } /// A generic response for all queries #[derive(Serialize, Debug, Default)] pub struct ApiResponse { /// The value requested, if any pub value: Option, /// A text to notify user, if relevant pub notification: Option, /// A list of updates, if any pub updates: Option>, /// A text describing errors, if any pub errors: Option, } impl ApiResponse { fn push_update(&mut self, update: db::Update) { if let Some(v) = self.updates.as_mut() { v.push(update); } else { self.updates = Some(vec![update]); } } fn push_error>(&mut self, error: S) { if let Some(errors) = self.errors.as_mut() { *errors = format!("{}\n{}", errors, error.into()); } else { self.errors = Some(error.into()) } } fn set_value(&mut self, value: db::Value) { self.value = Some(value); } fn notify>(&mut self, text: S) { self.notification = Some(text.into()); } } pub enum ApiError { DieselError(diesel::result::Error), InvalidAction(String), } /// Every allowed queries on the database pub enum ApiActions { FetchPlayers, FetchInventory, FetchClaims, // Player actions FetchPlayer(i32), FetchNotifications(i32), FetchLoot(i32), UpdateWealth(i32, f64), BuyItems(i32, Vec<(i32, Option)>), SellItems(i32, SellParams), ClaimItem(i32, i32), UnclaimItem(i32, i32), // Group actions AddLoot(Vec), } pub enum AdminActions { AddPlayer(String, f64), //AddInventoryItem(pub String, pub i32), ResolveClaims, //SetClaimsTimeout(pub i32), } pub fn execute( conn: &DbConnection, query: ApiActions, ) -> Result { let mut response = ApiResponse::default(); // TODO: 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<&str> = match query { ApiActions::FetchPlayers => { response.set_value(Value::PlayerList(db::Players(conn).all()?)); None } ApiActions::FetchInventory => { response.set_value(Value::ItemList(db::Inventory(conn).all()?)); None } ApiActions::FetchClaims => { response.set_value(Value::ClaimList(db::fetch_claims(conn)?)); None } ApiActions::FetchPlayer(id) => { response.set_value(Value::Player(db::Players(conn).find(id)?)); None } ApiActions::FetchNotifications(id) => { response.set_value(Value::Notifications( db::AsPlayer(conn, id).notifications()?, )); None } ApiActions::FetchLoot(id) => { response.set_value(Value::ItemList(db::LootManager(conn, id).all()?)); None } ApiActions::UpdateWealth(id, amount) => { response.push_update(Update::Wealth( db::AsPlayer(conn, id).update_wealth(amount)?, )); response.notify(format!("Mis à jour ({}po)!", amount)); Some("Argent mis à jour") } ApiActions::BuyItems(id, params) => { // TODO: check that player has enough money ! let mut cumulated_diff: Vec = Vec::with_capacity(params.len()); let mut added_items: u16 = 0; for (item_id, price_mod) in params.into_iter() { if let Ok((item, diff)) = db::buy_item_from_inventory(conn, id, item_id, price_mod) { cumulated_diff.push(diff); response.push_update(Update::ItemAdded(item)); added_items += 1; } else { response.push_error(format!("Error adding {}", item_id)); } } let total_amount = cumulated_diff .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("Achat d'objets") } // Behavior differs if player is group or regular. // Group sells item like players then split the total amount among players. ApiActions::SellItems(id, params) => { let mut all_results: Vec = Vec::with_capacity(params.items.len()); let mut sold_items: u16 = 0; for (loot_id, price_mod) in params.items.iter() { if let Ok((deleted, diff)) = db::sell_item_transaction(conn, id, *loot_id, *price_mod) { all_results.push(diff); response.push_update(Update::ItemRemoved(deleted)); sold_items += 1; } else { response.push_error(format!("Erreur lors de la vente (loot_id : {})", loot_id)); } } let total_amount = all_results .into_iter() .fold(db::Wealth::from_gp(0.0), |acc, i| acc + i); match id { 0 => { let share = db::split_and_share( conn, total_amount.to_gp() as i32, params.players.expect("Should not be None"), )?; response.notify(format!( "Les objets ont été vendus, chaque joueur a reçu {} po", share.to_gp() )); response.push_update(Update::Wealth(share)); } _ => { response.notify(format!( "{} objet(s) vendu(s) pour {} po", sold_items, total_amount.to_gp() )); response.push_update(Update::Wealth(total_amount)); } } Some("Vente d'objets") } ApiActions::ClaimItem(id, item) => { response.push_update(Update::ClaimAdded(db::Claims(conn).add(id, item)?)); response.notify(format!("Pour moi !")); None } ApiActions::UnclaimItem(id, item) => { response.push_update(Update::ClaimRemoved(db::Claims(conn).remove(id, item)?)); response.notify(format!("Bof! Finalement non.")); None } // Group actions ApiActions::AddLoot(items) => { let mut added_items = 0; for item in items.into_iter() { if let Ok(added) = db::LootManager(conn, 0).add_from(&item) { response.push_update(Update::ItemAdded(added)); added_items += 1; } else { response.push_error(format!("Error adding {:?}", item)); } } response.notify(format!("{} objets lootés !", added_items)); // Notify players through persistent notifications if let Err(e) = db::Players(conn) .notifiy_all("De nouveaux objets ont été ajoutés au coffre de groupe !") { response.push_error(format!("Erreur durant la notification : {:?}", e)); }; Some("Nouveau loot") } }; // match _action_text -> Save updates in DB dbg!(&action_text); Ok(response) } /// Reverts the last action stored for player fn revert_last_action(conn: &DbConnection, id: i32) -> Result { let mut response = ApiResponse::default(); // 1. Load the last action // 2. Iterate trought updates and reverse them (in a single transaction) // 3. Send nice message back ("'action desc text' annulé !") Ok(response) }