Refactoring to get the find command working

+ Decoupled Database Types from API Return Types
  + Allows response to be more content rich
+ Added helper methods to load all the information related ot a model
 + This also helps us move a bit closer to what we would have to do for something like Diesel
 + Might be good to add more functions like this to decouple db operations from the API layer
main
Joey Hines 2021-10-23 13:11:23 -06:00
parent f6b797b6c1
commit d8de648395
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
13 changed files with 203 additions and 111 deletions

View File

@ -2,12 +2,13 @@ use crate::commands::{Command, RequestType};
use crate::context::Context; use crate::context::Context;
use crate::helper::get_player_from_req; use crate::helper::get_player_from_req;
use crate::Result; use crate::Result;
use geoffrey_models::models::locations::Location; use geoffrey_models::models::locations::{LocationDb, Location};
use geoffrey_models::models::parameters::add_location_params::AddLocationParams; use geoffrey_models::models::parameters::add_location_params::AddLocationParams;
use geoffrey_models::models::parameters::CommandRequest; use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel; use geoffrey_models::models::CommandLevel;
use std::sync::Arc; use std::sync::Arc;
use geoffrey_db::helper::load_location;
pub struct AddLocation {} pub struct AddLocation {}
@ -30,17 +31,19 @@ impl Command for AddLocation {
fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp> { fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp> {
if let Some(player) = get_player_from_req(&ctx.db, &req)? { if let Some(player) = get_player_from_req(&ctx.db, &req)? {
let args = &req.arguments; let args = &req.arguments;
let location = Location::new( let location = LocationDb::new(
args.name.as_str(), args.name.as_str(),
args.position, args.position,
player.id.unwrap(), player.id.unwrap(),
args.tunnel.clone(), args.tunnel.clone(),
args.loc_type.clone(), args.loc_type.into(),
); );
ctx.db let location = ctx.db
.insert(location) .insert(location)?;
.map_err(|err| GeoffreyAPIError::DatabaseError(err.to_string()))
load_location(&ctx.db, &location).map_err(|err| err.into())
} else { } else {
Err(GeoffreyAPIError::PlayerNotRegistered) Err(GeoffreyAPIError::PlayerNotRegistered)
} }

View File

@ -1,12 +1,14 @@
use crate::commands::{Command, RequestType}; use crate::commands::{Command, RequestType};
use crate::context::Context; use crate::context::Context;
use crate::Result; use crate::Result;
use geoffrey_models::models::locations::Location; use geoffrey_models::models::locations::{LocationDb, Location};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::parameters::find_params::FindParams; use geoffrey_models::models::parameters::find_params::FindParams;
use geoffrey_models::models::parameters::CommandRequest; use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel; use geoffrey_models::models::CommandLevel;
use std::sync::Arc; use std::sync::Arc;
use geoffrey_db::helper::load_location;
pub struct FindCommand {} pub struct FindCommand {}
@ -27,17 +29,29 @@ impl Command for FindCommand {
} }
fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp> { fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp> {
let locations = ctx
.db
.filter(|_, loc: &Location| {
let name = loc.name.to_lowercase();
let query = req.arguments.query.to_lowercase(); let query = req.arguments.query.to_lowercase();
let players: Vec<u64> = ctx
.db
.filter(|_, player: &Player| {
let player_name = player.name.to_lowercase();
name.contains(&query) player_name.contains(&query)
}) })?
.map_err(|err| GeoffreyAPIError::DatabaseError(err.to_string()))? .map(|player| player.id.unwrap())
.collect(); .collect();
Ok(locations) let locations: Vec<LocationDb> = ctx
.db
.filter(|_, loc: &LocationDb| {
let name = loc.name.to_lowercase();
name.contains(&query) || loc.owners().iter().any(|owner_id| players.contains(owner_id))
}).map_err(|err| GeoffreyAPIError::from(err))?.collect();
let locations: Result<Vec<Location>> = locations.iter().map(|loc| load_location(&ctx.db, loc).map_err(|err| GeoffreyAPIError::from(err))).collect();
locations
} }
} }

View File

