refactor, cleanup

This commit is contained in:
2024-10-11 09:29:33 -04:00
parent 07a1e9277e
commit 2688d2304e
35 changed files with 66 additions and 127 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

View File

View File

@@ -0,0 +1,3 @@
mod model;
pub use model::*;

View 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(()),
}
}
}

View 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(()),
}
}
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

13
src/data/dnd/mod.rs Normal file
View 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);
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View 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
);
}
}

View 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
}
},
};
}
}

View 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
View File

@@ -0,0 +1,3 @@
mod model;
pub use model::*;

58
src/data/events/model.rs Normal file
View 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
View File

@@ -0,0 +1,3 @@
mod model;
pub use model::*;

60
src/data/guilds/model.rs Normal file
View 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
View File

@@ -0,0 +1,3 @@
mod model;
pub use model::*;

View 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
View 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(())
}