Implemented sorting an ordering for selling

+ Added parameter validation as well
+ Currently, Restock and Price are the two sorting methods available
main
Joey Hines 2021-11-14 16:28:55 -07:00
parent ebaf28e39e
commit e1b362aa1c
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
6 changed files with 70 additions and 5 deletions

View File

@ -28,6 +28,14 @@ impl Command for AddItem {
CommandLevel::REGISTERED CommandLevel::REGISTERED
} }
fn validate_parameters(req: &Self::Req) -> Result<()> {
if req.quantity == 0 {
Err(GeoffreyAPIError::ParameterInvalid("quantity".to_string()))
} else {
Ok(())
}
}
fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp> { fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp> {
let user = user.unwrap(); let user = user.unwrap();

View File

@ -42,6 +42,10 @@ pub trait Command {
fn command_level() -> CommandLevel; fn command_level() -> CommandLevel;
fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp>; fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp>;
fn validate_parameters(_: &Self::Req) -> Result<()> {
Ok(())
}
fn user_is_authorized(token: &Option<Token>, user: &Option<Player>) -> Result<()> { fn user_is_authorized(token: &Option<Token>, user: &Option<Player>) -> Result<()> {
if let Some(token) = token { if let Some(token) = token {
if !match Self::command_level() { if !match Self::command_level() {
@ -77,7 +81,10 @@ pub fn handle_command<T: Command>(ctx: Arc<Context>, req: T::Req) -> Result<T::R
let token = get_token_from_req(&ctx.db, &req)?; let token = get_token_from_req(&ctx.db, &req)?;
match T::user_is_authorized(&token, &user) { match T::user_is_authorized(&token, &user) {
Ok(_) => T::run_command(ctx, req, user), Ok(_) => {
T::validate_parameters(&req)?;
T::run_command(ctx, req, user)
}
Err(e) => Err(e), Err(e) => Err(e),
} }
} }

View File

@ -1,8 +1,9 @@
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::item::ItemListing;
use geoffrey_models::models::locations::{LocationDataDb, LocationDb}; use geoffrey_models::models::locations::{LocationDataDb, LocationDb};
use geoffrey_models::models::parameters::selling_params::SellingParams; use geoffrey_models::models::parameters::selling_params::{ItemSort, Order, SellingParams};
use geoffrey_models::models::player::Player; use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::selling_listing::SellingListing; use geoffrey_models::models::response::selling_listing::SellingListing;
use geoffrey_models::models::CommandLevel; use geoffrey_models::models::CommandLevel;
@ -27,7 +28,7 @@ impl Command for Selling {
} }
fn run_command(ctx: Arc<Context>, req: Self::Req, _: Option<Player>) -> Result<Self::Resp> { fn run_command(ctx: Arc<Context>, req: Self::Req, _: Option<Player>) -> Result<Self::Resp> {
let shops: Vec<SellingListing> = ctx let mut listings: Vec<SellingListing> = ctx
.db .db
.filter(|_, loc: &LocationDb| { .filter(|_, loc: &LocationDb| {
if let LocationDataDb::Shop(shop_data) = loc.loc_data.clone() { if let LocationDataDb::Shop(shop_data) = loc.loc_data.clone() {
@ -68,7 +69,31 @@ impl Command for Selling {
.flatten() .flatten()
.collect(); .collect();
Ok(shops) let sort = req.sort.unwrap_or(ItemSort::Restock);
match sort {
ItemSort::Price => {
listings.sort_by(|a, b| ItemListing::order_by_price(&a.listing, &b.listing))
}
ItemSort::Restock => {
listings.sort_by(|a, b| ItemListing::order_by_restock(&a.listing, &b.listing))
}
}
let ordering = if let Some(order) = req.order {
order
} else {
match sort {
ItemSort::Price => Order::Low,
ItemSort::Restock => Order::High,
}
};
if ordering == Order::High {
listings.reverse();
}
Ok(listings)
} }
} }

View File

@ -1,5 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct Item { pub struct Item {
@ -33,4 +34,24 @@ impl ItemListing {
restocked_time: Utc::now(), restocked_time: Utc::now(),
} }
} }
pub fn normalized_price(&self) -> f32 {
if self.count_per_price == 0 {
f32::MAX
} else {
(self.price as f32) / (self.count_per_price as f32)
}
}
pub fn order_by_price(a: &Self, b: &Self) -> Ordering {
// Bit of a hack so we can using integer ordering
let a_price = (a.normalized_price() * 1000.0) as u64;
let b_price = (b.normalized_price() * 1000.0) as u64;
a_price.cmp(&b_price)
}
pub fn order_by_restock(a: &Self, b: &Self) -> Ordering {
a.restocked_time.cmp(&b.restocked_time)
}
} }

View File

@ -8,7 +8,7 @@ pub enum ItemSort {
Restock, Restock,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum Order { pub enum Order {
High, High,
Low, Low,

View File

@ -10,6 +10,7 @@ pub enum GeoffreyAPIError {
DatabaseError(String), DatabaseError(String),
TokenNotAuthorized, TokenNotAuthorized,
MultipleLocationsMatch, MultipleLocationsMatch,
ParameterInvalid(String),
} }
impl Display for GeoffreyAPIError { impl Display for GeoffreyAPIError {
@ -30,6 +31,9 @@ impl Display for GeoffreyAPIError {
GeoffreyAPIError::MultipleLocationsMatch => { GeoffreyAPIError::MultipleLocationsMatch => {
"The location query returned multiple locations.".to_string() "The location query returned multiple locations.".to_string()
} }
GeoffreyAPIError::ParameterInvalid(param) => {
format!("Parameter \"{}\" is invalid", param)
}
}; };
write!(f, "{}", string) write!(f, "{}", string)
} }