Skip to content

Commit

Permalink
feat: align bestiary list api with shop (#83)
Browse files Browse the repository at this point in the history
* align bestiary listing API with shop, moving to POST and allowing array for filters (multiple filters values for one filter)
* keep old bestiary listing GET but marking as deprecated (will be removed in a few months)
  • Loading branch information
RakuJa authored Dec 9, 2024
1 parent 212a8b1 commit e674ecc
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 98 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ path = "src/main.rs"

[lints.rust]
unsafe_code = "forbid"
deprecated = "allow"

[dependencies]
actix-web = "4.9.0"
Expand Down
51 changes: 27 additions & 24 deletions src/models/creature/creature_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,17 @@ impl Creature {
}

fn check_creature_pass_equality_filters(&self, filters: &CreatureFieldFilters) -> bool {
filters
.rarity_filter
.as_ref()
.map_or(true, |rarity| self.core_data.essential.rarity == *rarity)
&& filters
.size_filter
.as_ref()
.map_or(true, |size| self.core_data.essential.size == *size)
&& filters.alignment_filter.as_ref().map_or(true, |alignment| {
self.core_data.essential.alignment == *alignment
})
&& filters
.is_melee_filter
.map_or(true, |is_melee| self.core_data.derived.is_melee == is_melee)
filters.rarity_filter.as_ref().map_or(true, |x| {
x.iter()
.any(|rarity| self.core_data.essential.rarity == *rarity)
}) && filters.size_filter.as_ref().map_or(true, |x| {
x.iter().any(|size| self.core_data.essential.size == *size)
}) && filters.alignment_filter.as_ref().map_or(true, |x| {
x.iter()
.any(|align| self.core_data.essential.alignment == *align)
}) && filters
.is_melee_filter
.map_or(true, |is_melee| self.core_data.derived.is_melee == is_melee)
&& filters.is_ranged_filter.map_or(true, |is_ranged| {
self.core_data.derived.is_ranged == is_ranged
})
Expand All @@ -142,14 +139,14 @@ impl Creature {
.map_or(true, |is_spell_caster| {
self.core_data.derived.is_spell_caster == is_spell_caster
})
&& filters
.type_filter
.as_ref()
.map_or(true, |cr_type| self.core_data.essential.cr_type == *cr_type)
&& filters.type_filter.as_ref().map_or(true, |x| {
x.iter()
.any(|cr_type| self.core_data.essential.cr_type == *cr_type)
})
&& (filters.role_threshold.is_none()
|| filters.role_filter.as_ref().map_or(true, |cr_role| {
let t = filters.role_threshold.unwrap_or(0);
match cr_role {
cr_role.iter().any(|role| match role {
CreatureRoleEnum::Brute => self.core_data.derived.brute_percentage >= t,
CreatureRoleEnum::MagicalStriker => {
self.core_data.derived.magical_striker_percentage >= t
Expand All @@ -165,7 +162,7 @@ impl Creature {
CreatureRoleEnum::SpellCaster => {
self.core_data.derived.spell_caster_percentage >= t
}
}
})
}))
&& match filters.pathfinder_version.clone().unwrap_or_default() {
PathfinderVersionEnum::Legacy => !self.core_data.essential.remaster,
Expand All @@ -176,13 +173,19 @@ impl Creature {

fn check_creature_pass_string_filters(&self, filters: &CreatureFieldFilters) -> bool {
filters.name_filter.as_ref().map_or(true, |name| {
self.core_data.essential.name.to_lowercase().contains(name)
}) && filters.family_filter.as_ref().map_or(true, |name| {
self.core_data
.essential
.family
.name
.to_lowercase()
.contains(name)
.contains(name.to_lowercase().as_str())
}) && filters.family_filter.as_ref().map_or(true, |x| {
x.iter().any(|fam| {
self.core_data
.essential
.family
.to_lowercase()
.contains(fam.to_lowercase().as_str())
})
})
}
}
11 changes: 6 additions & 5 deletions src/models/item/item_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ impl Item {
.type_filter
.as_ref()
.map_or(true, |x| x.iter().any(|t_filt| self.item_type == *t_filt))
&& match filters.pathfinder_version.clone().unwrap_or_default() {
PathfinderVersionEnum::Legacy => !self.remaster,
PathfinderVersionEnum::Remaster => self.remaster,
PathfinderVersionEnum::Any => true,
}
}

fn check_item_pass_string_filters(&self, filters: &ItemFieldFilters) -> bool {
Expand All @@ -171,11 +176,7 @@ impl Item {
.to_lowercase()
.contains(cat.to_lowercase().as_str())
})
}) && match filters.pathfinder_version.clone().unwrap_or_default() {
PathfinderVersionEnum::Legacy => !self.remaster,
PathfinderVersionEnum::Remaster => self.remaster,
PathfinderVersionEnum::Any => true,
} && filters.source_filter.as_ref().map_or(true, |x| {
}) && filters.source_filter.as_ref().map_or(true, |x| {
x.iter().any(|source| {
self.source
.to_lowercase()
Expand Down
26 changes: 13 additions & 13 deletions src/models/routers_validator_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ use crate::models::shared::size_enum::SizeEnum;
use serde::{Deserialize, Serialize};
use strum::Display;
use utoipa::{IntoParams, ToSchema};
#[derive(Serialize, Deserialize, IntoParams)]
#[derive(Serialize, Deserialize, IntoParams, ToSchema)]
pub struct CreatureFieldFilters {
pub name_filter: Option<String>,
pub source_filter: Option<String>,
pub family_filter: Option<String>,
pub rarity_filter: Option<RarityEnum>,
pub size_filter: Option<SizeEnum>,
pub alignment_filter: Option<AlignmentEnum>,
pub role_filter: Option<CreatureRoleEnum>,
pub type_filter: Option<CreatureTypeEnum>,
#[param(minimum = 0, maximum = 100, example = 50)]
pub source_filter: Option<Vec<String>>,
pub family_filter: Option<Vec<String>>,
pub rarity_filter: Option<Vec<RarityEnum>>,
pub size_filter: Option<Vec<SizeEnum>>,
pub alignment_filter: Option<Vec<AlignmentEnum>>,
pub role_filter: Option<Vec<CreatureRoleEnum>>,
pub type_filter: Option<Vec<CreatureTypeEnum>>,
#[schema(minimum = 0, maximum = 100, example = 50)]
pub role_threshold: Option<i64>,
#[param(minimum = 0, example = 0)]
#[schema(minimum = 0, example = 0)]
pub min_hp_filter: Option<i64>,
#[param(minimum = 0, example = 100)]
#[schema(minimum = 0, example = 100)]
pub max_hp_filter: Option<i64>,
#[param(minimum = -1, example = -1)]
#[schema(minimum = -1, example = -1)]
pub min_level_filter: Option<i64>,
#[param(minimum = -1, example = 5)]
#[schema(minimum = -1, example = 5)]
pub max_level_filter: Option<i64>,
pub is_melee_filter: Option<bool>,
pub is_ranged_filter: Option<bool>,
Expand Down
77 changes: 55 additions & 22 deletions src/routes/bestiary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ use crate::models::db::sense::Sense;
use crate::models::routers_validator_structs::{CreatureFieldFilters, PaginatedRequest};
use crate::services::bestiary_service;
use crate::services::bestiary_service::BestiaryResponse;
use crate::services::sanitizer::sanitize_id;
use crate::AppState;
use actix_web::web::Query;
use actix_web::{error, get, web, Responder, Result};
use actix_web::{get, post, web, Responder};
use utoipa::OpenApi;

pub fn init_endpoints(cfg: &mut web::ServiceConfig) {
Expand All @@ -61,6 +62,7 @@ pub fn init_docs(doc: &mut utoipa::openapi::OpenApi) {
#[openapi(
paths(
get_bestiary,
get_bestiary_listing,
get_families_list,
get_traits_list,
get_sources_list,
Expand Down Expand Up @@ -122,14 +124,15 @@ pub fn init_docs(doc: &mut utoipa::openapi::OpenApi) {
),
)]
#[get("/list")]
#[deprecated(since = "2.4.0", note = "please use `get_bestiary_listing` instead")]
pub async fn get_bestiary(
data: web::Data<AppState>,
filters: Query<CreatureFieldFilters>,
pagination: Query<PaginatedRequest>,
sort_data: Query<BestiarySortData>,
) -> Result<impl Responder> {
) -> actix_web::Result<impl Responder> {
Ok(web::Json(
bestiary_service::get_bestiary(
bestiary_service::get_bestiary_listing(
&data,
&filters.0,
&BestiaryPaginatedRequest {
Expand All @@ -141,6 +144,42 @@ pub async fn get_bestiary(
))
}

#[utoipa::path(
post,
path = "/bestiary/list",
tag = "bestiary",
request_body(
content = CreatureFieldFilters,
content_type = "application/json"
),
params(
PaginatedRequest, BestiarySortData
),
responses(
(status=200, description = "Successful Response", body = BestiaryResponse),
(status=400, description = "Bad request.")
),
)]
#[post("/list")]
pub async fn get_bestiary_listing(
data: web::Data<AppState>,
web::Json(body): web::Json<CreatureFieldFilters>,
pagination: Query<PaginatedRequest>,
sort_data: Query<BestiarySortData>,
) -> actix_web::Result<impl Responder> {
Ok(web::Json(
bestiary_service::get_bestiary_listing(
&data,
&body,
&BestiaryPaginatedRequest {
paginated_request: pagination.0,
bestiary_sort_data: sort_data.0,
},
)
.await,
))
}

#[utoipa::path(
get,
path = "/bestiary/families",
Expand All @@ -154,7 +193,7 @@ pub async fn get_bestiary(
),
)]
#[get("/families")]
pub async fn get_families_list(data: web::Data<AppState>) -> Result<impl Responder> {
pub async fn get_families_list(data: web::Data<AppState>) -> actix_web::Result<impl Responder> {
Ok(web::Json(bestiary_service::get_families_list(&data).await))
}

Expand All @@ -171,7 +210,7 @@ pub async fn get_families_list(data: web::Data<AppState>) -> Result<impl Respond
),
)]
#[get("/traits")]
pub async fn get_traits_list(data: web::Data<AppState>) -> Result<impl Responder> {
pub async fn get_traits_list(data: web::Data<AppState>) -> actix_web::Result<impl Responder> {
Ok(web::Json(bestiary_service::get_traits_list(&data).await))
}

Expand All @@ -188,7 +227,7 @@ pub async fn get_traits_list(data: web::Data<AppState>) -> Result<impl Responder
),
)]
#[get("/sources")]
pub async fn get_sources_list(data: web::Data<AppState>) -> Result<impl Responder> {
pub async fn get_sources_list(data: web::Data<AppState>) -> actix_web::Result<impl Responder> {
Ok(web::Json(bestiary_service::get_sources_list(&data).await))
}

Expand All @@ -205,7 +244,7 @@ pub async fn get_sources_list(data: web::Data<AppState>) -> Result<impl Responde
),
)]
#[get("/rarities")]
pub async fn get_rarities_list(data: web::Data<AppState>) -> Result<impl Responder> {
pub async fn get_rarities_list(data: web::Data<AppState>) -> actix_web::Result<impl Responder> {
Ok(web::Json(bestiary_service::get_rarities_list(&data).await))
}

Expand All @@ -222,7 +261,7 @@ pub async fn get_rarities_list(data: web::Data<AppState>) -> Result<impl Respond
),
)]
#[get("/sizes")]
pub async fn get_sizes_list(data: web::Data<AppState>) -> Result<impl Responder> {
pub async fn get_sizes_list(data: web::Data<AppState>) -> actix_web::Result<impl Responder> {
Ok(web::Json(bestiary_service::get_sizes_list(&data).await))
}

Expand All @@ -239,7 +278,7 @@ pub async fn get_sizes_list(data: web::Data<AppState>) -> Result<impl Responder>
),
)]
#[get("/alignments")]
pub async fn get_alignments_list(data: web::Data<AppState>) -> Result<impl Responder> {
pub async fn get_alignments_list(data: web::Data<AppState>) -> actix_web::Result<impl Responder> {
Ok(web::Json(
bestiary_service::get_alignments_list(&data).await,
))
Expand All @@ -258,7 +297,9 @@ pub async fn get_alignments_list(data: web::Data<AppState>) -> Result<impl Respo
),
)]
#[get("/creature_types")]
pub async fn get_creature_types_list(data: web::Data<AppState>) -> Result<impl Responder> {
pub async fn get_creature_types_list(
data: web::Data<AppState>,
) -> actix_web::Result<impl Responder> {
Ok(web::Json(
bestiary_service::get_creature_types_list(&data).await,
))
Expand All @@ -277,7 +318,7 @@ pub async fn get_creature_types_list(data: web::Data<AppState>) -> Result<impl R
),
)]
#[get("/creature_roles")]
pub async fn get_creature_roles_list() -> Result<impl Responder> {
pub async fn get_creature_roles_list() -> actix_web::Result<impl Responder> {
Ok(web::Json(bestiary_service::get_creature_roles_list().await))
}

Expand All @@ -299,7 +340,7 @@ pub async fn get_creature(
data: web::Data<AppState>,
creature_id: web::Path<String>,
response_data_mods: Query<ResponseDataModifiers>,
) -> Result<impl Responder> {
) -> actix_web::Result<impl Responder> {
Ok(web::Json(
bestiary_service::get_creature(&data, sanitize_id(&creature_id)?, &response_data_mods.0)
.await,
Expand All @@ -324,7 +365,7 @@ pub async fn get_elite_creature(
data: web::Data<AppState>,
creature_id: web::Path<String>,
response_data_mods: Query<ResponseDataModifiers>,
) -> Result<impl Responder> {
) -> actix_web::Result<impl Responder> {
Ok(web::Json(
bestiary_service::get_elite_creature(
&data,
Expand Down Expand Up @@ -353,7 +394,7 @@ pub async fn get_weak_creature(
data: web::Data<AppState>,
creature_id: web::Path<String>,
response_data_mods: Query<ResponseDataModifiers>,
) -> Result<impl Responder> {
) -> actix_web::Result<impl Responder> {
Ok(web::Json(
bestiary_service::get_weak_creature(
&data,
Expand All @@ -363,11 +404,3 @@ pub async fn get_weak_creature(
.await,
))
}

fn sanitize_id(creature_id: &str) -> Result<i64> {
let id = creature_id.parse::<i64>();
match id {
Ok(s) => Ok(s),
Err(e) => Err(error::ErrorNotFound(e)),
}
}
11 changes: 2 additions & 9 deletions src/routes/shop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use crate::models::shop_structs::ShopTemplateData;
use crate::models::shop_structs::ShopTemplateEnum;
use crate::models::shop_structs::{ItemSortEnum, ShopPaginatedRequest};
use crate::models::shop_structs::{RandomShopData, ShopSortData};
use crate::services::sanitizer::sanitize_id;
use crate::services::shop_service;
use crate::services::shop_service::ShopListingResponse;
use crate::AppState;
use actix_web::web::Query;
use actix_web::{error, get, post, web, Responder};
use actix_web::{get, post, web, Responder};
use utoipa::OpenApi;

pub fn init_endpoints(cfg: &mut web::ServiceConfig) {
Expand Down Expand Up @@ -201,11 +202,3 @@ pub async fn get_items_traits_list(data: web::Data<AppState>) -> actix_web::Resu
pub async fn get_templates_data() -> actix_web::Result<impl Responder> {
Ok(web::Json(shop_service::get_shop_templates_data().await))
}

fn sanitize_id(creature_id: &str) -> actix_web::Result<i64> {
let id = creature_id.parse::<i64>();
match id {
Ok(s) => Ok(s),
Err(e) => Err(error::ErrorNotFound(e)),
}
}
2 changes: 1 addition & 1 deletion src/services/bestiary_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub async fn get_weak_creature(
}
}

pub async fn get_bestiary(
pub async fn get_bestiary_listing(
app_state: &AppState,
field_filter: &CreatureFieldFilters,
pagination: &BestiaryPaginatedRequest,
Expand Down
1 change: 1 addition & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod bestiary_service;
pub mod encounter_handler;
pub mod encounter_service;
pub mod sanitizer;
pub mod shop_service;
pub mod url_calculator;
Loading

0 comments on commit e674ecc

Please sign in to comment.