Updated query parameters for spells
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "self"
|
"type": "self"
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "touch"
|
"type": "touch"
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
@@ -315,7 +315,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
@@ -359,7 +359,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"type": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
@@ -391,5 +391,41 @@
|
|||||||
"The spell creates more than one beam when you reach higher levels: two beams at 5th level, three beams at 11th level, and four beams at 17th level. You can direct the beams at the same target or at different ones. Make a separate attack roll for each beam."
|
"The spell creates more than one beam when you reach higher levels: two beams at 5th level, three beams at 11th level, and four beams at 17th level. You can direct the beams at the same target or at different ones. Make a separate attack roll for each beam."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Encode Thoughts",
|
||||||
|
"school": "enchantment",
|
||||||
|
"level": 0,
|
||||||
|
"ritual": true,
|
||||||
|
"casting_time": {
|
||||||
|
"amount": 1,
|
||||||
|
"unit": "action"
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"type": "self"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"verbal": false,
|
||||||
|
"somatic": true,
|
||||||
|
"material": false
|
||||||
|
},
|
||||||
|
"durations": [
|
||||||
|
{
|
||||||
|
"type": "timed",
|
||||||
|
"amount": 8,
|
||||||
|
"unit": "hours"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"classes": ["wizard"],
|
||||||
|
"sources": [
|
||||||
|
{ "source": "GGR", "page": 47 }
|
||||||
|
],
|
||||||
|
"description": {
|
||||||
|
"entries": [
|
||||||
|
"Putting a finger to your head, you pull a memory, an idea, or a message from your mind and transform it into a tangible string of glowing energy called a thought strand, which persists for the duration or until you cast this spell again. The thought strand appears in an unoccupied space within 5 feet of you as a Tiny, weightless, semisolid object that can be held and carried like a ribbon. It is otherwise stationary.",
|
||||||
|
"If you cast this spell while concentrating on a spell or an ability that allows you to read or manipulate the thoughts of others (such as {@spell detect thoughts} or {@spell modify memory}), you can transform the thoughts or memories you read, rather than your own, into a thought strand.",
|
||||||
|
"Casting this spell while holding a thought strand allows you to instantly receive whatever memory, idea, or message the thought strand contains. (Casting {@spell detect thoughts} on the strand has the same effect.)"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
CREATE TABLE IF NOT EXISTS spells (
|
CREATE TABLE IF NOT EXISTS spells (
|
||||||
id INTEGER GENERATED ALWAYS AS IDENTITY,
|
id INTEGER GENERATED ALWAYS AS IDENTITY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
|
school TEXT NOT NULL,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
ritual BOOLEAN DEFAULT FALSE,
|
||||||
|
concentration BOOLEAN DEFAULT FALSE,
|
||||||
|
classes TEXT[] NOT NULL,
|
||||||
|
damage_inflict TEXT[] NOT NULL,
|
||||||
|
damage_resist TEXT[] NOT NULL,
|
||||||
|
conditions TEXT[] NOT NULL,
|
||||||
|
saving_throw TEXT[] NOT NULL,
|
||||||
|
attack_type TEXT,
|
||||||
data JSONB NOT NULL
|
data JSONB NOT NULL
|
||||||
);
|
);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -17,30 +17,30 @@ pub enum AbilityType {
|
|||||||
Charisma
|
Charisma
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl AbilityType {
|
impl AbilityType {
|
||||||
// pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
// match self {
|
match self {
|
||||||
// AbilityType::Strength => "Strength".to_string(),
|
AbilityType::Strength => "Strength".to_string(),
|
||||||
// AbilityType::Dexterity => "Dexterity".to_string(),
|
AbilityType::Dexterity => "Dexterity".to_string(),
|
||||||
// AbilityType::Constitution => "Constitution".to_string(),
|
AbilityType::Constitution => "Constitution".to_string(),
|
||||||
// AbilityType::Intelligence => "Intelligence".to_string(),
|
AbilityType::Intelligence => "Intelligence".to_string(),
|
||||||
// AbilityType::Wisdom => "Wisdom".to_string(),
|
AbilityType::Wisdom => "Wisdom".to_string(),
|
||||||
// AbilityType::Charisma => "Charisma".to_string()
|
AbilityType::Charisma => "Charisma".to_string()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl FromStr for AbilityType {
|
impl FromStr for AbilityType {
|
||||||
// type Err = ();
|
type Err = ();
|
||||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
// match s {
|
match s {
|
||||||
// "Strength" => Ok(AbilityType::Strength),
|
"Strength" => Ok(AbilityType::Strength),
|
||||||
// "Dexterity" => Ok(AbilityType::Dexterity),
|
"Dexterity" => Ok(AbilityType::Dexterity),
|
||||||
// "Constitution" => Ok(AbilityType::Constitution),
|
"Constitution" => Ok(AbilityType::Constitution),
|
||||||
// "Intelligence" => Ok(AbilityType::Intelligence),
|
"Intelligence" => Ok(AbilityType::Intelligence),
|
||||||
// "Wisdom" => Ok(AbilityType::Wisdom),
|
"Wisdom" => Ok(AbilityType::Wisdom),
|
||||||
// "Charisma" => Ok(AbilityType::Charisma),
|
"Charisma" => Ok(AbilityType::Charisma),
|
||||||
// _ => Err(())
|
_ => Err(())
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
@@ -36,48 +36,48 @@ pub enum ConditionType {
|
|||||||
Unconscious
|
Unconscious
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl ConditionType {
|
impl ConditionType {
|
||||||
// pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
// match self {
|
match self {
|
||||||
// ConditionType::Blinded => "Blinded".to_string(),
|
ConditionType::Blinded => "Blinded".to_string(),
|
||||||
// ConditionType::Charmed => "Charmed".to_string(),
|
ConditionType::Charmed => "Charmed".to_string(),
|
||||||
// ConditionType::Deafened => "Deafened".to_string(),
|
ConditionType::Deafened => "Deafened".to_string(),
|
||||||
// ConditionType::Exhaustion => "Exhaustion".to_string(),
|
ConditionType::Exhaustion => "Exhaustion".to_string(),
|
||||||
// ConditionType::Frightened => "Frightened".to_string(),
|
ConditionType::Frightened => "Frightened".to_string(),
|
||||||
// ConditionType::Grappled => "Grappled".to_string(),
|
ConditionType::Grappled => "Grappled".to_string(),
|
||||||
// ConditionType::Incapacitated => "Incapacitated".to_string(),
|
ConditionType::Incapacitated => "Incapacitated".to_string(),
|
||||||
// ConditionType::Invisible => "Invisible".to_string(),
|
ConditionType::Invisible => "Invisible".to_string(),
|
||||||
// ConditionType::Paralyzed => "Paralyzed".to_string(),
|
ConditionType::Paralyzed => "Paralyzed".to_string(),
|
||||||
// ConditionType::Petrified => "Petrified".to_string(),
|
ConditionType::Petrified => "Petrified".to_string(),
|
||||||
// ConditionType::Poisoned => "Poisoned".to_string(),
|
ConditionType::Poisoned => "Poisoned".to_string(),
|
||||||
// ConditionType::Prone => "Prone".to_string(),
|
ConditionType::Prone => "Prone".to_string(),
|
||||||
// ConditionType::Restrained => "Restrained".to_string(),
|
ConditionType::Restrained => "Restrained".to_string(),
|
||||||
// ConditionType::Stunned => "Stunned".to_string(),
|
ConditionType::Stunned => "Stunned".to_string(),
|
||||||
// ConditionType::Unconscious => "Unconscious".to_string()
|
ConditionType::Unconscious => "Unconscious".to_string()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl FromStr for ConditionType {
|
impl FromStr for ConditionType {
|
||||||
// type Err = ();
|
type Err = ();
|
||||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
// match s {
|
match s {
|
||||||
// "Blinded" => Ok(ConditionType::Blinded),
|
"Blinded" => Ok(ConditionType::Blinded),
|
||||||
// "Charmed" => Ok(ConditionType::Charmed),
|
"Charmed" => Ok(ConditionType::Charmed),
|
||||||
// "Deafened" => Ok(ConditionType::Deafened),
|
"Deafened" => Ok(ConditionType::Deafened),
|
||||||
// "Exhaustion" => Ok(ConditionType::Exhaustion),
|
"Exhaustion" => Ok(ConditionType::Exhaustion),
|
||||||
// "Frightened" => Ok(ConditionType::Frightened),
|
"Frightened" => Ok(ConditionType::Frightened),
|
||||||
// "Grappled" => Ok(ConditionType::Grappled),
|
"Grappled" => Ok(ConditionType::Grappled),
|
||||||
// "Incapacitated" => Ok(ConditionType::Incapacitated),
|
"Incapacitated" => Ok(ConditionType::Incapacitated),
|
||||||
// "Invisible" => Ok(ConditionType::Invisible),
|
"Invisible" => Ok(ConditionType::Invisible),
|
||||||
// "Paralyzed" => Ok(ConditionType::Paralyzed),
|
"Paralyzed" => Ok(ConditionType::Paralyzed),
|
||||||
// "Petrified" => Ok(ConditionType::Petrified),
|
"Petrified" => Ok(ConditionType::Petrified),
|
||||||
// "Poisoned" => Ok(ConditionType::Poisoned),
|
"Poisoned" => Ok(ConditionType::Poisoned),
|
||||||
// "Prone" => Ok(ConditionType::Prone),
|
"Prone" => Ok(ConditionType::Prone),
|
||||||
// "Restrained" => Ok(ConditionType::Restrained),
|
"Restrained" => Ok(ConditionType::Restrained),
|
||||||
// "Stunned" => Ok(ConditionType::Stunned),
|
"Stunned" => Ok(ConditionType::Stunned),
|
||||||
// "Unconscious" => Ok(ConditionType::Unconscious),
|
"Unconscious" => Ok(ConditionType::Unconscious),
|
||||||
// _ => Err(())
|
_ => Err(())
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ diesel::table! {
|
|||||||
spells (id) {
|
spells (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
|
school -> Text,
|
||||||
|
level -> Integer,
|
||||||
|
ritual -> Bool,
|
||||||
|
concentration -> Bool,
|
||||||
|
classes -> Array<Text>,
|
||||||
|
damage_inflict -> Array<Text>,
|
||||||
|
damage_resist -> Array<Text>,
|
||||||
|
conditions -> Array<Text>,
|
||||||
|
saving_throw -> Array<Text>,
|
||||||
|
attack_type -> Nullable<Text>,
|
||||||
data -> Jsonb
|
data -> Jsonb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,29 +3,39 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{db::{schema::spells::{self}, classes::AbilityType, conditions::ConditionType}, error_handler::ServiceError};
|
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};
|
use super::{SchoolType, CastingTime, CastingType, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, Source, Description, DurationType};
|
||||||
|
|
||||||
#[derive(Queryable, QueryableByName)]
|
#[derive(Queryable, QueryableByName)]
|
||||||
#[diesel(table_name = spells)]
|
#[diesel(table_name = spells)]
|
||||||
pub struct QuerySpell {
|
pub struct QuerySpell {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub school: String,
|
||||||
|
pub level: i32,
|
||||||
|
pub ritual: bool,
|
||||||
|
pub concentration: bool,
|
||||||
|
pub classes: Vec<String>,
|
||||||
|
pub damage_inflict: Vec<String>,
|
||||||
|
pub damage_resist: Vec<String>,
|
||||||
|
pub conditions: Vec<String>,
|
||||||
|
pub saving_throw: Vec<String>,
|
||||||
|
pub attack_type: Option<String>,
|
||||||
pub data: serde_json::Value,
|
pub data: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct QueryFilters {
|
pub struct QueryFilters {
|
||||||
pub by_name: Option<String>,
|
pub by_name: Option<String>,
|
||||||
pub by_schools: Option<Vec<SchoolType>>,
|
pub by_schools: Option<Vec<String>>,
|
||||||
pub by_levels: Option<Vec<i32>>,
|
pub by_levels: Option<Vec<i32>>,
|
||||||
pub by_ritual: Option<bool>,
|
pub by_ritual: Option<bool>,
|
||||||
pub by_concentration: Option<bool>,
|
pub by_concentration: Option<bool>,
|
||||||
pub by_classes: Option<Vec<String>>,
|
pub by_classes: Option<Vec<String>>,
|
||||||
pub by_damage_inflict: Option<Vec<SpellDamageType>>,
|
pub by_damage_inflict: Option<Vec<String>>,
|
||||||
pub by_damage_resist: Option<Vec<SpellDamageType>>,
|
pub by_damage_resist: Option<Vec<String>>,
|
||||||
pub by_conditions: Option<Vec<ConditionType>>,
|
pub by_conditions: Option<Vec<String>>,
|
||||||
pub by_saving_throw: Option<Vec<AbilityType>>,
|
pub by_saving_throw: Option<Vec<String>>,
|
||||||
pub by_attack_type: Option<SpellAttackType>,
|
pub by_attack_type: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for QueryFilters {
|
impl Default for QueryFilters {
|
||||||
@@ -53,15 +63,85 @@ impl QuerySpell {
|
|||||||
// Limit query to page and limit
|
// Limit query to page and limit
|
||||||
let offset = (page - 1) * limit;
|
let offset = (page - 1) * limit;
|
||||||
query = query.offset(offset as i64);
|
query = query.offset(offset as i64);
|
||||||
// Apply filters
|
if let Some(name) = &filters.by_name {
|
||||||
if let Some(name) = filters.by_name.to_owned() {
|
|
||||||
query = query.filter(spells::name.ilike(format!("%{}%", 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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
if let Some(conditions) = &filters.by_conditions {
|
||||||
|
query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
if let Some(attack_type) = &filters.by_attack_type {
|
||||||
|
query = query.filter(spells::attack_type.eq(attack_type.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
let spells = query.load::<QuerySpell>(&mut conn)?;
|
let spells = query.load::<QuerySpell>(&mut conn)?;
|
||||||
Ok(spells)
|
Ok(spells)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_count(filters: &QueryFilters) -> Result<i64, ServiceError> {
|
||||||
|
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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
if let Some(conditions) = &filters.by_conditions {
|
||||||
|
query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
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::<Vec<String>>()));
|
||||||
|
}
|
||||||
|
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<Self, ServiceError> {
|
pub fn get_by_id(id: i32) -> Result<Self, ServiceError> {
|
||||||
let mut conn = crate::db::connection()?;
|
let mut conn = crate::db::connection()?;
|
||||||
let spell = spells::table
|
let spell = spells::table
|
||||||
@@ -70,17 +150,6 @@ impl QuerySpell {
|
|||||||
Ok(spell)
|
Ok(spell)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_count(filters: &QueryFilters) -> Result<i64, ServiceError> {
|
|
||||||
let mut conn = crate::db::connection()?;
|
|
||||||
let mut query = spells::table.count().into_boxed();
|
|
||||||
if let Some(name) = filters.by_name.to_owned() {
|
|
||||||
query = query.filter(spells::name.ilike(format!("%{}%", name)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = query.get_result(&mut conn)?;
|
|
||||||
Ok(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete(id: i32) -> Result<Self, ServiceError> {
|
pub fn delete(id: i32) -> Result<Self, ServiceError> {
|
||||||
let mut conn = crate::db::connection()?;
|
let mut conn = crate::db::connection()?;
|
||||||
let spell = diesel::delete(spells::table.filter(spells::id.eq(id))).get_result(&mut conn)?;
|
let spell = diesel::delete(spells::table.filter(spells::id.eq(id))).get_result(&mut conn)?;
|
||||||
@@ -92,6 +161,16 @@ impl QuerySpell {
|
|||||||
#[diesel(table_name = spells)]
|
#[diesel(table_name = spells)]
|
||||||
pub struct InsertSpell {
|
pub struct InsertSpell {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub school: String,
|
||||||
|
pub level: i32,
|
||||||
|
pub ritual: bool,
|
||||||
|
pub concentration: bool,
|
||||||
|
pub classes: Vec<String>,
|
||||||
|
pub damage_inflict: Vec<String>,
|
||||||
|
pub damage_resist: Vec<String>,
|
||||||
|
pub conditions: Vec<String>,
|
||||||
|
pub saving_throw: Vec<String>,
|
||||||
|
pub attack_type: Option<String>,
|
||||||
pub data: serde_json::Value
|
pub data: serde_json::Value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,13 +253,38 @@ impl Into<InsertSpell> for Spell {
|
|||||||
fn into(self) -> InsertSpell {
|
fn into(self) -> InsertSpell {
|
||||||
return InsertSpell {
|
return InsertSpell {
|
||||||
name: self.name.to_string(),
|
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::<Vec<String>>(),
|
||||||
|
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) {
|
data: match serde_json::to_value(&self) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("Failed to serialize spell description: {}", err);
|
log::error!("Failed to serialize spell: {}", err);
|
||||||
serde_json::Value::Null
|
serde_json::Value::Null
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use actix_web::{get, post, put, delete, web, HttpResponse, HttpRequest, ResponseError};
|
use actix_web::{get, post, put, delete, web, HttpResponse, HttpRequest, ResponseError};
|
||||||
|
use log::error;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use crate::{db::{spells::{QuerySpell, QueryFilters}, GetResponse, Metadata}, error_handler::ServiceError};
|
use crate::{db::{spells::{QuerySpell, QueryFilters}, GetResponse, Metadata}, error_handler::ServiceError};
|
||||||
@@ -8,6 +9,16 @@ use super::{Spell, InsertSpell};
|
|||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct GetAllParams {
|
struct GetAllParams {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
|
schools: Option<String>,
|
||||||
|
levels: Option<String>,
|
||||||
|
ritual: Option<bool>,
|
||||||
|
concentration: Option<bool>,
|
||||||
|
classes: Option<String>,
|
||||||
|
damage_inflict: Option<String>,
|
||||||
|
damage_resist: Option<String>,
|
||||||
|
conditions: Option<String>,
|
||||||
|
saving_throw: Option<String>,
|
||||||
|
attack_type: Option<String>,
|
||||||
limit: Option<i32>,
|
limit: Option<i32>,
|
||||||
page: Option<i32>,
|
page: Option<i32>,
|
||||||
}
|
}
|
||||||
@@ -23,6 +34,43 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
|||||||
};
|
};
|
||||||
let mut filters = QueryFilters::default();
|
let mut filters = QueryFilters::default();
|
||||||
filters.by_name = params.name.clone();
|
filters.by_name = params.name.clone();
|
||||||
|
filters.by_schools = match ¶ms.schools {
|
||||||
|
Some(schools) => Some(schools.split(",").map(|s| s.to_string()).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
filters.by_levels = match ¶ms.levels {
|
||||||
|
Some(levels) => Some(levels.split(",").map(|s| match s.to_string().parse::<i32>() {
|
||||||
|
Ok(level) => level,
|
||||||
|
Err(_) => 0
|
||||||
|
}).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
filters.by_ritual = params.ritual;
|
||||||
|
filters.by_concentration = params.concentration;
|
||||||
|
filters.by_classes = match ¶ms.classes {
|
||||||
|
Some(classes) => Some(classes.split(",").map(|s| s.to_string()).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
filters.by_damage_inflict = match ¶ms.damage_inflict {
|
||||||
|
Some(damage_inflict) => Some(damage_inflict.split(",").map(|s| s.to_string()).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
filters.by_damage_resist = match ¶ms.damage_resist {
|
||||||
|
Some(damage_resist) => Some(damage_resist.split(",").map(|s| s.to_string()).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
filters.by_conditions = match ¶ms.conditions {
|
||||||
|
Some(conditions) => Some(conditions.split(",").map(|s| s.to_string()).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
filters.by_saving_throw = match ¶ms.saving_throw {
|
||||||
|
Some(saving_throw) => Some(saving_throw.split(",").map(|s| s.to_string()).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
filters.by_attack_type = match ¶ms.attack_type {
|
||||||
|
Some(attack_type) => Some(attack_type.split(",").map(|s| s.to_string()).collect()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
// Limit must be between 1 and 100
|
// Limit must be between 1 and 100
|
||||||
let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(20), 1), 100);
|
let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(20), 1), 100);
|
||||||
let total_count = QuerySpell::get_count(&filters).unwrap();
|
let total_count = QuerySpell::get_count(&filters).unwrap();
|
||||||
@@ -46,7 +94,10 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Err(err) => ResponseError::error_response(&err)
|
Err(err) => {
|
||||||
|
error!("{:?}", err.message);
|
||||||
|
ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +115,10 @@ async fn get_by_id(id: web::Path<String>) -> HttpResponse {
|
|||||||
data: Spell::from(spell),
|
data: Spell::from(spell),
|
||||||
metadata: None
|
metadata: None
|
||||||
}),
|
}),
|
||||||
Err(err) => ResponseError::error_response(&err)
|
Err(err) => {
|
||||||
|
error!("{:?}", err.message);
|
||||||
|
ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +126,10 @@ async fn get_by_id(id: web::Path<String>) -> HttpResponse {
|
|||||||
async fn create(spell: web::Json<Spell>) -> HttpResponse {
|
async fn create(spell: web::Json<Spell>) -> HttpResponse {
|
||||||
match InsertSpell::insert(spell.into_inner().into()) {
|
match InsertSpell::insert(spell.into_inner().into()) {
|
||||||
Ok(spell) => HttpResponse::Created().json(Spell::from(spell)),
|
Ok(spell) => HttpResponse::Created().json(Spell::from(spell)),
|
||||||
Err(err) => ResponseError::error_response(&err)
|
Err(err) => {
|
||||||
|
error!("{:?}", err.message);
|
||||||
|
ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +144,10 @@ async fn update(id: web::Path<String>, spell: web::Json<Spell>) -> HttpResponse
|
|||||||
};
|
};
|
||||||
match web::block(move || InsertSpell::update(id, spell.into_inner().into())).await.unwrap() {
|
match web::block(move || InsertSpell::update(id, spell.into_inner().into())).await.unwrap() {
|
||||||
Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
|
Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
|
||||||
Err(err) => ResponseError::error_response(&err)
|
Err(err) => {
|
||||||
|
error!("{:?}", err.message);
|
||||||
|
ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +162,10 @@ async fn delete(id: web::Path<String>) -> HttpResponse {
|
|||||||
};
|
};
|
||||||
match web::block(move || QuerySpell::delete(id)).await.unwrap() {
|
match web::block(move || QuerySpell::delete(id)).await.unwrap() {
|
||||||
Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
|
Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
|
||||||
Err(err) => ResponseError::error_response(&err)
|
Err(err) => {
|
||||||
|
error!("{:?}", err.message);
|
||||||
|
ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use serde::{Deserialize, Serialize, ser::SerializeMap};
|
use serde::{Deserialize, Serialize, ser::SerializeMap};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -21,43 +21,43 @@ pub enum SchoolType {
|
|||||||
Transmutation
|
Transmutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl SchoolType {
|
impl SchoolType {
|
||||||
// pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
// match self {
|
match self {
|
||||||
// SchoolType::Abjuration => "abjuration".to_string(),
|
SchoolType::Abjuration => "abjuration".to_string(),
|
||||||
// SchoolType::Conjuration => "conjuration".to_string(),
|
SchoolType::Conjuration => "conjuration".to_string(),
|
||||||
// SchoolType::Divination => "divination".to_string(),
|
SchoolType::Divination => "divination".to_string(),
|
||||||
// SchoolType::Enchantment => "enchantment".to_string(),
|
SchoolType::Enchantment => "enchantment".to_string(),
|
||||||
// SchoolType::Evocation => "evocation".to_string(),
|
SchoolType::Evocation => "evocation".to_string(),
|
||||||
// SchoolType::Illusion => "illusion".to_string(),
|
SchoolType::Illusion => "illusion".to_string(),
|
||||||
// SchoolType::Necromancy => "necromancy".to_string(),
|
SchoolType::Necromancy => "necromancy".to_string(),
|
||||||
// SchoolType::Transmutation => "transmutation".to_string()
|
SchoolType::Transmutation => "transmutation".to_string()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl FromStr for SchoolType {
|
impl FromStr for SchoolType {
|
||||||
// type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
// match s {
|
match s {
|
||||||
// "abjuration" => Ok(SchoolType::Abjuration),
|
"abjuration" => Ok(SchoolType::Abjuration),
|
||||||
// "conjuration" => Ok(SchoolType::Conjuration),
|
"conjuration" => Ok(SchoolType::Conjuration),
|
||||||
// "divination" => Ok(SchoolType::Divination),
|
"divination" => Ok(SchoolType::Divination),
|
||||||
// "enchantment" => Ok(SchoolType::Enchantment),
|
"enchantment" => Ok(SchoolType::Enchantment),
|
||||||
// "evocation" => Ok(SchoolType::Evocation),
|
"evocation" => Ok(SchoolType::Evocation),
|
||||||
// "illusion" => Ok(SchoolType::Illusion),
|
"illusion" => Ok(SchoolType::Illusion),
|
||||||
// "necromancy" => Ok(SchoolType::Necromancy),
|
"necromancy" => Ok(SchoolType::Necromancy),
|
||||||
// "transmutation" => Ok(SchoolType::Transmutation),
|
"transmutation" => Ok(SchoolType::Transmutation),
|
||||||
// _ => Err(())
|
_ => Err(())
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct CastingTime {
|
pub struct CastingTime {
|
||||||
pub amount: i32,
|
pub amount: i32,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "unit")]
|
||||||
pub casting_type: CastingType
|
pub casting_type: CastingType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,26 +110,26 @@ pub enum SpellAttackType {
|
|||||||
Ranged,
|
Ranged,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl SpellAttackType {
|
impl SpellAttackType {
|
||||||
// pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
// match self {
|
match self {
|
||||||
// SpellAttackType::Melee => "melee".to_string(),
|
SpellAttackType::Melee => "melee".to_string(),
|
||||||
// SpellAttackType::Ranged => "ranged".to_string()
|
SpellAttackType::Ranged => "ranged".to_string()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl FromStr for SpellAttackType {
|
impl FromStr for SpellAttackType {
|
||||||
// type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
// match s {
|
match s {
|
||||||
// "melee" => Ok(SpellAttackType::Melee),
|
"melee" => Ok(SpellAttackType::Melee),
|
||||||
// "ranged" => Ok(SpellAttackType::Ranged),
|
"ranged" => Ok(SpellAttackType::Ranged),
|
||||||
// _ => Err(())
|
_ => Err(())
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub enum SpellDamageType {
|
pub enum SpellDamageType {
|
||||||
@@ -161,48 +161,48 @@ pub enum SpellDamageType {
|
|||||||
Thunder
|
Thunder
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl SpellDamageType {
|
impl SpellDamageType {
|
||||||
// pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
// match self {
|
match self {
|
||||||
// SpellDamageType::Acid => "acid".to_string(),
|
SpellDamageType::Acid => "acid".to_string(),
|
||||||
// SpellDamageType::Bludgeoning => "bludgeoning".to_string(),
|
SpellDamageType::Bludgeoning => "bludgeoning".to_string(),
|
||||||
// SpellDamageType::Cold => "cold".to_string(),
|
SpellDamageType::Cold => "cold".to_string(),
|
||||||
// SpellDamageType::Fire => "fire".to_string(),
|
SpellDamageType::Fire => "fire".to_string(),
|
||||||
// SpellDamageType::Force => "force".to_string(),
|
SpellDamageType::Force => "force".to_string(),
|
||||||
// SpellDamageType::Lightning => "lightning".to_string(),
|
SpellDamageType::Lightning => "lightning".to_string(),
|
||||||
// SpellDamageType::Necrotic => "necrotic".to_string(),
|
SpellDamageType::Necrotic => "necrotic".to_string(),
|
||||||
// SpellDamageType::Piercing => "piercing".to_string(),
|
SpellDamageType::Piercing => "piercing".to_string(),
|
||||||
// SpellDamageType::Poison => "poison".to_string(),
|
SpellDamageType::Poison => "poison".to_string(),
|
||||||
// SpellDamageType::Psychic => "psychic".to_string(),
|
SpellDamageType::Psychic => "psychic".to_string(),
|
||||||
// SpellDamageType::Radiant => "radiant".to_string(),
|
SpellDamageType::Radiant => "radiant".to_string(),
|
||||||
// SpellDamageType::Slashing => "slashing".to_string(),
|
SpellDamageType::Slashing => "slashing".to_string(),
|
||||||
// SpellDamageType::Thunder => "thunder".to_string()
|
SpellDamageType::Thunder => "thunder".to_string()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl FromStr for SpellDamageType {
|
impl FromStr for SpellDamageType {
|
||||||
// type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
// match s {
|
match s {
|
||||||
// "acid" => Ok(SpellDamageType::Acid),
|
"acid" => Ok(SpellDamageType::Acid),
|
||||||
// "bludgeoning" => Ok(SpellDamageType::Bludgeoning),
|
"bludgeoning" => Ok(SpellDamageType::Bludgeoning),
|
||||||
// "cold" => Ok(SpellDamageType::Cold),
|
"cold" => Ok(SpellDamageType::Cold),
|
||||||
// "fire" => Ok(SpellDamageType::Fire),
|
"fire" => Ok(SpellDamageType::Fire),
|
||||||
// "force" => Ok(SpellDamageType::Force),
|
"force" => Ok(SpellDamageType::Force),
|
||||||
// "lightning" => Ok(SpellDamageType::Lightning),
|
"lightning" => Ok(SpellDamageType::Lightning),
|
||||||
// "necrotic" => Ok(SpellDamageType::Necrotic),
|
"necrotic" => Ok(SpellDamageType::Necrotic),
|
||||||
// "piercing" => Ok(SpellDamageType::Piercing),
|
"piercing" => Ok(SpellDamageType::Piercing),
|
||||||
// "poison" => Ok(SpellDamageType::Poison),
|
"poison" => Ok(SpellDamageType::Poison),
|
||||||
// "psychic" => Ok(SpellDamageType::Psychic),
|
"psychic" => Ok(SpellDamageType::Psychic),
|
||||||
// "radiant" => Ok(SpellDamageType::Radiant),
|
"radiant" => Ok(SpellDamageType::Radiant),
|
||||||
// "slashing" => Ok(SpellDamageType::Slashing),
|
"slashing" => Ok(SpellDamageType::Slashing),
|
||||||
// "thunder" => Ok(SpellDamageType::Thunder),
|
"thunder" => Ok(SpellDamageType::Thunder),
|
||||||
// _ => Err(())
|
_ => Err(())
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Range {
|
pub struct Range {
|
||||||
|
|||||||
Reference in New Issue
Block a user