summaryrefslogtreecommitdiff
path: root/src/storage
diff options
context:
space:
mode:
authorseth <[email protected]>2023-12-05 05:17:49 -0500
committerseth <[email protected]>2023-12-15 16:41:13 -0500
commit815cb0df3b3e3f9dd2078b00f85754da87b1d55e (patch)
tree85099483f8ebb0586bc097b65f6c5a2b5997150e /src/storage
parent0ca61ddff6ec7404f0aeabc1c8c785bbc8db7fd5 (diff)
refactor: centralize storage handlers
Diffstat (limited to 'src/storage')
-rw-r--r--src/storage/mod.rs146
-rw-r--r--src/storage/reactboard.rs18
-rw-r--r--src/storage/settings.rs37
3 files changed, 201 insertions, 0 deletions
diff --git a/src/storage/mod.rs b/src/storage/mod.rs
new file mode 100644
index 0000000..e6f186a
--- /dev/null
+++ b/src/storage/mod.rs
@@ -0,0 +1,146 @@
+use std::fmt::{Debug, Display};
+
+use color_eyre::eyre::Result;
+use log::*;
+use poise::serenity_prelude::GuildId;
+use redis::{AsyncCommands, Client, FromRedisValue, ToRedisArgs};
+
+pub mod reactboard;
+pub mod settings;
+
+pub use reactboard::*;
+pub use settings::*;
+
+#[derive(Clone, Debug)]
+pub struct Storage {
+ client: Client,
+}
+
+impl Storage {
+ pub fn new(redis_url: &str) -> Result<Self> {
+ let client = Client::open(redis_url)?;
+
+ Ok(Self { client })
+ }
+
+ pub async fn get_key<T>(&self, key: &str) -> Result<T>
+ where
+ T: FromRedisValue,
+ {
+ debug!("Getting key {key}");
+
+ let mut con = self.client.get_async_connection().await?;
+ let res: T = con.get(key).await?;
+
+ Ok(res)
+ }
+
+ pub async fn set_key<'a>(
+ &self,
+ key: &str,
+ value: impl ToRedisArgs + Debug + Send + Sync + 'a,
+ ) -> Result<()> {
+ debug!("Creating key {key}:\n{value:#?}");
+
+ let mut con = self.client.get_async_connection().await?;
+ con.set(key, value).await?;
+
+ Ok(())
+ }
+
+ pub async fn key_exists(&self, key: &str) -> Result<bool> {
+ debug!("Checking if key {key} exists");
+
+ let mut con = self.client.get_async_connection().await?;
+ let exists: u64 = con.exists(key).await?;
+
+ Ok(exists > 0)
+ }
+
+ pub async fn delete_key(&self, key: &str) -> Result<()> {
+ debug!("Deleting key {key}");
+
+ let mut con = self.client.get_async_connection().await?;
+ con.del(key).await?;
+
+ Ok(())
+ }
+
+ pub async fn add_to_index<'a>(
+ &self,
+ key: &str,
+ member: impl ToRedisArgs + Send + Sync + 'a,
+ ) -> Result<()> {
+ let index = format!("{key}:index");
+ debug!("Appending index {index}");
+
+ let mut con = self.client.get_async_connection().await?;
+ con.sadd(index, member).await?;
+
+ Ok(())
+ }
+
+ pub fn format_settings_key(subkey: impl Display) -> String {
+ format!("{}:{subkey}", SETTINGS_KEY)
+ }
+
+ pub async fn create_settings_key(&self, settings: Settings) -> Result<()> {
+ let key = Self::format_settings_key(settings.guild_id);
+
+ self.set_key(&key, &settings).await?;
+ self.add_to_index(SETTINGS_KEY, settings).await?;
+
+ Ok(())
+ }
+
+ /// get guilds that have enabled optional commands
+ pub async fn get_opted_guilds(&self) -> Result<Vec<GuildId>> {
+ debug!("Fetching opted-in guilds");
+
+ let guilds = self.get_all_guild_settings().await?;
+ let opted: Vec<GuildId> = guilds
+ .iter()
+ .filter_map(|g| {
+ if g.optional_commands_enabled {
+ Some(g.guild_id)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok(opted)
+ }
+
+ pub async fn get_all_guild_settings(&self) -> Result<Vec<Settings>> {
+ debug!("Fetching all guild settings");
+
+ let mut con = self.client.get_async_connection().await?;
+ let key = Self::format_settings_key("index");
+
+ let guilds: Vec<Settings> = con.smembers(key).await?;
+
+ Ok(guilds)
+ }
+
+ pub async fn get_guild_settings(&self, guild_id: &GuildId) -> Result<Settings> {
+ debug!("Fetching guild settings for {guild_id}");
+
+ let key = Self::format_settings_key(guild_id);
+ let settings: Settings = self.get_key(&key).await?;
+
+ Ok(settings)
+ }
+
+ pub async fn create_reactboard_info_key(&self, reactboard: ReactBoardInfo) -> Result<()> {
+ self.set_key(REACT_BOARD_KEY, reactboard).await?;
+ Ok(())
+ }
+
+ pub async fn get_reactboard_info(&self) -> Result<ReactBoardInfo> {
+ debug!("Fetching reactboard info");
+ let reactboard: ReactBoardInfo = self.get_key(REACT_BOARD_KEY).await?;
+
+ Ok(reactboard)
+ }
+}
diff --git a/src/storage/reactboard.rs b/src/storage/reactboard.rs
new file mode 100644
index 0000000..e08aa54
--- /dev/null
+++ b/src/storage/reactboard.rs
@@ -0,0 +1,18 @@
+use poise::serenity_prelude::{ChannelId, MessageId};
+use redis_macros::{FromRedisValue, ToRedisArgs};
+use serde::{Deserialize, Serialize};
+
+pub const REACT_BOARD_KEY: &str = "reactboard-v1";
+
+#[derive(Clone, Debug, Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
+pub struct ReactBoardEntry {
+ pub original_id: MessageId,
+ pub reaction_count: u64,
+ pub channel_id: ChannelId,
+ pub message_id: MessageId,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
+pub struct ReactBoardInfo {
+ pub reactions: Vec<ReactBoardEntry>,
+}
diff --git a/src/storage/settings.rs b/src/storage/settings.rs
new file mode 100644
index 0000000..c8a663d
--- /dev/null
+++ b/src/storage/settings.rs
@@ -0,0 +1,37 @@
+use poise::serenity_prelude::{ChannelId, GuildId, ReactionType};
+use redis_macros::{FromRedisValue, ToRedisArgs};
+use serde::{Deserialize, Serialize};
+
+pub const SETTINGS_KEY: &str = "settings-v1";
+
+#[derive(poise::ChoiceParameter)]
+pub enum SettingsProperties {
+ GuildId,
+ PinBoardChannel,
+ PinBoardWatch,
+ ReactBoardChannel,
+ ReactBoardRequirement,
+ ReactBoardReactions,
+ OptionalCommandsEnabled,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
+pub struct Settings {
+ pub guild_id: GuildId,
+ pub pinboard_channel: Option<ChannelId>,
+ pub pinboard_watch: Option<Vec<ChannelId>>,
+ pub reactboard_channel: Option<ChannelId>,
+ pub reactboard_requirement: Option<u64>,
+ pub reactboard_reactions: Option<Vec<ReactionType>>,
+ pub optional_commands_enabled: bool,
+}
+
+impl Settings {
+ pub fn can_use_reaction(&self, reaction: &ReactionType) -> bool {
+ if let Some(reactions) = &self.reactboard_reactions {
+ reactions.iter().any(|r| r == reaction)
+ } else {
+ false
+ }
+ }
+}