@ -12,7 +12,8 @@ pub enum LogLevel {
impl From<&str> for LogLevel { impl From<&str> for LogLevel {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
match s { let s = s.to_lowercase();
match s.as_str() {
"warn" | "w" => LogLevel::Warn, "warn" | "w" => LogLevel::Warn,
"info" | "i" => LogLevel::Info, "info" | "i" => LogLevel::Info,
"debug" | "d" => LogLevel::Debug, "debug" | "d" => LogLevel::Debug,

View File

@ -107,13 +107,14 @@ impl Database {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::database::Database; use crate::database::Database;
use geoffrey_models::models::locations::{Location, LocationType}; use geoffrey_models::models::locations::{LocationDb, LocationDataDb};
use geoffrey_models::models::player::{Player, UserID}; use geoffrey_models::models::player::{Player, UserID};
use geoffrey_models::models::{Dimension, Position}; use geoffrey_models::models::{Dimension, Position};
use geoffrey_models::GeoffreyDatabaseModel; use geoffrey_models::GeoffreyDatabaseModel;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::path::Path; use std::path::Path;
use std::time::Instant; use std::time::Instant;
use geoffrey_models::models::locations::shop::Shop;
lazy_static! { lazy_static! {
static ref DB: Database = Database::new(Path::new("../test_database")).unwrap(); static ref DB: Database = Database::new(Path::new("../test_database")).unwrap();
@ -121,12 +122,14 @@ mod tests {
fn cleanup() { fn cleanup() {
DB.clear_tree::<Player>().unwrap(); DB.clear_tree::<Player>().unwrap();
DB.clear_tree::<Location>().unwrap(); DB.clear_tree::<LocationDb>().unwrap();
DB.db.clear().unwrap();
DB.db.flush().unwrap(); DB.db.flush().unwrap();
} }
#[test] #[test]
fn test_insert() { fn test_insert() {
cleanup();
let player = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 }); let player = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 });
let p2 = DB.insert::<Player>(player.clone()).unwrap(); let p2 = DB.insert::<Player>(player.clone()).unwrap();
@ -139,6 +142,7 @@ mod tests {
#[test] #[test]
fn test_unique_insert() { fn test_unique_insert() {
cleanup();
let player1 = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 }); let player1 = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 });
let player2 = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 }); let player2 = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 });
@ -150,6 +154,7 @@ mod tests {
#[test] #[test]
fn test_get() { fn test_get() {
cleanup();
let player = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 }); let player = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 });
let p2 = DB.insert::<Player>(player.clone()).unwrap(); let p2 = DB.insert::<Player>(player.clone()).unwrap();
@ -162,19 +167,22 @@ mod tests {
#[test] #[test]
fn test_filter() { fn test_filter() {
cleanup();
let player = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 }); let player = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 });
let player = DB.insert::<Player>(player.clone()).unwrap(); let player = DB.insert::<Player>(player.clone()).unwrap();
let loc = Location::new( let loc = LocationDb::new(
"Test Shop", "Test Shop",
Position::new(0, 0, Dimension::Overworld), Position::new(0, 0, Dimension::Overworld),
player.id.unwrap(), player.id.unwrap(),
None, None,
LocationType::Shop(0), LocationDataDb::Shop(Shop {
item_listings: Default::default()
}),
); );
let loc = DB.insert::<Location>(loc.clone()).unwrap(); let loc = DB.insert::<LocationDb>(loc.clone()).unwrap();
let count = DB let count = DB
.filter(|id: u64, l: &Location| { .filter(|id: u64, l: &LocationDb| {
assert_eq!(id, l.id().unwrap()); assert_eq!(id, l.id().unwrap());
loc.id().unwrap() == id loc.id().unwrap() == id
}) })

View File

@ -0,0 +1,39 @@
use geoffrey_models::models::locations::{LocationDb, LocationDataDb, LocationData, Location};
use crate::database::Database;
use geoffrey_models::models::player::Player;
use crate::error::Result;
use geoffrey_models::models::locations::town::Town;
use geoffrey_models::models::locations::market::Market;
pub fn load_location_data(db: &Database, data: &LocationDataDb) -> Result<LocationData> {
Ok(match data {
LocationDataDb::Base => LocationData::Base,
LocationDataDb::Shop(shop_data) => LocationData::Shop(shop_data.clone()),
LocationDataDb::Attraction => LocationData::Attraction,
LocationDataDb::Town(town_data) => {
let town_data = Town {
residents: db.filter(|id, _: &Player| town_data.residents.contains(&id))?.collect()
};
LocationData::Town(town_data)
}
LocationDataDb::Farm(farm_data) => LocationData::Farm(farm_data.clone()),
LocationDataDb::Market(market_data) => {
let shops: Result<Vec<Location>> = db.filter(|id, _: &LocationDb| market_data.shops.contains(&id))?.map(|loc| load_location(db, &loc)).collect();
let market_data = Market {
is_public: market_data.is_public,
shops: shops?
};
LocationData::Market(market_data)
}
})
}
pub fn load_location(db: &Database, location: &LocationDb) -> Result<Location> {
let owners: Vec<Player> = db.filter(|id, _: &Player| location.owners().contains(&id))?.collect();
let loc_data = load_location_data(db, &location.loc_data)?;
Ok(Location::from_db_location(location.clone(), owners, loc_data))
}

