First pass on shop/item handling
+ The `add_item` command adds items to a location of type `Shop` + `selling` allows items to be queried + Items are not sorted yet + The user will be able to apply optional sorting params + Allowed the `insert` function of the DB to be used to update a model already in the DB + Clippy + Fmtmain
parent
22ffe75422
commit
8e8798f509
|
@ -0,0 +1,67 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_db::helper::load_location;
|
||||
use geoffrey_models::models::item::ItemListing;
|
||||
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;
|
||||
use geoffrey_models::models::CommandLevel;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct AddItem {}
|
||||
|
||||
impl Command for AddItem {
|
||||
type Req = AddItemParams;
|
||||
type Resp = Location;
|
||||
|
||||
fn command_name() -> String {
|
||||
"add_item".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 shops: Vec<LocationDb> = ctx
|
||||
.db
|
||||
.filter(|_, loc: &LocationDb| {
|
||||
let loc_type: LocationType = loc.loc_data.clone().into();
|
||||
|
||||
loc_type == LocationType::Shop
|
||||
&& loc.owners().contains(&user.id.unwrap())
|
||||
&& loc.name.to_lowercase().contains(&req.shop.to_lowercase())
|
||||
})?
|
||||
.collect();
|
||||
|
||||
if shops.is_empty() {
|
||||
Err(GeoffreyAPIError::EntryNotFound)
|
||||
} else if shops.len() > 1 {
|
||||
Err(GeoffreyAPIError::MultipleLocationsMatch)
|
||||
} else {
|
||||
let mut shop = shops[0].clone();
|
||||
|
||||
if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data {
|
||||
shop_data.item_listings.insert(ItemListing::new(
|
||||
&req.item_name,
|
||||
req.price,
|
||||
req.quantity,
|
||||
));
|
||||
|
||||
let shop = ctx.db.insert(shop)?;
|
||||
|
||||
load_location(&ctx.db, &shop).map_err(|err| err.into())
|
||||
} else {
|
||||
Err(GeoffreyAPIError::EntryNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
use crate::commands::add_item::AddItem;
|
||||
use crate::commands::add_location::AddLocation;
|
||||
use crate::commands::find::FindCommand;
|
||||
use crate::commands::register::Register;
|
||||
use crate::commands::selling::Selling;
|
||||
use crate::context::Context;
|
||||
use crate::helper::{get_player_from_req, get_token_from_req};
|
||||
use crate::Result;
|
||||
|
@ -17,10 +19,12 @@ use std::sync::Arc;
|
|||
use warp::filters::BoxedFilter;
|
||||
use warp::Filter;
|
||||
|
||||
pub mod add_item;
|
||||
pub mod add_location;
|
||||
pub mod add_token;
|
||||
pub mod find;
|
||||
pub mod register;
|
||||
pub mod selling;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
|
@ -109,6 +113,8 @@ pub fn command_filter(
|
|||
warp::path("command").and(
|
||||
create_command_filter::<FindCommand>(ctx.clone())
|
||||
.or(create_command_filter::<AddLocation>(ctx.clone()))
|
||||
.or(create_command_filter::<Register>(ctx)),
|
||||
.or(create_command_filter::<Register>(ctx.clone()))
|
||||
.or(create_command_filter::<Selling>(ctx.clone()))
|
||||
.or(create_command_filter::<AddItem>(ctx)),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
use crate::commands::{Command, RequestType};
|
||||
use crate::context::Context;
|
||||
use crate::Result;
|
||||
use geoffrey_models::models::locations::{LocationDataDb, LocationDb};
|
||||
use geoffrey_models::models::parameters::selling_params::SellingParams;
|
||||
use geoffrey_models::models::player::Player;
|
||||
use geoffrey_models::models::response::selling_listing::SellingListing;
|
||||
use geoffrey_models::models::CommandLevel;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Selling {}
|
||||
|
||||
impl Command for Selling {
|
||||
type Req = SellingParams;
|
||||
type Resp = Vec<SellingListing>;
|
||||
|
||||
fn command_name() -> String {
|
||||
"selling".to_string()
|
||||
}
|
||||
|
||||
fn request_type() -> RequestType {
|
||||
RequestType::GET
|
||||
}
|
||||
|
||||
fn command_level() -> CommandLevel {
|
||||
CommandLevel::ALL
|
||||
}
|
||||
|
||||
fn run_command(ctx: Arc<Context>, req: Self::Req, _: Option<Player>) -> Result<Self::Resp> {
|
||||
let shops: Vec<SellingListing> = ctx
|
||||
.db
|
||||
.filter(|_, loc: &LocationDb| {
|
||||
if let LocationDataDb::Shop(shop_data) = loc.loc_data.clone() {
|
||||
shop_data.item_listings.iter().any(|item| {
|
||||
item.item
|
||||
.name
|
||||
.to_lowercase()
|
||||
.contains(&req.query.to_lowercase())
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})?
|
||||
.filter_map(|shop: LocationDb| {
|
||||
if let LocationDataDb::Shop(shop_data) = shop.clone().loc_data {
|
||||
let listings: Vec<SellingListing> = shop_data
|
||||
.item_listings
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
if item.item.name.to_lowercase().contains(&req.query) {
|
||||
Some(SellingListing {
|
||||
listing: item.clone(),
|
||||
shop_name: shop.name.clone(),
|
||||
shop_loc: shop.position,
|
||||
portal: shop.tunnel.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(listings)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Ok(shops)
|
||||
}
|
||||
}
|
|
@ -34,7 +34,9 @@ impl Database {
|
|||
}
|
||||
};
|
||||
|
||||
let match_count = self.filter(|_, o: &T| !o.check_unique(&model))?.count();
|
||||
let match_count = self
|
||||
.filter(|o_id, o: &T| o_id != id && !o.check_unique(&model))?
|
||||
.count();
|
||||
|
||||
if match_count > 0 {
|
||||
log::debug!("{} is not unique: {:?}", T::tree(), model);
|
||||
|
|
|
@ -23,14 +23,14 @@ pub struct ItemListing {
|
|||
}
|
||||
|
||||
impl ItemListing {
|
||||
fn new(item: &str, price: u32, count_per_price: u32, restocked_time: DateTime<Utc>) -> Self {
|
||||
pub fn new(item: &str, price: u32, count_per_price: u32) -> Self {
|
||||
Self {
|
||||
item: Item {
|
||||
name: item.to_string(),
|
||||
},
|
||||
price,
|
||||
count_per_price,
|
||||
restocked_time,
|
||||
restocked_time: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ pub mod market;
|
|||
pub mod shop;
|
||||
pub mod town;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialOrd, PartialEq)]
|
||||
pub enum LocationType {
|
||||
Base,
|
||||
Shop,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
use crate::models::parameters::CommandRequest;
|
||||
use crate::models::player::UserID;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AddItemParams {
|
||||
pub token: String,
|
||||
pub user_id: UserID,
|
||||
pub item_name: String,
|
||||
pub price: u32,
|
||||
pub quantity: u32,
|
||||
pub shop: String,
|
||||
}
|
||||
|
||||
impl CommandRequest for AddItemParams {
|
||||
fn token(&self) -> String {
|
||||
self.token.clone()
|
||||
}
|
||||
|
||||
fn user_id(&self) -> Option<UserID> {
|
||||
Some(self.user_id.clone())
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
pub mod add_item_params;
|
||||
pub mod add_location_params;
|
||||
pub mod add_token_params;
|
||||
pub mod find_params;
|
||||
pub mod register_params;
|
||||
pub mod selling_params;
|
||||
|
||||
use crate::models::player::{Player, UserID};
|
||||
use crate::models::token::{Permissions, Token};
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
use crate::models::parameters::CommandRequest;
|
||||
use crate::models::player::UserID;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum ItemSort {
|
||||
Price,
|
||||
Restock,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum Order {
|
||||
High,
|
||||
Low,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SellingParams {
|
||||
pub token: String,
|
||||
pub query: String,
|
||||
pub sort: Option<ItemSort>,
|
||||
pub order: Option<Order>,
|
||||
}
|
||||
|
||||
impl CommandRequest for SellingParams {
|
||||
fn token(&self) -> String {
|
||||
self.token.clone()
|
||||
}
|
||||
|
||||
fn user_id(&self) -> Option<UserID> {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ pub enum GeoffreyAPIError {
|
|||
EntryNotUnique,
|
||||
DatabaseError(String),
|
||||
TokenNotAuthorized,
|
||||
MultipleLocationsMatch,
|
||||
}
|
||||
|
||||
impl Display for GeoffreyAPIError {
|
||||
|
@ -26,6 +27,9 @@ impl Display for GeoffreyAPIError {
|
|||
GeoffreyAPIError::TokenNotAuthorized => {
|
||||
"Token supplied in request is not authorized".to_string()
|
||||
}
|
||||
GeoffreyAPIError::MultipleLocationsMatch => {
|
||||
"The location query returned multiple locations.".to_string()
|
||||
}
|
||||
};
|
||||
write!(f, "{}", string)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::models::response::api_error::GeoffreyAPIError;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod api_error;
|
||||
pub mod selling_listing;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum APIResponse<T> {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
use crate::models::item::ItemListing;
|
||||
use crate::models::{Position, Tunnel};
|
||||
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 listing: ItemListing,
|
||||
}
|
Loading…
Reference in New Issue