diff --git a/geoffrey_db/src/database.rs b/geoffrey_db/src/database.rs index 6648e86..219bcf8 100644 --- a/geoffrey_db/src/database.rs +++ b/geoffrey_db/src/database.rs @@ -6,7 +6,7 @@ use sled::IVec; use std::convert::TryInto; use std::path::Path; -const DB_VERSION: u64 = 3; +const DB_VERSION: u64 = 4; const DB_METADATA_ID: u64 = 1; fn option_bytes_to_model(bytes: Option, id: u64) -> Result { diff --git a/geoffrey_db/src/migration/migration_2.rs b/geoffrey_db/src/migration/migration_2.rs index 50a1108..a7f252e 100644 --- a/geoffrey_db/src/migration/migration_2.rs +++ b/geoffrey_db/src/migration/migration_2.rs @@ -21,8 +21,6 @@ impl Migration for PosAndNetherMigration { loc["tunnel"] = JsonValue::Null; - println!("{}", loc.dump()); - loc_tree.insert(id, loc.to_string().as_bytes())?; } diff --git a/geoffrey_db/src/migration/migration_3.rs b/geoffrey_db/src/migration/migration_3.rs index 6fac2e5..0da862e 100644 --- a/geoffrey_db/src/migration/migration_3.rs +++ b/geoffrey_db/src/migration/migration_3.rs @@ -56,7 +56,7 @@ mod tests { use std::path::Path; #[test] - fn test_migration_2() { + fn test_migration_3() { let db = Database::new(Path::new("../migration_3_db/")).unwrap(); let loc_tree = db.db.open_tree(LocationDb::tree()).unwrap(); diff --git a/geoffrey_db/src/migration/migration_4.rs b/geoffrey_db/src/migration/migration_4.rs new file mode 100644 index 0000000..4320cf4 --- /dev/null +++ b/geoffrey_db/src/migration/migration_4.rs @@ -0,0 +1,135 @@ +use crate::database::Database; +use crate::migration::Migration; +use geoffrey_models::models::locations::LocationDb; +use geoffrey_models::GeoffreyDatabaseModel; + +pub(crate) struct OutOfStockVoting {} + +impl Migration for OutOfStockVoting { + fn up(db: &Database) -> crate::error::Result<()> { + let loc_tree = db.db.open_tree(LocationDb::tree())?; + + for entry in loc_tree.iter() { + let (id, loc_ivec) = entry?; + let mut loc = json::parse(std::str::from_utf8(&loc_ivec).unwrap()).unwrap(); + + for item in loc["loc_data"]["Shop"]["item_listings"].members_mut() { + item["out_of_stock_votes"] = json::JsonValue::Array(Vec::new()); + } + + loc_tree.insert(id, loc.to_string().as_bytes()).unwrap(); + } + + Ok(()) + } + + fn down(db: &Database) -> crate::error::Result<()> { + let loc_tree = db.db.open_tree(LocationDb::tree())?; + + for entry in loc_tree.iter() { + let (id, loc_ivec) = entry?; + let mut loc = json::parse(std::str::from_utf8(&loc_ivec).unwrap()).unwrap(); + + for item in &mut loc["loc_data"]["Shop"]["item_listings"].members_mut() { + item["out_of_stock_votes"].clear() + } + + loc_tree.insert(id, loc.to_string().as_bytes()).unwrap(); + } + + Ok(()) + } + + fn version() -> u64 { + 2 + } +} + +#[cfg(test)] +mod tests { + use crate::database::Database; + use crate::migration::migration_4::OutOfStockVoting; + use crate::migration::Migration; + use geoffrey_models::models::locations::LocationDb; + use geoffrey_models::GeoffreyDatabaseModel; + use sled::IVec; + use std::path::Path; + + #[test] + fn test_migration_4() { + let db = Database::new(Path::new("../migration_4_db/")).unwrap(); + + let loc_tree = db.db.open_tree(LocationDb::tree()).unwrap(); + + let old_shop = json::parse( + r#"{ + "id": 55, + "name": "Test", + "owners": [0], + "position": { + "x": 55, + "y": 55, + "z": 55, + "dimension": "Overworld" + }, + "portal": { + "x": 8, + "z": 8 + }, + "loc_data": { + "Shop": { + "item_listings": [ + { + "item": {"name": "sed"}, + "price": 5, + "count_per_price": 10, + "restocked_time": "2021-12-26T17:47:10+00:00" + } + ] + } + } + }"#, + ) + .unwrap(); + + let old_base = json::parse( + r#"{ + "id": 55, + "name": "Test", + "owners": [0], + "position": { + "x": 55, + "y": 55, + "z": 55, + "dimension": "Overworld" + }, + "portal": { + "x": 8, + "z": 8 + }, + "loc_data": "Base" + }"#, + ) + .unwrap(); + + loc_tree + .insert(IVec::from(vec![55]), old_shop.to_string().as_bytes()) + .unwrap(); + + loc_tree + .insert(IVec::from(vec![64]), old_base.to_string().as_bytes()) + .unwrap(); + + OutOfStockVoting::up(&db).unwrap(); + + let new_loc = loc_tree.get(IVec::from(vec![55])).unwrap().unwrap(); + + let new_loc = json::parse(std::str::from_utf8(&new_loc).unwrap()).unwrap(); + + assert_eq!(new_loc["loc_data"]["Shop"]["out_of_stock_votes"].len(), 0); + + drop(db); + + std::fs::remove_dir_all("../migration_4_db").unwrap(); + } +} diff --git a/geoffrey_db/src/migration/mod.rs b/geoffrey_db/src/migration/mod.rs index 22bc7af..4e78d58 100644 --- a/geoffrey_db/src/migration/mod.rs +++ b/geoffrey_db/src/migration/mod.rs @@ -2,10 +2,12 @@ use crate::database::Database; use crate::error::Result; use crate::migration::migration_2::PosAndNetherMigration; use crate::migration::migration_3::TunnelToPortalMigration; +use crate::migration::migration_4::OutOfStockVoting; use geoffrey_models::models::db_metadata::DBMetadata; mod migration_2; mod migration_3; +mod migration_4; trait Migration { fn up(db: &Database) -> Result<()>; @@ -18,6 +20,7 @@ fn upgrade(db: &Database, current_version: u64, target_version: u64) -> Result<( match ver { 2 => PosAndNetherMigration::up(db)?, 3 => TunnelToPortalMigration::up(db)?, + 4 => OutOfStockVoting::up(db)?, _ => (), } } @@ -30,6 +33,7 @@ fn downgrade(db: &Database, current_version: u64, target_version: u64) -> Result match ver { 2 => PosAndNetherMigration::down(db)?, 3 => TunnelToPortalMigration::down(db)?, + 4 => OutOfStockVoting::down(db)?, _ => (), } } diff --git a/geoffrey_models/src/models/item.rs b/geoffrey_models/src/models/item.rs index 0d168ca..fa75060 100644 --- a/geoffrey_models/src/models/item.rs +++ b/geoffrey_models/src/models/item.rs @@ -1,6 +1,8 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +use std::collections::HashSet; +use std::hash::{Hash, Hasher}; #[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] pub struct Item { @@ -15,12 +17,13 @@ impl Item { } } -#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ItemListing { pub item: Item, pub price: u32, pub count_per_price: u32, pub restocked_time: DateTime, + pub out_of_stock_votes: HashSet, } impl ItemListing { @@ -32,6 +35,7 @@ impl ItemListing { price, count_per_price, restocked_time: Utc::now(), + out_of_stock_votes: HashSet::default(), } } @@ -54,4 +58,35 @@ impl ItemListing { pub fn order_by_restock(a: &Self, b: &Self) -> Ordering { a.restocked_time.cmp(&b.restocked_time) } + + pub fn report_out_of_stock(&mut self, id: u64) { + self.out_of_stock_votes.insert(id); + } + + pub fn is_out_of_stock(&self, report_limit: u32) -> bool { + self.out_of_stock_votes.len() > report_limit as usize + } + + pub fn restock(&mut self) { + self.restocked_time = Utc::now(); + self.out_of_stock_votes.clear(); + } } + +impl Hash for ItemListing { + fn hash(&self, state: &mut H) { + self.item.name.hash(state); + self.price.hash(state); + self.count_per_price.hash(state); + } +} + +impl PartialEq for ItemListing { + fn eq(&self, other: &Self) -> bool { + (self.item.name == other.item.name) + && (self.price == other.price) + && (self.count_per_price == other.count_per_price) + } +} + +impl Eq for ItemListing {}