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

View File

@ -1,12 +1,14 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
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::CommandRequest;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
use geoffrey_db::helper::load_location;
pub struct FindCommand {}
@ -27,17 +29,29 @@ impl Command for FindCommand {
}
fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp> {
let locations = ctx
let query = req.arguments.query.to_lowercase();
let players: Vec<u64> = ctx
.db
.filter(|_, loc: &Location| {
let name = loc.name.to_lowercase();
let query = req.arguments.query.to_lowercase();
.filter(|_, player: &Player| {
let player_name = player.name.to_lowercase();
name.contains(&query)
})
.map_err(|err| GeoffreyAPIError::DatabaseError(err.to_string()))?
player_name.contains(&query)
})?
.map(|player| player.id.unwrap())
.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 {
fn from(s: &str) -> Self {
match s {
let s = s.to_lowercase();
match s.as_str() {
"warn" | "w" => LogLevel::Warn,
"info" | "i" => LogLevel::Info,
"debug" | "d" => LogLevel::Debug,

View File

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

View File

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

View File

@ -3,39 +3,91 @@ use std::collections::HashSet;
use crate::models::{Position, Tunnel};
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 market;
pub mod shop;
pub mod town;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum LocationType {
Base,
Shop(u64),
Shop,
Attraction,
Town(u64),
Farm(u64),
Market(u64),
Town,
Farm,
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)]
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>,
pub name: String,
pub position: Position,
owners: HashSet<u64>,
pub tunnel: Option<Tunnel>,
loc_type: LocationType,
pub loc_data: LocationDataDb,
}
impl Location {
impl LocationDb {
pub fn new(
name: &str,
position: Position,
owner: u64,
tunnel: Option<Tunnel>,
loc_type: LocationType,
loc_type: LocationDataDb,
) -> Self {
let mut owners = HashSet::new();
owners.insert(owner);
@ -46,7 +98,7 @@ impl Location {
position,
owners,
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> {
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)]
mod tests {
use crate::models::locations::{Location, LocationType};
use crate::models::locations::{LocationDb, LocationDataDb};
use crate::models::{Dimension, Position};
use crate::GeoffreyDatabaseModel;
#[test]
fn test_location_check_unique() {
let l1 = Location::new(
let l1 = LocationDb::new(
"Test",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
LocationDataDb::Base,
);
let l2 = Location::new(
let l2 = LocationDb::new(
"NotTest",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
LocationDataDb::Base,
);
assert!(l1.check_unique(&l2));
let l1 = Location::new(
let l1 = LocationDb::new(
"Test",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
LocationDataDb::Base,
);
let l2 = Location::new(
let l2 = LocationDb::new(
"teSt",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
LocationDataDb::Base,
);
assert!(!l1.check_unique(&l2));

View File

@ -3,24 +3,8 @@ use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use crate::models::item::ItemListing;
use crate::GeoffreyDatabaseModel;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Shop {
id: Option<u64>,
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 std::collections::HashSet;
use crate::models::player::Player;
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Town {
id: Option<u64>,
pub struct TownDb {
pub residents: HashSet<u64>,
}
impl GeoffreyDatabaseModel for Town {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id)
}
fn tree() -> String {
"town".to_string()
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Town {
pub residents: Vec<Player>,
}

View File

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

View File

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