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 modelsmain
parent
a655146c81
commit
12218d0b62
|
@ -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"
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
pub mod database;
|
||||
pub mod error;
|
||||
pub mod helper;
|
||||
pub(crate) mod migration;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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>;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue