use diesel::prelude::*; use serde::{Deserialize, Serialize}; use crate::{db::{schema::spells::{self}, classes::AbilityType, conditions::ConditionType}, error_handler::ServiceError}; use super::{SchoolType, CastingTime, CastingType, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, Source, Description, DurationType}; #[derive(Queryable, QueryableByName)] #[diesel(table_name = spells)] pub struct QuerySpell { pub id: i32, pub name: String, pub school: String, pub level: i32, pub ritual: bool, pub concentration: bool, pub classes: Vec, pub damage_inflict: Vec, pub damage_resist: Vec, pub conditions: Vec, pub saving_throw: Vec, pub attack_type: Option, pub data: serde_json::Value, } #[derive(Debug)] pub struct QueryFilters { pub by_name: Option, pub by_schools: Option>, pub by_levels: Option>, pub by_ritual: Option, pub by_concentration: Option, pub by_classes: Option>, pub by_damage_inflict: Option>, pub by_damage_resist: Option>, pub by_conditions: Option>, pub by_saving_throw: Option>, pub by_attack_type: Option, } impl Default for QueryFilters { fn default() -> Self { Self { by_name: None, by_schools: None, by_levels: None, by_ritual: None, by_concentration: None, by_classes: None, by_damage_inflict: None, by_damage_resist: None, by_conditions: None, by_saving_throw: None, by_attack_type: None, } } } impl QuerySpell { pub fn get_all(filters: &QueryFilters, limit: i32, page: i32) -> Result, ServiceError> { let mut conn = crate::db::connection()?; let mut query = spells::table.limit(limit as i64).into_boxed(); // Limit query to page and limit let offset = (page - 1) * limit; query = query.offset(offset as i64); if let Some(name) = &filters.by_name { query = query.filter(spells::name.ilike(format!("%{}%", name))); } if let Some(schools) = &filters.by_schools { query = query.filter(spells::school.eq_any(schools.iter().map(|school| school.to_string()).collect::>())); } if let Some(levels) = &filters.by_levels { query = query.filter(spells::level.eq_any(levels)); } if let Some(ritual) = filters.by_ritual { query = query.filter(spells::ritual.eq(ritual)); } if let Some(concentration) = filters.by_concentration { query = query.filter(spells::concentration.eq(concentration)); } if let Some(classes) = &filters.by_classes { query = query.filter(spells::classes.overlaps_with(classes)); } if let Some(damage_inflict) = &filters.by_damage_inflict { query = query.filter(spells::damage_inflict.overlaps_with(damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect::>())); } if let Some(damage_resist) = &filters.by_damage_resist { query = query.filter(spells::damage_resist.overlaps_with(damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect::>())); } if let Some(conditions) = &filters.by_conditions { query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::>())); } if let Some(saving_throw) = &filters.by_saving_throw { query = query.filter(spells::saving_throw.overlaps_with(saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect::>())); } if let Some(attack_type) = &filters.by_attack_type { query = query.filter(spells::attack_type.eq(attack_type.to_string())); } let spells = query.load::(&mut conn)?; Ok(spells) } pub fn get_count(filters: &QueryFilters) -> Result { let mut conn = crate::db::connection()?; let mut query = spells::table.count().into_boxed(); if let Some(name) = &filters.by_name { query = query.filter(spells::name.ilike(format!("%{}%", name))); } if let Some(schools) = &filters.by_schools { query = query.filter(spells::school.eq_any(schools.iter().map(|school| school.to_string()).collect::>())); } if let Some(levels) = &filters.by_levels { query = query.filter(spells::level.eq_any(levels)); } if let Some(ritual) = filters.by_ritual { query = query.filter(spells::ritual.eq(ritual)); } if let Some(concentration) = filters.by_concentration { query = query.filter(spells::concentration.eq(concentration)); } if let Some(classes) = &filters.by_classes { query = query.filter(spells::classes.overlaps_with(classes)); } if let Some(damage_inflict) = &filters.by_damage_inflict { query = query.filter(spells::damage_inflict.overlaps_with(damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect::>())); } if let Some(damage_resist) = &filters.by_damage_resist { query = query.filter(spells::damage_resist.overlaps_with(damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect::>())); } if let Some(conditions) = &filters.by_conditions { query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::>())); } if let Some(saving_throw) = &filters.by_saving_throw { query = query.filter(spells::saving_throw.overlaps_with(saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect::>())); } if let Some(attack_type) = &filters.by_attack_type { query = query.filter(spells::attack_type.eq(attack_type.to_string())); } let count = query.get_result(&mut conn)?; Ok(count) } pub fn get_by_id(id: i32) -> Result { let mut conn = crate::db::connection()?; let spell = spells::table .filter(spells::id.eq(id)) .first::(&mut conn)?; Ok(spell) } pub fn delete(id: i32) -> Result { let mut conn = crate::db::connection()?; let spell = diesel::delete(spells::table.filter(spells::id.eq(id))).get_result(&mut conn)?; Ok(spell) } } #[derive(Insertable, AsChangeset)] #[diesel(table_name = spells)] pub struct InsertSpell { pub name: String, pub school: String, pub level: i32, pub ritual: bool, pub concentration: bool, pub classes: Vec, pub damage_inflict: Vec, pub damage_resist: Vec, pub conditions: Vec, pub saving_throw: Vec, pub attack_type: Option, pub data: serde_json::Value } impl InsertSpell { pub fn insert(spell: Self) -> Result { let mut conn = crate::db::connection()?; let spell = diesel::insert_into(spells::table).values(spell).get_result(&mut conn)?; Ok(spell) } pub fn update(id: i32, spell: Self) -> Result { let mut conn = crate::db::connection()?; let spell = diesel::update(spells::table.filter(spells::id.eq(id))).set(spell).get_result(&mut conn)?; Ok(spell) } } #[derive(Debug, Serialize, Deserialize)] pub struct Spell { pub name: String, pub school: SchoolType, pub level: i32, pub ritual: bool, pub casting_time: CastingTime, #[serde(skip_serializing_if = "Option::is_none")] pub saving_throw: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub attack_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub damage_inflict: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub damage_resist: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub conditions: Option>, pub range: Range, #[serde(skip_serializing_if = "Option::is_none")] pub area: Option, pub components: Components, pub durations: Vec, pub classes: Vec, pub sources: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub tags: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option } impl From for Spell { fn from(query: QuerySpell) -> Self { return match serde_json::from_value(query.data) { Ok(data) => data, Err(err) => { log::error!("Failed to parse spell: {}", err); Self { name: "".to_string(), school: SchoolType::Abjuration, level: 0, ritual: false, casting_time: CastingTime { amount: 0, casting_type: CastingType::Action }, saving_throw: None, attack_type: None, damage_inflict: None, damage_resist: None, conditions: None, range: Range { range_type: "".to_string(), amount: None, unit: None }, area: None, components: Components { verbal: false, somatic: false, material: false, materials_needed: None, materials_cost: None, materials_consumed: None }, durations: vec![], classes: vec![], sources: vec![], tags: None, description: None, } } } } } impl Into for Spell { fn into(self) -> InsertSpell { return InsertSpell { name: self.name.to_string(), school: self.school.to_string(), level: self.level, ritual: self.ritual, concentration: self.durations.iter().any(|duration| match duration.duration_type { DurationType::Concentration => true, _ => false }), classes: self.classes.iter().map(|class| class.to_string()).collect::>(), damage_inflict: match &self.damage_inflict { Some(damage_inflict) => damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect(), None => vec![] }, damage_resist: match &self.damage_resist { Some(damage_resist) => damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect(), None => vec![] }, conditions: match &self.conditions { Some(conditions) => conditions.iter().map(|condition| condition.to_string()).collect(), None => vec![] }, saving_throw: match &self.saving_throw { Some(saving_throw) => saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect(), None => vec![] }, attack_type: self.attack_type.as_ref().map(|attack_type| attack_type.to_string()), data: match serde_json::to_value(&self) { Ok(data) => data, Err(err) => { log::error!("Failed to serialize spell: {}", err); serde_json::Value::Null } } } } }