diff --git a/Cargo.lock b/Cargo.lock index 830913b..c1ac89c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,6 +456,7 @@ version = "0.1.0" dependencies = [ "byteorder", "geoffrey_models", + "json", "lazy_static", "log", "serde 1.0.130", @@ -707,6 +708,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "lazy_static" version = "1.4.0" diff --git a/geoffrey_api/src/commands/add_location.rs b/geoffrey_api/src/commands/add_location.rs index 86bc74f..fd1f7f5 100644 --- a/geoffrey_api/src/commands/add_location.rs +++ b/geoffrey_api/src/commands/add_location.rs @@ -33,7 +33,7 @@ impl Command for AddLocation { req.name.as_str(), req.position, user.id.unwrap(), - req.tunnel.clone(), + req.portal.clone(), req.loc_type.into(), ); diff --git a/geoffrey_bot/src/bot/commands/add_location.rs b/geoffrey_bot/src/bot/commands/add_location.rs index 0e90c0d..65b5799 100644 --- a/geoffrey_bot/src/bot/commands/add_location.rs +++ b/geoffrey_bot/src/bot/commands/add_location.rs @@ -54,22 +54,28 @@ impl BotCommand for AddLocationCommand { .create_option(|option| { option .name("x") - .description("X coordinate of the shop") + .description("X coordinate of the location") .kind(ApplicationCommandOptionType::Integer) + .max_int_value(i32::MAX) + .min_int_value(i32::MIN) .required(true) }) .create_option(|option| { option .name("y") - .description("Y coordinate of the shop") + .description("Y coordinate of the location") .kind(ApplicationCommandOptionType::Integer) + .max_int_value(i32::MAX) + .min_int_value(i32::MIN) .required(true) }) .create_option(|option| { option .name("z") - .description("Z coordinate of the shop") + .description("Z coordinate of the location") .kind(ApplicationCommandOptionType::Integer) + .max_int_value(i32::MAX) + .min_int_value(i32::MIN) .required(true) }) .create_option(|option| { @@ -82,20 +88,6 @@ impl BotCommand for AddLocationCommand { .add_string_choice(Dimension::TheEnd, Dimension::TheEnd) .required(false) }) - .create_option(|option| { - option - .name("portal_x") - .description("X Coordinate of the portal in the nether") - .kind(ApplicationCommandOptionType::Integer) - .required(false) - }) - .create_option(|option| { - option - .name("portal_z") - .description("Z Coordinate of the portal in the nether") - .kind(ApplicationCommandOptionType::Integer) - .required(false) - }) }) .await?; @@ -109,11 +101,11 @@ impl BotCommand for AddLocationCommand { let name = option_to_string(options.get(0), "name")?; let loc_type = option_to_loc_type(options.get(1), "loc_type")?; let x = option_to_i64(options.get(2), "x")?; - let _y = option_to_i64(options.get(3), "x")?; - let z = option_to_i64(options.get(4), "x")?; + let y = option_to_i64(options.get(3), "y")?; + let z = option_to_i64(options.get(4), "z")?; let dim = option_to_dim(options.get(5), "dimension").unwrap_or(Dimension::Overworld); - let position = Position::new(x as i32, z as i32, dim); + let position = Position::new(x as i32, y as i32, z as i32, dim); Ok(Self::ApiParams::new(name, position, loc_type, None)) } diff --git a/geoffrey_db/Cargo.toml b/geoffrey_db/Cargo.toml index ec8c327..39537df 100644 --- a/geoffrey_db/Cargo.toml +++ b/geoffrey_db/Cargo.toml @@ -14,6 +14,7 @@ geoffrey_models = { path = "../geoffrey_models" } byteorder = "1.4.2" log = "0.4.14" simple_logger = "1.13.0" +json = "0.12.4" [dev-dependencies] lazy_static = "1.4.0" diff --git a/geoffrey_db/src/database.rs b/geoffrey_db/src/database.rs index cb8a7ed..79ab5f4 100644 --- a/geoffrey_db/src/database.rs +++ b/geoffrey_db/src/database.rs @@ -2,16 +2,26 @@ use crate::error::{GeoffreyDBError, Result}; use geoffrey_models::GeoffreyDatabaseModel; use std::convert::TryInto; use std::path::Path; +use geoffrey_models::models::db_metadata::DBMetadata; +use crate::migration::do_migration; + +const DB_VERSION: u64 = 2; +const DB_METADATA_ID: u64 = 1; pub struct Database { - db: sled::Db, + pub(crate) db: sled::Db, } impl Database { pub fn new(db_path: &Path) -> Result { let db = sled::open(db_path)?; + let db = Self { db }; - Ok(Self { db }) + do_migration(&db, DB_VERSION)?; + + log::info!("Geoffrey Database V{}", db.version()?); + + Ok(db) } fn get_tree(&self) -> Result @@ -106,6 +116,20 @@ impl Database { { Ok(self.db.open_tree(T::tree()).map(|tree| tree.iter())?) } + + pub fn version(&self) -> Result { + Ok(self.get::(DB_METADATA_ID)?.version) + } + + pub(crate) fn set_version(&mut self, version: u64) -> Result<()> { + let mut md = self.get::(0)?; + + md.version = version; + + self.insert(md)?; + + Ok(()) + } } #[cfg(test)] @@ -182,7 +206,7 @@ mod tests { let player = DB.insert::(player.clone()).unwrap(); let loc = LocationDb::new( "Test Shop", - Position::new(0, 0, Dimension::Overworld), + Position::new(0, 0, 0, Dimension::Overworld), player.id.unwrap(), None, LocationDataDb::Shop(Shop { diff --git a/geoffrey_db/src/lib.rs b/geoffrey_db/src/lib.rs index d56cdff..267c6eb 100644 --- a/geoffrey_db/src/lib.rs +++ b/geoffrey_db/src/lib.rs @@ -3,3 +3,4 @@ pub mod database; pub mod error; pub mod helper; +pub(crate) mod migration; diff --git a/geoffrey_db/src/migration/migration_2.rs b/geoffrey_db/src/migration/migration_2.rs new file mode 100644 index 0000000..c223bf5 --- /dev/null +++ b/geoffrey_db/src/migration/migration_2.rs @@ -0,0 +1,98 @@ +use crate::migration::Migration; +use crate::database::Database; +use geoffrey_models::models::locations::LocationDb; +use geoffrey_models::GeoffreyDatabaseModel; +use json::JsonValue; + +pub(crate) struct PosAndNetherMigration {} + +impl Migration for PosAndNetherMigration { + 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(); + + let z = loc["position"]["y"].clone(); + + loc["position"]["y"] = 65.into(); + loc["position"]["z"] = z; + + loc["tunnel"] = JsonValue::Null; + + println!("{}", loc.dump()); + + loc_tree.insert(id, loc.to_string().as_bytes())?; + } + + 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(); + + loc["position"]["y"] = loc["position"]["z"].clone(); + loc["position"]["z"].clear(); + + // Clear out the tunnel entry, there is not a great way to convert + loc["tunnel"] = JsonValue::Null; + + loc_tree.insert(id, loc.to_string().as_bytes())?; + } + + Ok(()) + + } + + fn version() -> u64 { + 2 + } +} + +#[cfg(test)] +mod tests { + use crate::database::Database; + use std::path::Path; + use geoffrey_models::models::locations::LocationDb; + use geoffrey_models::GeoffreyDatabaseModel; + use sled::IVec; + use crate::migration::migration_2::PosAndNetherMigration; + use crate::migration::Migration; + + #[test] + fn test_migration_2() { + let db = Database::new(Path::new("../migration_2_db/")).unwrap(); + + let loc_tree = db.db.open_tree(LocationDb::tree()).unwrap(); + + let old_loc_format = json::parse(r#"{ + "id": 55, + "name": "Test", + "position": { + "x": 55, + "y": 55, + "dimension": "Overworld" + }, + "loc_data": "Base" + }"#).unwrap(); + + loc_tree.insert(IVec::from(vec![55]), old_loc_format.to_string().as_bytes()).unwrap(); + + PosAndNetherMigration::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["position"]["y"], 65); + assert_eq!(new_loc["position"]["z"], 55); + + drop(db); + + std::fs::remove_dir_all("../migration_2_db").unwrap(); + } +} \ No newline at end of file diff --git a/geoffrey_db/src/migration/mod.rs b/geoffrey_db/src/migration/mod.rs new file mode 100644 index 0000000..d2d0b7f --- /dev/null +++ b/geoffrey_db/src/migration/mod.rs @@ -0,0 +1,53 @@ +use crate::database::Database; +use crate::error::Result; +use crate::migration::migration_2::PosAndNetherMigration; +use geoffrey_models::models::db_metadata::DBMetadata; + +mod migration_2; + +trait Migration { + fn up(db: &Database) -> Result<()>; + fn down(db: &Database) -> Result<()>; + fn version() -> u64; +} + +fn upgrade(db: &Database, current_version: u64, target_version: u64) -> Result<()> { + for ver in current_version+1..=target_version { + match ver { + 2 => PosAndNetherMigration::up(db)?, + _ => () + } + } + + Ok(()) +} + +fn downgrade(db: &Database, current_version: u64, target_version: u64) -> Result<()> { + for ver in (target_version..current_version).rev() { + match ver { + 2 => PosAndNetherMigration::up(db)?, + _ => () + } + } + + Ok(()) +} + +pub fn do_migration(db: &Database, target_version: u64) -> Result<()> { + let current_version = db.version().unwrap_or(0); + + if target_version > current_version { + upgrade(db, current_version, target_version)?; + } + else if target_version < current_version { + downgrade(db, current_version, target_version)?; + } + + let metadata = DBMetadata { + version: target_version, + }; + + db.insert(metadata)?; + + Ok(()) +} diff --git a/geoffrey_models/src/lib.rs b/geoffrey_models/src/lib.rs index e71027e..73d86db 100644 --- a/geoffrey_models/src/lib.rs +++ b/geoffrey_models/src/lib.rs @@ -5,7 +5,6 @@ use std::fmt::Debug; pub mod models; -const DB_VERSION: u64 = 1; pub trait GeoffreyDatabaseModel: Serialize + DeserializeOwned + Debug { fn id(&self) -> Option; diff --git a/geoffrey_models/src/models/db_metadata.rs b/geoffrey_models/src/models/db_metadata.rs new file mode 100644 index 0000000..6cf2798 --- /dev/null +++ b/geoffrey_models/src/models/db_metadata.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; +use crate::GeoffreyDatabaseModel; + +#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] +pub struct DBMetadata { + pub version: u64, +} + +impl GeoffreyDatabaseModel for DBMetadata { + fn id(&self) -> Option { + Some(1) + } + + fn set_id(&mut self, _id: u64) {} + + fn tree() -> String { + "DBMetadata".to_string() + } +} + + diff --git a/geoffrey_models/src/models/locations/mod.rs b/geoffrey_models/src/models/locations/mod.rs index c30bbd9..5f97baa 100644 --- a/geoffrey_models/src/models/locations/mod.rs +++ b/geoffrey_models/src/models/locations/mod.rs @@ -6,7 +6,7 @@ use crate::models::locations::market::{Market, MarketDb}; use crate::models::locations::shop::Shop; use crate::models::locations::town::{Town, TownDb}; use crate::models::player::Player; -use crate::models::{Position, Tunnel}; +use crate::models::{Position, Portal}; use crate::GeoffreyDatabaseModel; use std::fmt::{Display, Formatter}; use std::str::FromStr; @@ -111,7 +111,7 @@ pub struct LocationDb { pub name: String, pub position: Position, owners: HashSet, - pub tunnel: Option, + pub tunnel: Option, pub loc_data: LocationDataDb, } @@ -120,7 +120,7 @@ impl LocationDb { name: &str, position: Position, owner: u64, - tunnel: Option, + tunnel: Option, loc_type: LocationDataDb, ) -> Self { let mut owners = HashSet::new(); @@ -173,7 +173,7 @@ pub struct Location { pub name: String, pub position: Position, pub owners: Vec, - pub tunnel: Option, + pub tunnel: Option, pub loc_data: LocationData, } @@ -204,14 +204,14 @@ mod tests { fn test_location_check_unique() { let l1 = LocationDb::new( "Test", - Position::new(0, 0, Dimension::Overworld), + Position::new(0, 0, 0, Dimension::Overworld), 0u64, None, LocationDataDb::Base, ); let l2 = LocationDb::new( "NotTest", - Position::new(0, 0, Dimension::Overworld), + Position::new(0, 0, 0, Dimension::Overworld), 0u64, None, LocationDataDb::Base, @@ -221,14 +221,14 @@ mod tests { let l1 = LocationDb::new( "Test", - Position::new(0, 0, Dimension::Overworld), + Position::new(0, 0, 0, Dimension::Overworld), 0u64, None, LocationDataDb::Base, ); let l2 = LocationDb::new( "teSt", - Position::new(0, 0, Dimension::Overworld), + Position::new(0, 0, 0,Dimension::Overworld), 0u64, None, LocationDataDb::Base, diff --git a/geoffrey_models/src/models/mod.rs b/geoffrey_models/src/models/mod.rs index c0dcc8e..ab343fe 100644 --- a/geoffrey_models/src/models/mod.rs +++ b/geoffrey_models/src/models/mod.rs @@ -9,6 +9,7 @@ pub mod parameters; pub mod player; pub mod response; pub mod token; +pub mod db_metadata; #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub enum Dimension { @@ -73,25 +74,54 @@ impl Display for Direction { pub struct Position { pub x: i32, pub y: i32, + pub z: i32, pub dimension: Dimension, } impl Position { - pub fn new(x: i32, y: i32, dimension: Dimension) -> Self { - Self { x, y, dimension } + pub fn new(x: i32, y: i32, z: i32, dimension: Dimension) -> Self { + Self { x, y, z, dimension } } } impl Display for Position { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} @ (x={}, z={}) ", self.dimension, self.x, self.y) + write!(f, "(x={}, y={}, z={}) in {} ", self.x, self.y, self.z, self.dimension) } } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Tunnel { - direction: Direction, - number: i32, +pub struct Portal { + x: i32, + z: i32 +} + +impl Portal { + pub fn new(x: i32, z: i32) -> Self { + Self { + x, + z + } + } + + pub fn direction(&self) -> Direction { + if self.x.abs() > self.z.abs() { + if self.x < 0 { + Direction::West + } + else { + Direction::East + } + } + else { + if self.z < 0 { + Direction::North + } + else { + Direction::South + } + } + } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)] diff --git a/geoffrey_models/src/models/parameters/add_location_params.rs b/geoffrey_models/src/models/parameters/add_location_params.rs index 7cc4a5c..752256d 100644 --- a/geoffrey_models/src/models/parameters/add_location_params.rs +++ b/geoffrey_models/src/models/parameters/add_location_params.rs @@ -1,7 +1,7 @@ use crate::models::locations::LocationType; use crate::models::parameters::CommandRequest; use crate::models::player::UserID; -use crate::models::{Position, Tunnel}; +use crate::models::{Position, Portal}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -11,7 +11,7 @@ pub struct AddLocationParams { pub name: String, pub position: Position, pub loc_type: LocationType, - pub tunnel: Option, + pub portal: Option, } impl AddLocationParams { @@ -19,7 +19,7 @@ impl AddLocationParams { name: String, position: Position, loc_type: LocationType, - tunnel: Option, + portal: Option, ) -> Self { Self { token: Default::default(), @@ -27,7 +27,7 @@ impl AddLocationParams { name, position, loc_type, - tunnel, + portal, } } } diff --git a/geoffrey_models/src/models/response/selling_listing.rs b/geoffrey_models/src/models/response/selling_listing.rs index 93f788d..006394e 100644 --- a/geoffrey_models/src/models/response/selling_listing.rs +++ b/geoffrey_models/src/models/response/selling_listing.rs @@ -1,11 +1,11 @@ use crate::models::item::ItemListing; -use crate::models::{Position, Tunnel}; +use crate::models::{Position, Portal}; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct SellingListing { pub shop_name: String, pub shop_loc: Position, - pub portal: Option, + pub portal: Option, pub listing: ItemListing, }