View File

@ -2,3 +2,4 @@
pub mod database; pub mod database;
pub mod error; pub mod error;
pub mod helper;

View File

@ -1,24 +1,8 @@
use crate::models::item::Item; use crate::models::item::Item;
use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FarmData { pub struct FarmData {
id: Option<u64>,
pub items_produced: HashSet<Item>, pub items_produced: HashSet<Item>,
} }
impl GeoffreyDatabaseModel for FarmData {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id)
}
fn tree() -> String {
"farm".to_string()
}
}

View File

@ -1,23 +1,15 @@
use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
use crate::models::locations::Location;
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Market { pub struct MarketDb {
pub id: Option<u64>, pub is_public: bool,
pub shops: HashSet<u64>, pub shops: HashSet<u64>,
} }
impl GeoffreyDatabaseModel for Market { #[derive(Serialize, Deserialize, Debug, Clone, Default)]
fn id(&self) -> Option<u64> { pub struct Market {
self.id pub is_public: bool,
} pub shops: Vec<Location>,
fn set_id(&mut self, id: u64) {
self.id = Some(id)
}
fn tree() -> String {
"market".to_string()
}
} }

View File

@ -3,39 +3,91 @@ use std::collections::HashSet;
use crate::models::{Position, Tunnel}; use crate::models::{Position, Tunnel};
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
use crate::models::locations::shop::Shop;
use crate::models::locations::town::{TownDb, Town};
use crate::models::locations::farm::FarmData;
use crate::models::locations::market::{MarketDb, Market};
use crate::models::player::Player;
pub mod farm; pub mod farm;
pub mod market; pub mod market;
pub mod shop; pub mod shop;
pub mod town; pub mod town;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum LocationType { pub enum LocationType {
Base, Base,
Shop(u64), Shop,
Attraction, Attraction,
Town(u64), Town,
Farm(u64), Farm,
Market(u64), Market,
}
impl From<LocationDataDb> for LocationType {
fn from(l: LocationDataDb) -> Self {
match l {
LocationDataDb::Base => LocationType::Base,
LocationDataDb::Shop(_) => LocationType::Shop,
LocationDataDb::Attraction => LocationType::Attraction,
LocationDataDb::Town(_) => LocationType::Town,
LocationDataDb::Farm(_) => LocationType::Farm,
LocationDataDb::Market(_) => LocationType::Market,
}
}
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Location { pub enum LocationDataDb {
Base,
Shop(Shop),
Attraction,
Town(TownDb),
Farm(FarmData),
Market(MarketDb),
}
impl From<LocationType> for LocationDataDb {
fn from(t: LocationType) -> Self {
match t {
LocationType::Base => LocationDataDb::Base,
LocationType::Shop => LocationDataDb::Shop(Default::default()),
LocationType::Attraction => LocationDataDb::Attraction,
LocationType::Town => LocationDataDb::Town(Default::default()),
LocationType::Farm => LocationDataDb::Farm(Default::default()),
LocationType::Market => LocationDataDb::Market(Default::default())
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum LocationData {
Base,
Shop(Shop),
Attraction,
Town(Town),
Farm(FarmData),
Market(Market),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LocationDb {
id: Option<u64>, id: Option<u64>,
pub name: String, pub name: String,
pub position: Position, pub position: Position,
owners: HashSet<u64>, owners: HashSet<u64>,
pub tunnel: Option<Tunnel>, pub tunnel: Option<Tunnel>,
loc_type: LocationType, pub loc_data: LocationDataDb,
} }
impl Location { impl LocationDb {
pub fn new( pub fn new(
name: &str, name: &str,
position: Position, position: Position,
owner: u64, owner: u64,
tunnel: Option<Tunnel>, tunnel: Option<Tunnel>,
loc_type: LocationType, loc_type: LocationDataDb,
) -> Self { ) -> Self {
let mut owners = HashSet::new(); let mut owners = HashSet::new();
owners.insert(owner); owners.insert(owner);
@ -46,7 +98,7 @@ impl Location {
position, position,
owners, owners,
tunnel, tunnel,
loc_type, loc_data: loc_type,
} }
} }
@ -63,7 +115,7 @@ impl Location {
} }
} }
impl GeoffreyDatabaseModel for Location { impl GeoffreyDatabaseModel for LocationDb {
fn id(&self) -> Option<u64> { fn id(&self) -> Option<u64> {
self.id self.id
} }
@ -81,44 +133,68 @@ impl GeoffreyDatabaseModel for Location {
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Location {
pub id: u64,
pub name: String,
pub position: Position,
pub owners: Vec<Player>,
pub tunnel: Option<Tunnel>,
pub loc_data: LocationData,
}
impl Location {
pub fn from_db_location(location: LocationDb, owners: Vec<Player>, loc_data: LocationData) -> Self {
Self {
id: location.id.unwrap(),
name: location.name,
position: location.position,
owners,
tunnel: location.tunnel,
loc_data
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::models::locations::{Location, LocationType}; use crate::models::locations::{LocationDb, LocationDataDb};
use crate::models::{Dimension, Position}; use crate::models::{Dimension, Position};
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
#[test] #[test]
fn test_location_check_unique() { fn test_location_check_unique() {
let l1 = Location::new( let l1 = LocationDb::new(
"Test", "Test",
Position::new(0, 0, Dimension::Overworld), Position::new(0, 0, Dimension::Overworld),
0u64, 0u64,
None, None,
LocationType::Base, LocationDataDb::Base,
); );
let l2 = Location::new( let l2 = LocationDb::new(
"NotTest", "NotTest",
Position::new(0, 0, Dimension::Overworld), Position::new(0, 0, Dimension::Overworld),
0u64, 0u64,
None, None,
LocationType::Base, LocationDataDb::Base,
); );
assert!(l1.check_unique(&l2)); assert!(l1.check_unique(&l2));
let l1 = Location::new( let l1 = LocationDb::new(
"Test", "Test",
Position::new(0, 0, Dimension::Overworld), Position::new(0, 0, Dimension::Overworld),
0u64, 0u64,
None, None,
LocationType::Base, LocationDataDb::Base,
); );
let l2 = Location::new( let l2 = LocationDb::new(
"teSt", "teSt",
Position::new(0, 0, Dimension::Overworld), Position::new(0, 0, Dimension::Overworld),
0u64, 0u64,
None, None,
LocationType::Base, LocationDataDb::Base,
); );
assert!(!l1.check_unique(&l2)); assert!(!l1.check_unique(&l2));

View File

@ -3,24 +3,8 @@ use std::collections::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::models::item::ItemListing; use crate::models::item::ItemListing;
use crate::GeoffreyDatabaseModel;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Shop { pub struct Shop {
id: Option<u64>,
pub item_listings: HashSet<ItemListing>, pub item_listings: HashSet<ItemListing>,
} }
impl GeoffreyDatabaseModel for Shop {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id);
}
fn tree() -> String {
"shop".to_string()
}
}

View File

@ -1,23 +1,13 @@
use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
use crate::models::player::Player;
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Town { pub struct TownDb {
id: Option<u64>,
pub residents: HashSet<u64>, pub residents: HashSet<u64>,
} }
impl GeoffreyDatabaseModel for Town { #[derive(Serialize, Deserialize, Debug, Clone, Default)]
fn id(&self) -> Option<u64> { pub struct Town {
self.id pub residents: Vec<Player>,
}
fn set_id(&mut self, id: u64) {
self.id = Some(id)
}
fn tree() -> String {
"town".to_string()
}
} }

View File

@ -1,4 +1,4 @@
use crate::models::locations::LocationType; use crate::models::locations::{LocationType};
use crate::models::{Position, Tunnel}; use crate::models::{Position, Tunnel};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -57,19 +57,19 @@ impl GeoffreyDatabaseModel for Player {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::models::player::Player; use crate::models::player::{Player, UserID};
use crate::models::player::UserID::{DiscordUUID, MinecraftUUID}; use crate::models::player::UserID::{DiscordUUID, MinecraftUUID};
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
#[test] #[test]
fn test_player_check_unique() { fn test_player_check_unique() {
let p1 = Player::new("CoolTest123", DiscordUUID(0u64)); let p1 = Player::new("CoolTest123", UserID::DiscordUUID {discord_uuid: 0u64});
let p2 = Player::new("NotCoolTest123", DiscordUUID(1u64)); let p2 = Player::new("NotCoolTest123", UserID::DiscordUUID {discord_uuid: 1u64});
assert!(p1.check_unique(&p2)); assert!(p1.check_unique(&p2));
let p1 = Player::new("CoolTest123", MinecraftUUID("0".to_string())); let p1 = Player::new("CoolTest123", UserID::MinecraftUUID{mc_uuid: "0".to_string()});
let p2 = Player::new("NotCoolTest123", MinecraftUUID("0".to_string())); let p2 = Player::new("NotCoolTest123", UserID::MinecraftUUID{ mc_uuid: "0".to_string()});
assert!(!p1.check_unique(&p2)); assert!(!p1.check_unique(&p2));
} }