report_out_of_stock command + new query system
continuous-integration/woodpecker the build was successful
Details
continuous-integration/woodpecker the build was successful
Details
+ report_out_of_stock can be used by a when an item is not in stock in a shop + Added new query system + Impl for both LocationDB and Player for now + Goal is to increase code re-use without a million functions for different queries + Should be expanded to more models + I Should really just rip out the DB and make it a generic thing + Clippy + fmtmain
parent
928e59a700
commit
c6ec8467c1
|
@ -3,9 +3,10 @@ use crate::config::GeoffreyAPIConfig;
|
|||
use crate::context::Context;
|
||||
use crate::helper::validate_string_parameter;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::{find_location_by_name_type, load_location};
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::item::ItemListing;
|
||||
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationType};
|
||||
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationDb, LocationType};
|
||||
use geoffrey_models::models::parameters::add_item_params::AddItemParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||
|
@ -33,8 +34,16 @@ impl Command for AddItem {
|
|||
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
|
||||
let user = user.unwrap();
|
||||
|
||||
let mut shop =
|
||||
find_location_by_name_type(&ctx.db, &req.shop, user.id.unwrap(), LocationType::Shop)?;
|
||||
let query = QueryBuilder::<LocationDb>::default()
|
||||
.with_type(LocationType::Shop)
|
||||
.with_owner(user.id.unwrap())
|
||||
.with_name(&req.shop);
|
||||
|
||||
let mut shop = ctx
|
||||
.db
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data {
|
||||
shop_data.item_listings.insert(ItemListing::new(
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::{find_location_by_name, load_location};
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::locations::{Location, LocationDb};
|
||||
use geoffrey_models::models::parameters::delete_params::DeleteParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
|
@ -30,7 +31,16 @@ impl Command for Delete {
|
|||
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
|
||||
let user = user.unwrap();
|
||||
|
||||
let location = find_location_by_name(&ctx.db, &req.location, user.id.unwrap())?;
|
||||
let query = QueryBuilder::<LocationDb>::default()
|
||||
.with_owner(user.id.unwrap())
|
||||
.with_name(&req.location);
|
||||
|
||||
let location = ctx
|
||||
.db
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
let location = load_location(&ctx.db, &location).map_err(GeoffreyAPIError::from)?;
|
||||
|
||||
ctx.db.remove::<LocationDb>(location.id)?;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::{find_location_by_name, load_location};
|
||||
use geoffrey_models::models::locations::Location;
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::locations::{Location, LocationDb};
|
||||
use geoffrey_models::models::parameters::edit_params::EditParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||
use geoffrey_models::models::CommandLevel;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -29,7 +31,15 @@ impl Command for Edit {
|
|||
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
|
||||
let user = user.unwrap();
|
||||
|
||||
let mut location = find_location_by_name(&ctx.db, &req.loc_name, user.id.unwrap())?;
|
||||
let query = QueryBuilder::<LocationDb>::default()
|
||||
.with_owner(user.id.unwrap())
|
||||
.with_name(&req.loc_name);
|
||||
|
||||
let mut location = ctx
|
||||
.db
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
if let Some(new_pos) = &req.new_pos {
|
||||
location.position = *new_pos;
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::commands::{Command, RequestType};
|
|||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::locations::{Location, LocationDb};
|
||||
use geoffrey_models::models::parameters::info_params::InfoParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
|
@ -28,17 +29,12 @@ impl Command for InfoCommand {
|
|||
}
|
||||
|
||||
fn run_command(ctx: Arc<Context>, req: &Self::Req, _: Option<Player>) -> Result<Self::Resp> {
|
||||
let query = req.location_name.to_lowercase();
|
||||
let query = QueryBuilder::<LocationDb>::default().with_name_regex(&req.location_name)?;
|
||||
|
||||
let location: LocationDb = ctx
|
||||
let location = ctx
|
||||
.db
|
||||
.filter(|_, loc: &LocationDb| {
|
||||
let name = loc.name.to_lowercase();
|
||||
|
||||
name == query
|
||||
})
|
||||
.map_err(GeoffreyAPIError::from)?
|
||||
.next()
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
Ok(load_location(&ctx.db, &location)?)
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::commands::info::InfoCommand;
|
|||
use crate::commands::link::LinkCommand;
|
||||
use crate::commands::register::Register;
|
||||
use crate::commands::remove_item::RemoveItem;
|
||||
use crate::commands::report_out_of_stock::ReportOutOfStock;
|
||||
use crate::commands::restock::Restock;
|
||||
use crate::commands::selling::Selling;
|
||||
use crate::commands::set_portal::SetPortal;
|
||||
|
@ -37,6 +38,7 @@ pub mod info;
|
|||
pub mod link;
|
||||
pub mod register;
|
||||
pub mod remove_item;
|
||||
pub mod report_out_of_stock;
|
||||
pub mod restock;
|
||||
pub mod selling;
|
||||
pub mod set_portal;
|
||||
|
@ -147,6 +149,7 @@ pub fn command_filter(
|
|||
.or(create_command_filter::<RemoveItem>(ctx.clone()))
|
||||
.or(create_command_filter::<InfoCommand>(ctx.clone()))
|
||||
.or(create_command_filter::<Restock>(ctx.clone()))
|
||||
.or(create_command_filter::<ReportOutOfStock>(ctx.clone()))
|
||||
.or(create_command_filter::<SetPortal>(ctx)),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::{find_location_by_name_type, load_location};
|
||||
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationType};
|
||||
use geoffrey_models::models::parameters::remove_item_params::RemoveItemParameters;
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationDb, LocationType};
|
||||
use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||
use geoffrey_models::models::CommandLevel;
|
||||
|
@ -12,7 +13,7 @@ use std::sync::Arc;
|
|||
pub struct RemoveItem {}
|
||||
|
||||
impl Command for RemoveItem {
|
||||
type Req = RemoveItemParameters;
|
||||
type Req = ItemCommandParams;
|
||||
type Resp = Location;
|
||||
|
||||
fn command_name() -> String {
|
||||
|
@ -30,12 +31,16 @@ impl Command for RemoveItem {
|
|||
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
|
||||
let user = user.unwrap();
|
||||
|
||||
let mut shop = find_location_by_name_type(
|
||||
&ctx.db,
|
||||
&req.shop_name,
|
||||
user.id.unwrap(),
|
||||
LocationType::Shop,
|
||||
)?;
|
||||
let query = QueryBuilder::<LocationDb>::default()
|
||||
.with_type(LocationType::Shop)
|
||||
.with_name(&req.shop_name)
|
||||
.with_owner(user.id.unwrap());
|
||||
|
||||
let mut shop = ctx
|
||||
.db
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data {
|
||||
shop_data
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::item::ItemListing;
|
||||
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationDb, LocationType};
|
||||
use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||
use geoffrey_models::models::CommandLevel;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ReportOutOfStock {}
|
||||
|
||||
impl Command for ReportOutOfStock {
|
||||
type Req = ItemCommandParams;
|
||||
type Resp = Location;
|
||||
|
||||
fn command_name() -> String {
|
||||
"report_out_of_stock".to_string()
|
||||
}
|
||||
|
||||
fn request_type() -> RequestType {
|
||||
RequestType::POST
|
||||
}
|
||||
|
||||
fn command_level() -> CommandLevel {
|
||||
CommandLevel::REGISTERED
|
||||
}
|
||||
|
||||
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
|
||||
let user = user.unwrap();
|
||||
|
||||
let query = QueryBuilder::<LocationDb>::default()
|
||||
.with_type(LocationType::Shop)
|
||||
.with_name(&req.shop_name);
|
||||
|
||||
let mut shop = ctx
|
||||
.db
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data {
|
||||
let filter = regex::Regex::new(&req.item_name)
|
||||
.map_err(|e| GeoffreyAPIError::InvalidRegex(e.to_string()))?;
|
||||
|
||||
let updated_items: HashSet<ItemListing> = shop_data
|
||||
.item_listings
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let mut item = item.clone();
|
||||
if filter.is_match(&item.item.name) {
|
||||
item.report_out_of_stock(user.id.unwrap())
|
||||
}
|
||||
|
||||
item
|
||||
})
|
||||
.collect();
|
||||
|
||||
shop_data.item_listings = updated_items;
|
||||
let shop = ctx.db.insert(shop)?;
|
||||
|
||||
load_location(&ctx.db, &shop).map_err(|err| err.into())
|
||||
} else {
|
||||
Err(GeoffreyAPIError::EntryNotFound)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::{find_location_by_name_type, load_location};
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::item::ItemListing;
|
||||
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationType};
|
||||
use geoffrey_models::models::parameters::restock_params::RestockParameters;
|
||||
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationDb, LocationType};
|
||||
use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||
use geoffrey_models::models::CommandLevel;
|
||||
|
@ -14,7 +15,7 @@ use std::sync::Arc;
|
|||
pub struct Restock {}
|
||||
|
||||
impl Command for Restock {
|
||||
type Req = RestockParameters;
|
||||
type Req = ItemCommandParams;
|
||||
type Resp = Location;
|
||||
|
||||
fn command_name() -> String {
|
||||
|
@ -32,16 +33,20 @@ impl Command for Restock {
|
|||
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
|
||||
let user = user.unwrap();
|
||||
|
||||
let mut shop = find_location_by_name_type(
|
||||
&ctx.db,
|
||||
&req.shop_name,
|
||||
user.id.unwrap(),
|
||||
LocationType::Shop,
|
||||
)?;
|
||||
let query = QueryBuilder::<LocationDb>::default()
|
||||
.with_type(LocationType::Shop)
|
||||
.with_name(&req.shop_name)
|
||||
.with_owner(user.id.unwrap());
|
||||
|
||||
let mut shop = ctx
|
||||
.db
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data {
|
||||
let filter = regex::Regex::new(&req.item_name)
|
||||
.map_err(|_| GeoffreyAPIError::ParameterInvalid("item_name".to_string()))?;
|
||||
.map_err(|e| GeoffreyAPIError::InvalidRegex(e.to_string()))?;
|
||||
|
||||
let updated_items: HashSet<ItemListing> = shop_data
|
||||
.item_listings
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::{find_location_by_name, load_location};
|
||||
use geoffrey_models::models::locations::Location;
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_db::query::QueryBuilder;
|
||||
use geoffrey_models::models::locations::{Location, LocationDb};
|
||||
use geoffrey_models::models::parameters::set_portal_params::SetPortalParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||
use geoffrey_models::models::CommandLevel;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -29,7 +31,15 @@ impl Command for SetPortal {
|
|||
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
|
||||
let user = user.unwrap();
|
||||
|
||||
let mut location = find_location_by_name(&ctx.db, &req.loc_name, user.id.unwrap())?;
|
||||
let query = QueryBuilder::<LocationDb>::default()
|
||||
.with_name(&req.loc_name)
|
||||
.with_owner(user.id.unwrap());
|
||||
|
||||
let mut location = ctx
|
||||
.db
|
||||
.run_query(query)?
|
||||
.pop()
|
||||
.ok_or(GeoffreyAPIError::EntryNotFound)?;
|
||||
|
||||
location.portal = Some(req.portal.clone());
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use serenity::model::interactions::application_command::{
|
|||
};
|
||||
|
||||
use geoffrey_models::models::locations::Location;
|
||||
use geoffrey_models::models::parameters::remove_item_params::RemoveItemParameters;
|
||||
use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
|
||||
|
||||
use crate::bot::arg_parse::option_to_string;
|
||||
use crate::bot::commands::{BotCommand, CommandError};
|
||||
|
@ -18,7 +18,7 @@ pub struct RemoveItemCommand;
|
|||
|
||||
#[async_trait]
|
||||
impl BotCommand for RemoveItemCommand {
|
||||
type ApiParams = RemoveItemParameters;
|
||||
type ApiParams = ItemCommandParams;
|
||||
type ApiResp = Location;
|
||||
|
||||
fn command_name() -> String {
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::bot::arg_parse::option_to_string;
|
|||
use crate::bot::commands::{BotCommand, CommandError};
|
||||
use crate::bot::formatters::display_loc_full;
|
||||
use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_SHOP;
|
||||
use geoffrey_models::models::parameters::restock_params::RestockParameters;
|
||||
use geoffrey_models::models::parameters::item_command_params::ItemCommandParams;
|
||||
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||
use serenity::builder::CreateApplicationCommand;
|
||||
|
||||
|
@ -18,7 +18,7 @@ pub struct RestockCommand;
|
|||
|
||||
#[async_trait]
|
||||
impl BotCommand for RestockCommand {
|
||||
type ApiParams = RestockParameters;
|
||||
type ApiParams = ItemCommandParams;
|
||||
type ApiResp = Location;
|
||||
|
||||
fn command_name() -> String {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::error::{GeoffreyDBError, Result};
|
||||
use crate::migration::do_migration;
|
||||
use crate::query::QueryBuilder;
|
||||
use geoffrey_models::models::db_metadata::DBMetadata;
|
||||
use geoffrey_models::GeoffreyDatabaseModel;
|
||||
use sled::IVec;
|
||||
|
@ -113,6 +114,31 @@ impl Database {
|
|||
}))
|
||||
}
|
||||
|
||||
pub fn run_query<T>(&self, query_builder: QueryBuilder<T>) -> Result<Vec<T>>
|
||||
where
|
||||
T: GeoffreyDatabaseModel,
|
||||
{
|
||||
let result: Vec<T> = self
|
||||
.filter(|id, loc: &T| {
|
||||
for query in &query_builder.queries {
|
||||
let res = query(id, loc);
|
||||
|
||||
if !res {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
})?
|
||||
.collect();
|
||||
|
||||
if result.is_empty() {
|
||||
Err(GeoffreyDBError::NotFound)
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove<T>(&self, id: u64) -> Result<T>
|
||||
where
|
||||
T: GeoffreyDatabaseModel,
|
||||
|
@ -145,29 +171,14 @@ impl Database {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::database::Database;
|
||||
use crate::test::{cleanup, DB, LOCK};
|
||||
use geoffrey_models::models::locations::shop::Shop;
|
||||
use geoffrey_models::models::locations::{LocationDataDb, LocationDb};
|
||||
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::sync::Mutex;
|
||||
use std::time::Instant;
|
||||
|
||||
lazy_static! {
|
||||
static ref DB: Database = Database::new(Path::new("../test_database")).unwrap();
|
||||
static ref LOCK: Mutex<()> = Mutex::default();
|
||||
}
|
||||
|
||||
fn cleanup() {
|
||||
DB.clear_tree::<Player>().unwrap();
|
||||
DB.clear_tree::<LocationDb>().unwrap();
|
||||
DB.db.clear().unwrap();
|
||||
DB.db.flush().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert() {
|
||||
let _lock = LOCK.lock().unwrap();
|
||||
|
|
|
@ -8,6 +8,7 @@ pub enum GeoffreyDBError {
|
|||
SerdeJsonError(serde_json::Error),
|
||||
NotUnique,
|
||||
NotFound,
|
||||
RegexError(regex::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for GeoffreyDBError {}
|
||||
|
@ -19,6 +20,7 @@ impl std::fmt::Display for GeoffreyDBError {
|
|||
GeoffreyDBError::SerdeJsonError(e) => write!(f, "Serde JSON Error: {}", e),
|
||||
GeoffreyDBError::NotUnique => write!(f, "Entry is not unique."),
|
||||
GeoffreyDBError::NotFound => write!(f, "Entry was not found."),
|
||||
GeoffreyDBError::RegexError(e) => write!(f, "Regex Error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +37,12 @@ impl From<serde_json::Error> for GeoffreyDBError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<regex::Error> for GeoffreyDBError {
|
||||
fn from(e: regex::Error) -> Self {
|
||||
Self::RegexError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GeoffreyDBError> for GeoffreyAPIError {
|
||||
fn from(e: GeoffreyDBError) -> Self {
|
||||
match e {
|
||||
|
@ -42,6 +50,7 @@ impl From<GeoffreyDBError> for GeoffreyAPIError {
|
|||
GeoffreyDBError::SerdeJsonError(_) => GeoffreyAPIError::DatabaseError(e.to_string()),
|
||||
GeoffreyDBError::NotUnique => GeoffreyAPIError::EntryNotUnique,
|
||||
GeoffreyDBError::NotFound => GeoffreyAPIError::EntryNotFound,
|
||||
GeoffreyDBError::RegexError(_) => GeoffreyAPIError::InvalidRegex(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,3 +4,26 @@ pub mod database;
|
|||
pub mod error;
|
||||
pub mod helper;
|
||||
pub(crate) mod migration;
|
||||
pub mod query;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::database::Database;
|
||||
use geoffrey_models::models::locations::LocationDb;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use lazy_static::lazy_static;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DB: Database = Database::new(Path::new("../test_database")).unwrap();
|
||||
pub static ref LOCK: Mutex<()> = Mutex::default();
|
||||
}
|
||||
|
||||
pub fn cleanup() {
|
||||
DB.clear_tree::<Player>().unwrap();
|
||||
DB.clear_tree::<LocationDb>().unwrap();
|
||||
DB.db.clear().unwrap();
|
||||
DB.db.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
use crate::error::Result;
|
||||
use crate::query::QueryBuilder;
|
||||
use geoffrey_models::models::locations::{LocationDb, LocationType};
|
||||
|
||||
impl QueryBuilder<LocationDb> {
|
||||
pub fn with_owner(self, owner_id: u64) -> Self {
|
||||
self.add_query_clause(Box::new(move |_: u64, loc: &LocationDb| {
|
||||
loc.owners().contains(&owner_id)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn with_name_regex(self, exp: &str) -> Result<Self> {
|
||||
let filter = regex::Regex::new(exp)?;
|
||||
|
||||
Ok(
|
||||
self.add_query_clause(Box::new(move |_: u64, loc: &LocationDb| {
|
||||
filter.is_match(&loc.name)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_name(self, name: &str) -> Self {
|
||||
let name = name.to_lowercase();
|
||||
self.add_query_clause(Box::new(move |_: u64, loc: &LocationDb| {
|
||||
loc.name.to_lowercase() == name
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn with_type(self, loc_type: LocationType) -> Self {
|
||||
self.add_query_clause(Box::new(move |_: u64, loc: &LocationDb| {
|
||||
let next_loc_type: LocationType = loc.loc_data.clone().into();
|
||||
|
||||
loc_type == next_loc_type
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::query::location_query::QueryBuilder;
|
||||
use crate::test::{cleanup, DB, LOCK};
|
||||
use geoffrey_models::models::locations::shop::Shop;
|
||||
use geoffrey_models::models::locations::{LocationDataDb, LocationDb, LocationType};
|
||||
use geoffrey_models::models::player::{Player, UserID};
|
||||
use geoffrey_models::models::Position;
|
||||
|
||||
fn setup_db() -> (Player, LocationDb, LocationDb) {
|
||||
let player = DB
|
||||
.insert(Player::new("Test", UserID::DiscordUUID { discord_uuid: 5 }))
|
||||
.unwrap();
|
||||
let base = DB
|
||||
.insert(LocationDb::new(
|
||||
"Test Base",
|
||||
Position::default(),
|
||||
player.id.unwrap(),
|
||||
None,
|
||||
LocationDataDb::Base,
|
||||
))
|
||||
.unwrap();
|
||||
let shop = DB
|
||||
.insert(LocationDb::new(
|
||||
"Test Shop",
|
||||
Position::default(),
|
||||
player.id.unwrap(),
|
||||
None,
|
||||
LocationDataDb::Shop(Shop::default()),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
(player, base, shop)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_owner_query() {
|
||||
let _lock = LOCK.lock().unwrap();
|
||||
cleanup();
|
||||
let (player, base, _) = setup_db();
|
||||
|
||||
let query: QueryBuilder<LocationDb> = QueryBuilder::new();
|
||||
|
||||
let query = query.with_owner(55);
|
||||
|
||||
assert!(DB.run_query(query).is_err());
|
||||
|
||||
let query: QueryBuilder<LocationDb> = QueryBuilder::new();
|
||||
let query = query.with_owner(player.id.unwrap());
|
||||
|
||||
let locations: Vec<LocationDb> = DB.run_query(query).unwrap();
|
||||
assert_eq!(locations.len(), 2);
|
||||
assert_eq!(locations[0].name, base.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_type_query() {
|
||||
let _lock = LOCK.lock().unwrap();
|
||||
cleanup();
|
||||
let (_, base, shop) = setup_db();
|
||||
|
||||
let query: QueryBuilder<LocationDb> = QueryBuilder::new();
|
||||
|
||||
let query = query.with_type(LocationType::Base);
|
||||
|
||||
let locations: Vec<LocationDb> = DB.run_query(query).unwrap();
|
||||
assert_eq!(locations.len(), 1);
|
||||
assert_eq!(locations[0].name, base.name);
|
||||
|
||||
let query: QueryBuilder<LocationDb> = QueryBuilder::new();
|
||||
let query = query.with_type(LocationType::Shop);
|
||||
|
||||
let locations: Vec<LocationDb> = DB.run_query(query).unwrap();
|
||||
assert_eq!(locations.len(), 1);
|
||||
assert_eq!(locations[0].name, shop.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_name_query() {
|
||||
let _lock = LOCK.lock().unwrap();
|
||||
cleanup();
|
||||
let (_, base, _) = setup_db();
|
||||
|
||||
let query: QueryBuilder<LocationDb> = QueryBuilder::new();
|
||||
|
||||
let query = query.with_name("Test Base");
|
||||
|
||||
let locations: Vec<LocationDb> = DB.run_query(query).unwrap();
|
||||
assert_eq!(locations.len(), 1);
|
||||
assert_eq!(locations[0].name, base.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_name_regex_query() {
|
||||
let _lock = LOCK.lock().unwrap();
|
||||
cleanup();
|
||||
let (_, base, _) = setup_db();
|
||||
|
||||
let query: QueryBuilder<LocationDb> = QueryBuilder::new();
|
||||
|
||||
let query = query.with_name_regex("Test").unwrap();
|
||||
|
||||
let locations: Vec<LocationDb> = DB.run_query(query).unwrap();
|
||||
assert_eq!(locations.len(), 2);
|
||||
assert_eq!(locations[0].name, base.name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use geoffrey_models::GeoffreyDatabaseModel;
|
||||
|
||||
pub mod location_query;
|
||||
pub mod player_query;
|
||||
|
||||
pub type GeoffreyDBQuery<T> = Box<dyn Fn(u64, &T) -> bool>;
|
||||
|
||||
pub struct QueryBuilder<T: GeoffreyDatabaseModel> {
|
||||
pub queries: Vec<GeoffreyDBQuery<T>>,
|
||||
}
|
||||
|
||||
impl<T: GeoffreyDatabaseModel> Default for QueryBuilder<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GeoffreyDatabaseModel> QueryBuilder<T> {
|
||||
pub fn new() -> Self {
|
||||
QueryBuilder {
|
||||
queries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_query_clause(mut self, clause: GeoffreyDBQuery<T>) -> Self {
|
||||
self.queries.push(clause);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(self, id: u64) -> Self {
|
||||
self.add_query_clause(Box::new(move |entry_id, _| entry_id == id))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
use crate::error::Result;
|
||||
use crate::query::QueryBuilder;
|
||||
use geoffrey_models::models::player::{Player, UserID};
|
||||
|
||||
impl QueryBuilder<Player> {
|
||||
pub fn with_name_regex(self, exp: &str) -> Result<Self> {
|
||||
let filter = regex::Regex::new(exp)?;
|
||||
|
||||
Ok(
|
||||
self.add_query_clause(Box::new(move |_: u64, player: &Player| {
|
||||
filter.is_match(&player.name)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_name(self, name: &str) -> Self {
|
||||
let name = name.to_lowercase();
|
||||
self.add_query_clause(Box::new(move |_: u64, player: &Player| {
|
||||
player.name.to_lowercase() == name
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn with_user_id(self, user_id: UserID) -> Self {
|
||||
self.add_query_clause(Box::new(move |_: u64, player: &Player| {
|
||||
player.has_user_id(&user_id)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::query::QueryBuilder;
|
||||
use crate::test::{cleanup, DB, LOCK};
|
||||
use geoffrey_models::models::player::{Player, UserID};
|
||||
|
||||
fn setup_db() -> (Player, Player, Player) {
|
||||
let player1 = DB
|
||||
.insert(Player::new(
|
||||
"ZeroHD",
|
||||
UserID::DiscordUUID { discord_uuid: 0 },
|
||||
))
|
||||
.unwrap();
|
||||
let player2 = DB
|
||||
.insert(Player::new(
|
||||
"Vakbezel",
|
||||
UserID::MinecraftUUID {
|
||||
mc_uuid: "0000".to_string(),
|
||||
},
|
||||
))
|
||||
.unwrap();
|
||||
let player3 = DB
|
||||
.insert(Player::new(
|
||||
"Etzelia",
|
||||
UserID::DiscordUUID { discord_uuid: 5 },
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
(player1, player2, player3)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_name() {
|
||||
let _lock = LOCK.lock().unwrap();
|
||||
cleanup();
|
||||
let (player1, player2, player3) = setup_db();
|
||||
|
||||
let query: QueryBuilder<Player> = QueryBuilder::new();
|
||||
let query = query.with_name("ZeroHD");
|
||||
assert_eq!(DB.run_query(query).unwrap()[0].name, player1.name);
|
||||
|
||||
let query: QueryBuilder<Player> = QueryBuilder::new();
|
||||
let query = query.with_name("Vakbezel");
|
||||
assert_eq!(DB.run_query(query).unwrap()[0].name, player2.name);
|
||||
|
||||
let query: QueryBuilder<Player> = QueryBuilder::new();
|
||||
let query = query.with_name("Etzelia");
|
||||
assert_eq!(DB.run_query(query).unwrap()[0].name, player3.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_user_id() {
|
||||
let _lock = LOCK.lock().unwrap();
|
||||
cleanup();
|
||||
let (player1, player2, player3) = setup_db();
|
||||
|
||||
let query: QueryBuilder<Player> = QueryBuilder::new();
|
||||
let query = query.with_user_id(UserID::DiscordUUID { discord_uuid: 0 });
|
||||
assert_eq!(DB.run_query(query).unwrap()[0].name, player1.name);
|
||||
|
||||
let query: QueryBuilder<Player> = QueryBuilder::new();
|
||||
let query = query.with_user_id(UserID::MinecraftUUID {
|
||||
mc_uuid: "0000".to_string(),
|
||||
});
|
||||
let query = query.with_name("Vakbezel");
|
||||
assert_eq!(DB.run_query(query).unwrap()[0].name, player2.name);
|
||||
|
||||
let query: QueryBuilder<Player> = QueryBuilder::new();
|
||||
let query = query.with_user_id(UserID::DiscordUUID { discord_uuid: 5 });
|
||||
assert_eq!(DB.run_query(query).unwrap()[0].name, player3.name);
|
||||
}
|
||||
}
|
|
@ -2,12 +2,12 @@ use crate::models::parameters::GeoffreyParam;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RestockParameters {
|
||||
pub struct ItemCommandParams {
|
||||
pub shop_name: String,
|
||||
pub item_name: String,
|
||||
}
|
||||
|
||||
impl RestockParameters {
|
||||
impl ItemCommandParams {
|
||||
pub fn new(shop_name: String, item_name: String) -> Self {
|
||||
Self {
|
||||
shop_name,
|
||||
|
@ -16,4 +16,4 @@ impl RestockParameters {
|
|||
}
|
||||
}
|
||||
|
||||
impl GeoffreyParam for RestockParameters {}
|
||||
impl GeoffreyParam for ItemCommandParams {}
|
|
@ -5,10 +5,9 @@ pub mod delete_params;
|
|||
pub mod edit_params;
|
||||
pub mod find_params;
|
||||
pub mod info_params;
|
||||
pub mod item_command_params;
|
||||
pub mod link_params;
|
||||
pub mod register_params;
|
||||
pub mod remove_item_params;
|
||||
pub mod restock_params;
|
||||
pub mod selling_params;
|
||||
pub mod set_portal_params;
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
use crate::models::parameters::GeoffreyParam;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RemoveItemParameters {
|
||||
pub shop_name: String,
|
||||
pub item_name: String,
|
||||
}
|
||||
|
||||
impl RemoveItemParameters {
|
||||
pub fn new(shop_name: String, item_name: String) -> Self {
|
||||
Self {
|
||||
shop_name,
|
||||
item_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GeoffreyParam for RemoveItemParameters {}
|
|
@ -13,6 +13,7 @@ pub enum GeoffreyAPIError {
|
|||
ParameterInvalid(String),
|
||||
PlayerRegistrationWithoutMCUUID,
|
||||
AccountLinkInvalid,
|
||||
InvalidRegex(String),
|
||||
}
|
||||
|
||||
impl Display for GeoffreyAPIError {
|
||||
|
@ -42,6 +43,9 @@ impl Display for GeoffreyAPIError {
|
|||
GeoffreyAPIError::AccountLinkInvalid => {
|
||||
"The supplied account link code is invalid".to_string()
|
||||
}
|
||||
GeoffreyAPIError::InvalidRegex(err) => {
|
||||
format!("Invalid regex supplied: {}", err)
|
||||
}
|
||||
};
|
||||
write!(f, "{}", string)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue