Updated models + migration infra

+ Model migration is handled in the DB modules
+ It involves writing an up and a down case
  + Both load and interact with the JSON directly
  + Done so it can be decoupled from models
main
Joey Hines 2021-12-05 16:51:13 -07:00
parent a655146c81
commit 12218d0b62
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
14 changed files with 271 additions and 45 deletions

7
Cargo.lock generated
View File

@ -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"

View File

@ -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(),
);

View File

@ -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))
}

View File

@ -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"

View File

@ -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<Self> {
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<T>(&self) -> Result<sled::Tree>
@ -106,6 +116,20 @@ impl Database {
{
Ok(self.db.open_tree(T::tree()).map(|tree| tree.iter())?)
}
pub fn version(&self) -> Result<u64> {
Ok(self.get::<DBMetadata>(DB_METADATA_ID)?.version)
}
pub(crate) fn set_version(&mut self, version: u64) -> Result<()> {
let mut md = self.get::<DBMetadata>(0)?;
md.version = version;
self.insert(md)?;
Ok(())
}
}
#[cfg(test)]
@ -182,7 +206,7 @@ mod tests {
let player = DB.insert::<Player>(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 {

View File

@ -3,3 +3,4 @@
pub mod database;
pub mod error;
pub mod helper;
pub(crate) mod migration;

View File

@ -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();
}
}

View File

@ -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(())
}

View File

@ -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<u64>;

View File

@ -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<u64> {
Some(1)
}
fn set_id(&mut self, _id: u64) {}
fn tree() -> String {
"DBMetadata".to_string()
}
}

View File

@ -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<u64>,
pub tunnel: Option<Tunnel>,
pub tunnel: Option<Portal>,
pub loc_data: LocationDataDb,
}
@ -120,7 +120,7 @@ impl LocationDb {
name: &str,
position: Position,
owner: u64,
tunnel: Option<Tunnel>,
tunnel: Option<Portal>,
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<Player>,
pub tunnel: Option<Tunnel>,
pub tunnel: Option<Portal>,
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,

View File

@ -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)]

View File

@ -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<Tunnel>,
pub portal: Option<Portal>,
}
impl AddLocationParams {
@ -19,7 +19,7 @@ impl AddLocationParams {
name: String,
position: Position,
loc_type: LocationType,
tunnel: Option<Tunnel>,
portal: Option<Portal>,
) -> Self {
Self {
token: Default::default(),
@ -27,7 +27,7 @@ impl AddLocationParams {
name,
position,
loc_type,
tunnel,
portal,
}
}
}

View File

@ -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<Tunnel>,
pub portal: Option<Portal>,
pub listing: ItemListing,
}