refactor, cleanup
This commit is contained in:
1
src/data/dnd/backgrounds/mod.rs
Normal file
1
src/data/dnd/backgrounds/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
src/data/dnd/bestiary/mod.rs
Normal file
1
src/data/dnd/bestiary/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
0
src/data/dnd/campaigns/mod.rs
Normal file
0
src/data/dnd/campaigns/mod.rs
Normal file
0
src/data/dnd/characters/mod.rs
Normal file
0
src/data/dnd/characters/mod.rs
Normal file
3
src/data/dnd/classes/mod.rs
Normal file
3
src/data/dnd/classes/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod model;
|
||||
|
||||
pub use model::*;
|
||||
46
src/data/dnd/classes/model.rs
Normal file
46
src/data/dnd/classes/model.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::str::FromStr;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AbilityType {
|
||||
#[serde(rename = "strength")]
|
||||
Strength,
|
||||
#[serde(rename = "dexterity")]
|
||||
Dexterity,
|
||||
#[serde(rename = "constitution")]
|
||||
Constitution,
|
||||
#[serde(rename = "intelligence")]
|
||||
Intelligence,
|
||||
#[serde(rename = "wisdom")]
|
||||
Wisdom,
|
||||
#[serde(rename = "charisma")]
|
||||
Charisma,
|
||||
}
|
||||
|
||||
impl AbilityType {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
AbilityType::Strength => "Strength".to_string(),
|
||||
AbilityType::Dexterity => "Dexterity".to_string(),
|
||||
AbilityType::Constitution => "Constitution".to_string(),
|
||||
AbilityType::Intelligence => "Intelligence".to_string(),
|
||||
AbilityType::Wisdom => "Wisdom".to_string(),
|
||||
AbilityType::Charisma => "Charisma".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AbilityType {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"Strength" => Ok(AbilityType::Strength),
|
||||
"Dexterity" => Ok(AbilityType::Dexterity),
|
||||
"Constitution" => Ok(AbilityType::Constitution),
|
||||
"Intelligence" => Ok(AbilityType::Intelligence),
|
||||
"Wisdom" => Ok(AbilityType::Wisdom),
|
||||
"Charisma" => Ok(AbilityType::Charisma),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/data/dnd/conditions/mod.rs
Normal file
82
src/data/dnd/conditions/mod.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::str::FromStr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum ConditionType {
|
||||
#[serde(rename = "blinded")]
|
||||
Blinded,
|
||||
#[serde(rename = "charmed")]
|
||||
Charmed,
|
||||
#[serde(rename = "deafened")]
|
||||
Deafened,
|
||||
#[serde(rename = "exhaustion")]
|
||||
Exhaustion,
|
||||
#[serde(rename = "frightened")]
|
||||
Frightened,
|
||||
#[serde(rename = "grappled")]
|
||||
Grappled,
|
||||
#[serde(rename = "incapacitated")]
|
||||
Incapacitated,
|
||||
#[serde(rename = "invisible")]
|
||||
Invisible,
|
||||
#[serde(rename = "paralyzed")]
|
||||
Paralyzed,
|
||||
#[serde(rename = "petrified")]
|
||||
Petrified,
|
||||
#[serde(rename = "poisoned")]
|
||||
Poisoned,
|
||||
#[serde(rename = "prone")]
|
||||
Prone,
|
||||
#[serde(rename = "restrained")]
|
||||
Restrained,
|
||||
#[serde(rename = "stunned")]
|
||||
Stunned,
|
||||
#[serde(rename = "unconscious")]
|
||||
Unconscious,
|
||||
}
|
||||
|
||||
impl ConditionType {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
ConditionType::Blinded => "Blinded".to_string(),
|
||||
ConditionType::Charmed => "Charmed".to_string(),
|
||||
ConditionType::Deafened => "Deafened".to_string(),
|
||||
ConditionType::Exhaustion => "Exhaustion".to_string(),
|
||||
ConditionType::Frightened => "Frightened".to_string(),
|
||||
ConditionType::Grappled => "Grappled".to_string(),
|
||||
ConditionType::Incapacitated => "Incapacitated".to_string(),
|
||||
ConditionType::Invisible => "Invisible".to_string(),
|
||||
ConditionType::Paralyzed => "Paralyzed".to_string(),
|
||||
ConditionType::Petrified => "Petrified".to_string(),
|
||||
ConditionType::Poisoned => "Poisoned".to_string(),
|
||||
ConditionType::Prone => "Prone".to_string(),
|
||||
ConditionType::Restrained => "Restrained".to_string(),
|
||||
ConditionType::Stunned => "Stunned".to_string(),
|
||||
ConditionType::Unconscious => "Unconscious".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ConditionType {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"Blinded" => Ok(ConditionType::Blinded),
|
||||
"Charmed" => Ok(ConditionType::Charmed),
|
||||
"Deafened" => Ok(ConditionType::Deafened),
|
||||
"Exhaustion" => Ok(ConditionType::Exhaustion),
|
||||
"Frightened" => Ok(ConditionType::Frightened),
|
||||
"Grappled" => Ok(ConditionType::Grappled),
|
||||
"Incapacitated" => Ok(ConditionType::Incapacitated),
|
||||
"Invisible" => Ok(ConditionType::Invisible),
|
||||
"Paralyzed" => Ok(ConditionType::Paralyzed),
|
||||
"Petrified" => Ok(ConditionType::Petrified),
|
||||
"Poisoned" => Ok(ConditionType::Poisoned),
|
||||
"Prone" => Ok(ConditionType::Prone),
|
||||
"Restrained" => Ok(ConditionType::Restrained),
|
||||
"Stunned" => Ok(ConditionType::Stunned),
|
||||
"Unconscious" => Ok(ConditionType::Unconscious),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/data/dnd/feats/mod.rs
Normal file
1
src/data/dnd/feats/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
src/data/dnd/items/mod.rs
Normal file
1
src/data/dnd/items/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
13
src/data/dnd/mod.rs
Normal file
13
src/data/dnd/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub mod backgrounds;
|
||||
pub mod bestiary;
|
||||
pub mod classes;
|
||||
pub mod conditions;
|
||||
pub mod feats;
|
||||
pub mod items;
|
||||
pub mod options;
|
||||
pub mod races;
|
||||
pub mod spells;
|
||||
|
||||
pub fn load_data(data_dir_path: &str) {
|
||||
spells::load_data(data_dir_path);
|
||||
}
|
||||
1
src/data/dnd/options/mod.rs
Normal file
1
src/data/dnd/options/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
src/data/dnd/races/mod.rs
Normal file
1
src/data/dnd/races/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
62
src/data/dnd/spells/mod.rs
Normal file
62
src/data/dnd/spells/mod.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
mod model;
|
||||
mod types;
|
||||
|
||||
use std::{
|
||||
fs::{metadata, File, read_dir},
|
||||
path::Path,
|
||||
io::BufReader,
|
||||
};
|
||||
|
||||
pub use model::*;
|
||||
pub use types::*;
|
||||
|
||||
pub fn load_data(data_dir_path: &str) {
|
||||
if Path::new(data_dir_path).exists() {
|
||||
let meta = metadata(data_dir_path).unwrap();
|
||||
if meta.is_dir() {
|
||||
let spells_dir_path = format!("{}/spells", data_dir_path);
|
||||
if Path::new(&spells_dir_path).exists() {
|
||||
let meta = metadata(&spells_dir_path).unwrap();
|
||||
if meta.is_dir() {
|
||||
for entry in read_dir(&spells_dir_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let file = File::open(path).unwrap();
|
||||
let reader = BufReader::new(file);
|
||||
let result: Result<Vec<Spell>, serde_json::Error> = serde_json::from_reader(reader);
|
||||
match result {
|
||||
Ok(spells) => {
|
||||
for spell in spells {
|
||||
// let mut filters = QueryFilters::default();
|
||||
// filters.by_name = Some(spell.name.clone());
|
||||
// match QuerySpell::get_all(&filters, 100, 1) {
|
||||
// Ok(spells) => {
|
||||
// if spells.len() > 0 {
|
||||
// trace!("Spell '{}' already exists", spell.name);
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// Err(err) => {
|
||||
// warn!("Error checking if spell '{}' exists: {}", spell.name, err);
|
||||
// continue;
|
||||
// }
|
||||
// };
|
||||
// let spell = InsertSpell::insert(spell.into()).unwrap();
|
||||
// trace!("Inserted spell: {}", spell.name);
|
||||
}
|
||||
}
|
||||
Err(err) => log::warn!("Error reading spells from file: {}", err),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!(
|
||||
"Data path '{}' does not exist, no data imported",
|
||||
data_dir_path
|
||||
);
|
||||
}
|
||||
}
|
||||
184
src/data/dnd/spells/model.rs
Normal file
184
src/data/dnd/spells/model.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::dnd::{classes::AbilityType, conditions::ConditionType};
|
||||
|
||||
use super::{
|
||||
SchoolType, CastingTime, SpellAttackType, SpellDamageType, Range, Area, Components, Duration,
|
||||
Source, Description, DurationType, Effect,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct QuerySpell {
|
||||
pub id: i32,
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InsertSpell {
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Spell {
|
||||
pub id: Option<i32>,
|
||||
pub name: String,
|
||||
pub school: SchoolType,
|
||||
pub level: i32,
|
||||
pub ritual: bool,
|
||||
pub casting_time: CastingTime,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub effect: Option<Effect>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub saving_throw: Option<Vec<AbilityType>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub attack_type: Option<SpellAttackType>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub damage_inflict: Option<Vec<SpellDamageType>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub damage_resist: Option<Vec<SpellDamageType>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub conditions: Option<Vec<ConditionType>>,
|
||||
pub range: Range,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub area: Option<Area>,
|
||||
pub components: Components,
|
||||
pub durations: Vec<Duration>,
|
||||
pub classes: Vec<String>,
|
||||
pub sources: Vec<Source>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<Description>,
|
||||
}
|
||||
|
||||
impl From<QuerySpell> 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 {
|
||||
id: None,
|
||||
name: "".to_string(),
|
||||
school: SchoolType::Abjuration,
|
||||
level: 0,
|
||||
ritual: false,
|
||||
casting_time: CastingTime {
|
||||
value: 0,
|
||||
casting_type: "".to_string(),
|
||||
note: None,
|
||||
},
|
||||
effect: None,
|
||||
saving_throw: None,
|
||||
attack_type: None,
|
||||
damage_inflict: None,
|
||||
damage_resist: None,
|
||||
conditions: None,
|
||||
range: Range {
|
||||
range_type: "".to_string(),
|
||||
value: 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<InsertSpell> 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::<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) {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
log::error!("Failed to serialize spell: {}", err);
|
||||
serde_json::Value::Null
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
366
src/data/dnd/spells/types.rs
Normal file
366
src/data/dnd/spells/types.rs
Normal file
@@ -0,0 +1,366 @@
|
||||
use std::str::FromStr;
|
||||
use serde::{Deserialize, Serialize, ser::SerializeMap};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum SchoolType {
|
||||
#[serde(rename = "abjuration")]
|
||||
Abjuration,
|
||||
#[serde(rename = "conjuration")]
|
||||
Conjuration,
|
||||
#[serde(rename = "divination")]
|
||||
Divination,
|
||||
#[serde(rename = "enchantment")]
|
||||
Enchantment,
|
||||
#[serde(rename = "evocation")]
|
||||
Evocation,
|
||||
#[serde(rename = "illusion")]
|
||||
Illusion,
|
||||
#[serde(rename = "necromancy")]
|
||||
Necromancy,
|
||||
#[serde(rename = "transmutation")]
|
||||
Transmutation,
|
||||
}
|
||||
|
||||
impl SchoolType {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
SchoolType::Abjuration => "abjuration".to_string(),
|
||||
SchoolType::Conjuration => "conjuration".to_string(),
|
||||
SchoolType::Divination => "divination".to_string(),
|
||||
SchoolType::Enchantment => "enchantment".to_string(),
|
||||
SchoolType::Evocation => "evocation".to_string(),
|
||||
SchoolType::Illusion => "illusion".to_string(),
|
||||
SchoolType::Necromancy => "necromancy".to_string(),
|
||||
SchoolType::Transmutation => "transmutation".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SchoolType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"abjuration" => Ok(SchoolType::Abjuration),
|
||||
"conjuration" => Ok(SchoolType::Conjuration),
|
||||
"divination" => Ok(SchoolType::Divination),
|
||||
"enchantment" => Ok(SchoolType::Enchantment),
|
||||
"evocation" => Ok(SchoolType::Evocation),
|
||||
"illusion" => Ok(SchoolType::Illusion),
|
||||
"necromancy" => Ok(SchoolType::Necromancy),
|
||||
"transmutation" => Ok(SchoolType::Transmutation),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CastingTime {
|
||||
pub value: i32,
|
||||
#[serde(rename = "unit")]
|
||||
pub casting_type: String,
|
||||
pub note: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum SpellAttackType {
|
||||
#[serde(rename = "melee")]
|
||||
Melee,
|
||||
#[serde(rename = "ranged")]
|
||||
Ranged,
|
||||
}
|
||||
|
||||
impl SpellAttackType {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
SpellAttackType::Melee => "melee".to_string(),
|
||||
SpellAttackType::Ranged => "ranged".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SpellAttackType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"melee" => Ok(SpellAttackType::Melee),
|
||||
"ranged" => Ok(SpellAttackType::Ranged),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum SpellDamageType {
|
||||
#[serde(rename = "acid")]
|
||||
Acid,
|
||||
#[serde(rename = "bludgeoning")]
|
||||
Bludgeoning,
|
||||
#[serde(rename = "cold")]
|
||||
Cold,
|
||||
#[serde(rename = "fire")]
|
||||
Fire,
|
||||
#[serde(rename = "force")]
|
||||
Force,
|
||||
#[serde(rename = "lightning")]
|
||||
Lightning,
|
||||
#[serde(rename = "necrotic")]
|
||||
Necrotic,
|
||||
#[serde(rename = "piercing")]
|
||||
Piercing,
|
||||
#[serde(rename = "poison")]
|
||||
Poison,
|
||||
#[serde(rename = "psychic")]
|
||||
Psychic,
|
||||
#[serde(rename = "radiant")]
|
||||
Radiant,
|
||||
#[serde(rename = "slashing")]
|
||||
Slashing,
|
||||
#[serde(rename = "thunder")]
|
||||
Thunder,
|
||||
}
|
||||
|
||||
impl SpellDamageType {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
SpellDamageType::Acid => "acid".to_string(),
|
||||
SpellDamageType::Bludgeoning => "bludgeoning".to_string(),
|
||||
SpellDamageType::Cold => "cold".to_string(),
|
||||
SpellDamageType::Fire => "fire".to_string(),
|
||||
SpellDamageType::Force => "force".to_string(),
|
||||
SpellDamageType::Lightning => "lightning".to_string(),
|
||||
SpellDamageType::Necrotic => "necrotic".to_string(),
|
||||
SpellDamageType::Piercing => "piercing".to_string(),
|
||||
SpellDamageType::Poison => "poison".to_string(),
|
||||
SpellDamageType::Psychic => "psychic".to_string(),
|
||||
SpellDamageType::Radiant => "radiant".to_string(),
|
||||
SpellDamageType::Slashing => "slashing".to_string(),
|
||||
SpellDamageType::Thunder => "thunder".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SpellDamageType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"acid" => Ok(SpellDamageType::Acid),
|
||||
"bludgeoning" => Ok(SpellDamageType::Bludgeoning),
|
||||
"cold" => Ok(SpellDamageType::Cold),
|
||||
"fire" => Ok(SpellDamageType::Fire),
|
||||
"force" => Ok(SpellDamageType::Force),
|
||||
"lightning" => Ok(SpellDamageType::Lightning),
|
||||
"necrotic" => Ok(SpellDamageType::Necrotic),
|
||||
"piercing" => Ok(SpellDamageType::Piercing),
|
||||
"poison" => Ok(SpellDamageType::Poison),
|
||||
"psychic" => Ok(SpellDamageType::Psychic),
|
||||
"radiant" => Ok(SpellDamageType::Radiant),
|
||||
"slashing" => Ok(SpellDamageType::Slashing),
|
||||
"thunder" => Ok(SpellDamageType::Thunder),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Range {
|
||||
#[serde(rename = "type")]
|
||||
pub range_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Area {
|
||||
#[serde(rename = "type")]
|
||||
pub area_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Duration {
|
||||
#[serde(rename = "type")]
|
||||
pub duration_type: DurationType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum DurationType {
|
||||
#[serde(rename = "concentration")]
|
||||
Concentration,
|
||||
#[serde(rename = "instantaneous")]
|
||||
Instantaneous,
|
||||
#[serde(rename = "timed")]
|
||||
Timed,
|
||||
#[serde(rename = "dispelled")]
|
||||
UntilDispelled,
|
||||
#[serde(rename = "special")]
|
||||
Special,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Source {
|
||||
pub source: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub page: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Description {
|
||||
pub entries: Vec<Entry>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Entry {
|
||||
pub text: Option<String>,
|
||||
pub list: Option<Vec<String>>,
|
||||
pub table: Option<EntryTable>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct EntryTable {
|
||||
pub headers: Vec<String>,
|
||||
pub rows: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Entry {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = serde_json::Value::deserialize(deserializer)?;
|
||||
match value {
|
||||
serde_json::Value::String(s) => Ok(Entry {
|
||||
text: Some(s),
|
||||
list: None,
|
||||
table: None,
|
||||
}),
|
||||
serde_json::Value::Object(o) => {
|
||||
let text = match o.get("text") {
|
||||
Some(t) => match t.as_str() {
|
||||
Some(s) => Some(s.to_string()),
|
||||
None => return Err(serde::de::Error::custom("Invalid entry text")),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let list = match o.get("list") {
|
||||
Some(i) => match i.as_array() {
|
||||
Some(a) => {
|
||||
let mut list = Vec::new();
|
||||
for item in a {
|
||||
match item.as_str() {
|
||||
Some(s) => list.push(s.to_string()),
|
||||
None => return Err(serde::de::Error::custom("Invalid entry list item")),
|
||||
}
|
||||
}
|
||||
Some(list)
|
||||
}
|
||||
None => return Err(serde::de::Error::custom("Invalid entry list items")),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let table = match o.get("table") {
|
||||
Some(t) => match t.as_object() {
|
||||
Some(o) => {
|
||||
let mut headers = Vec::new();
|
||||
let mut rows = Vec::new();
|
||||
match o.get("headers") {
|
||||
Some(c) => match c.as_array() {
|
||||
Some(a) => {
|
||||
for item in a {
|
||||
match item.as_str() {
|
||||
Some(s) => headers.push(s.to_string()),
|
||||
None => return Err(serde::de::Error::custom("Invalid entry table header")),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => return Err(serde::de::Error::custom("Invalid entry table headers")),
|
||||
},
|
||||
None => return Err(serde::de::Error::custom("Missing entry table headers")),
|
||||
};
|
||||
match o.get("rows") {
|
||||
Some(r) => match r.as_array() {
|
||||
Some(a) => {
|
||||
for row in a {
|
||||
match row.as_array() {
|
||||
Some(a) => {
|
||||
let mut row = Vec::new();
|
||||
for item in a {
|
||||
match item.as_str() {
|
||||
Some(s) => row.push(s.to_string()),
|
||||
None => {
|
||||
return Err(serde::de::Error::custom(
|
||||
"Invalid entry table row item",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
None => return Err(serde::de::Error::custom("Invalid entry table row")),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => return Err(serde::de::Error::custom("Invalid entry table rows")),
|
||||
},
|
||||
None => return Err(serde::de::Error::custom("Missing entry table rows")),
|
||||
};
|
||||
Some(EntryTable { headers, rows })
|
||||
}
|
||||
None => return Err(serde::de::Error::custom("Invalid entry table")),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
Ok(Entry { text, list, table })
|
||||
}
|
||||
_ => Err(serde::de::Error::custom("Invalid entry")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Entry {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(1))?;
|
||||
if let Some(text) = &self.text {
|
||||
map.serialize_entry("text", text)?;
|
||||
}
|
||||
if let Some(list) = &self.list {
|
||||
map.serialize_entry("list", list)?;
|
||||
}
|
||||
if let Some(table) = &self.table {
|
||||
map.serialize_entry("table", table)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Components {
|
||||
pub verbal: bool,
|
||||
pub somatic: bool,
|
||||
pub material: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub materials_needed: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub materials_cost: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub materials_consumed: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Effect {
|
||||
pub effect_type: Option<String>,
|
||||
}
|
||||
3
src/data/events/mod.rs
Normal file
3
src/data/events/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod model;
|
||||
|
||||
pub use model::*;
|
||||
58
src/data/events/model.rs
Normal file
58
src/data/events/model.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::SirenResult;
|
||||
|
||||
const TABLE_NAME: &str = "events";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct Event {
|
||||
pub id: Uuid,
|
||||
pub guild_id: i64,
|
||||
pub author_id: i64,
|
||||
pub title: String,
|
||||
pub date_time: DateTime<Utc>,
|
||||
pub description: Option<String>,
|
||||
pub rsvp: Vec<i64>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub async fn insert(&self) -> SirenResult<()> {
|
||||
let pool = crate::data::pool();
|
||||
sqlx::query(&format!(
|
||||
"INSERT INTO {} (
|
||||
id,
|
||||
guild_id,
|
||||
author_id,
|
||||
title,
|
||||
date_time,
|
||||
description,
|
||||
rsvp
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7
|
||||
)",
|
||||
TABLE_NAME
|
||||
))
|
||||
.bind(self.id)
|
||||
.bind(self.guild_id)
|
||||
.bind(self.author_id)
|
||||
.bind(&self.title)
|
||||
.bind(self.date_time)
|
||||
.bind(&self.description)
|
||||
.bind(&self.rsvp)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_by_id(id: i64) -> SirenResult<Option<Self>> {
|
||||
let pool = crate::data::pool();
|
||||
let item = sqlx::query_as::<_, Self>(&format!("SELECT * FROM {} WHERE id = $1", TABLE_NAME))
|
||||
.bind(id)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
}
|
||||
3
src/data/guilds/mod.rs
Normal file
3
src/data/guilds/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod model;
|
||||
|
||||
pub use model::*;
|
||||
60
src/data/guilds/model.rs
Normal file
60
src/data/guilds/model.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::error::SirenResult;
|
||||
|
||||
const TABLE_NAME: &str = "guilds";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct GuildCache {
|
||||
pub id: i64,
|
||||
pub bot_id: i64,
|
||||
pub volume: i32,
|
||||
}
|
||||
|
||||
impl GuildCache {
|
||||
pub async fn insert(&self) -> SirenResult<()> {
|
||||
let pool = crate::data::pool();
|
||||
sqlx::query(&format!(
|
||||
"INSERT INTO {} (
|
||||
id,
|
||||
bot_id,
|
||||
volume
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
)",
|
||||
TABLE_NAME
|
||||
))
|
||||
.bind(self.id)
|
||||
.bind(self.bot_id)
|
||||
.bind(self.volume)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_by_id(id: i64) -> SirenResult<Option<Self>> {
|
||||
let pool = crate::data::pool();
|
||||
let item = sqlx::query_as::<_, Self>(&format!("SELECT * FROM {} WHERE id = $1", TABLE_NAME))
|
||||
.bind(id)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
pub async fn update(&self) -> SirenResult<()> {
|
||||
let pool = crate::data::pool();
|
||||
sqlx::query(&format!(
|
||||
"UPDATE {} SET
|
||||
bot_id = $2,
|
||||
volume = $3
|
||||
WHERE id = $1",
|
||||
TABLE_NAME
|
||||
))
|
||||
.bind(self.id)
|
||||
.bind(self.bot_id)
|
||||
.bind(self.volume)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
3
src/data/messages/mod.rs
Normal file
3
src/data/messages/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod model;
|
||||
|
||||
pub use model::*;
|
||||
74
src/data/messages/model.rs
Normal file
74
src/data/messages/model.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::error::SirenResult;
|
||||
|
||||
const TABLE_NAME: &str = "messages";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct MessageCache {
|
||||
pub id: String,
|
||||
pub guild_id: i64,
|
||||
pub channel_id: i64,
|
||||
pub author_id: i64,
|
||||
pub created: i64,
|
||||
pub model: String,
|
||||
pub request: String,
|
||||
pub response: String,
|
||||
pub request_tags: Vec<String>,
|
||||
pub response_tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl MessageCache {
|
||||
pub async fn insert(&self) -> SirenResult<()> {
|
||||
let pool = crate::data::pool();
|
||||
sqlx::query(&format!(
|
||||
"INSERT INTO {} (
|
||||
id,
|
||||
guild_id,
|
||||
channel_id,
|
||||
author_id,
|
||||
created,
|
||||
model,
|
||||
request,
|
||||
response,
|
||||
request_tags,
|
||||
response_tags
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
)",
|
||||
TABLE_NAME
|
||||
))
|
||||
.bind(&self.id)
|
||||
.bind(self.guild_id)
|
||||
.bind(self.channel_id)
|
||||
.bind(self.author_id)
|
||||
.bind(self.created)
|
||||
.bind(&self.model)
|
||||
.bind(&self.request)
|
||||
.bind(&self.response)
|
||||
.bind(&self.request_tags)
|
||||
.bind(&self.response_tags)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find(
|
||||
guild_id: i64,
|
||||
channel_id: i64,
|
||||
author_id: i64,
|
||||
limit: i64,
|
||||
) -> SirenResult<Vec<MessageCache>> {
|
||||
let pool = crate::data::pool();
|
||||
let messages = sqlx::query_as::<_, MessageCache>(&format!(
|
||||
"SELECT * FROM {} WHERE guild_id = $1 AND channel_id = $2 AND author_id = $3 ORDER BY created ASC LIMIT $4",
|
||||
TABLE_NAME
|
||||
))
|
||||
.bind(guild_id)
|
||||
.bind(channel_id)
|
||||
.bind(author_id)
|
||||
.bind(limit)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
Ok(messages)
|
||||
}
|
||||
}
|
||||
85
src/data/mod.rs
Normal file
85
src/data/mod.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use std::{sync::OnceLock, time::Duration};
|
||||
|
||||
use redis::{aio::MultiplexedConnection as RedisConnection, Client as RedisClient, RedisResult};
|
||||
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
|
||||
use crate::error::SirenResult;
|
||||
|
||||
pub mod events;
|
||||
pub mod guilds;
|
||||
pub mod messages;
|
||||
|
||||
static POOL: OnceLock<Pool<Postgres>> = OnceLock::new();
|
||||
static REDIS: OnceLock<RedisClient> = OnceLock::new();
|
||||
|
||||
pub async fn initialize() -> SirenResult<()> {
|
||||
log::info!("Initializing database...");
|
||||
let db_user = std::env::var("DATABASE_USER").unwrap_or("siren".to_string());
|
||||
let db_password = std::env::var("DATABASE_PASSWORD").expect("DATABASE_PASSWORD must be set");
|
||||
let db_host: String = std::env::var("DATABASE_HOST").expect("DATABASE_HOST must be set");
|
||||
let db_port = std::env::var("DATABASE_PORT").unwrap_or("5432".to_string());
|
||||
let db_name = std::env::var("DATABASE_NAME").unwrap_or("siren".to_string());
|
||||
|
||||
// Setup Postgres pool connection
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.acquire_timeout(Duration::from_secs(30))
|
||||
.connect(&format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
db_user, db_password, db_host, db_port, db_name
|
||||
))
|
||||
.await?;
|
||||
match POOL.set(pool) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
log::warn!("Database pool already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
// Setup Redis connection
|
||||
let redis = {
|
||||
let host = std::env::var("REDIS_HOST").unwrap_or("localhost".to_string());
|
||||
let port = std::env::var("REDIS_PORT").unwrap_or("6379".to_string());
|
||||
let url = format!("redis://{}:{}", host, port);
|
||||
RedisClient::open(url).expect("Failed to create redis client")
|
||||
};
|
||||
match REDIS.set(redis) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
log::warn!("Redis client already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
match run_migrations().await {
|
||||
Ok(_) => log::debug!("Successfully ran migrations"),
|
||||
Err(e) => log::error!("Failed to run migrations: {}", e),
|
||||
}
|
||||
|
||||
log::info!("Database initialized");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pool() -> &'static Pool<Postgres> {
|
||||
POOL.get().unwrap()
|
||||
}
|
||||
|
||||
fn redis() -> &'static RedisClient {
|
||||
REDIS.get().unwrap()
|
||||
}
|
||||
|
||||
pub fn redis_connection() -> RedisResult<redis::Connection> {
|
||||
let conn = redis().get_connection()?;
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
pub async fn redis_async_connection() -> RedisResult<RedisConnection> {
|
||||
let conn = redis().get_multiplexed_async_connection().await?;
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
async fn run_migrations() -> SirenResult<()> {
|
||||
log::debug!("Running migrations");
|
||||
let pool = pool();
|
||||
sqlx::migrate!().run(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user