From 3d07413690c551d9f034c93af85ae8da5a495e14 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 20 Apr 2024 02:31:40 +0000 Subject: spring cleaning (#165) * treewide: lightly refactor everything * once_cell -> std::sync * remove build.rs we can get our target at runtime * commands::copypasta: refactor selection * drop owo_colors * reactboard: always remove author from count * commands: better handle behavior outside of guilds * ci: garnix -> gha * nix: drop flake-parts & pre-commit-hooks * nix: fix rust flags in derivation * add gha badge to readme * ci: fail when format changes are made * ci: only run on push to main * nix: fix nil script * nix: add libiconv to darwin deps * ci: disable fail-fast * nix: fix actionlint & static checks * ci: add release gates * nix: fix nil check again * ci: give release gates unique names * ci: only build static packages in docker workflow * nix: move dev outputs to subflake * fix some typos * nix: cleanup checks & dev shell * add editorconfig --- src/storage/mod.rs | 200 ++++++++++++++++------------------------------ src/storage/reactboard.rs | 2 - src/storage/settings.rs | 6 +- 3 files changed, 71 insertions(+), 137 deletions(-) (limited to 'src/storage') diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 0e2f445..45c150a 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,138 +1,47 @@ -use std::fmt::Debug; +use std::{env, fmt::Debug, str::FromStr}; use eyre::Result; use log::debug; use poise::serenity_prelude::{GuildId, MessageId}; -use redis::{AsyncCommands, Client, FromRedisValue, ToRedisArgs}; +use redis::{AsyncCommands, Client, ConnectionLike, RedisError}; -mod reactboard; -mod settings; -// these are purposefully private. see the comment below -use reactboard::REACTBOARD_KEY; -use settings::SETTINGS_KEY; +pub mod reactboard; +pub mod settings; -pub use reactboard::ReactBoardEntry; -pub use settings::{Properties, Settings}; +use reactboard::ReactBoardEntry; +use settings::Settings; + +pub const REACTBOARD_KEY: &str = "reactboard-v2"; +pub const SETTINGS_KEY: &str = "settings-v1"; #[derive(Clone, Debug)] pub struct Storage { - pub client: Client, + client: Client, } impl Storage { - pub fn new(redis_url: &str) -> Result { - let client = Client::open(redis_url)?; - - Ok(Self { client }) - } - - /* - these are mainly light abstractions to avoid the `let mut con` - boilerplate, as well as not require the caller to format the - strings for keys - */ - - async fn get_key(&self, key: &str) -> Result - 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) - } - - 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(()) - } - - async fn key_exists(&self, key: &str) -> Result { - 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) - } - - 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(()) - } - - async fn expire_key(&self, key: &str, expire_seconds: i64) -> Result<()> { - debug!("Expiring key {key} in {expire_seconds}"); - - let mut con = self.client.get_async_connection().await?; - con.expire(key, expire_seconds).await?; - - Ok(()) + pub fn new(client: Client) -> Self { + Self { client } } - async fn add_to_index<'a>( - &self, - key: &str, - member: impl ToRedisArgs + Debug + Send + Sync + 'a, - ) -> Result<()> { - let key = format!("{key}:index"); - debug!("Adding member {member:#?} to index {key}"); - - let mut con = self.client.get_async_connection().await?; - con.sadd(key, member).await?; + pub fn from_env() -> Result { + let redis_url = env::var("REDIS_URL")?; - Ok(()) + Ok(Self::from_str(&redis_url)?) } - async fn get_index(&self, key: &str) -> Result> - where - T: FromRedisValue, - { - let key = format!("{key}:index"); - debug!("Getting index {key}"); - - let mut con = self.client.get_async_connection().await?; - let members = con.smembers(key).await?; - - Ok(members) + pub fn is_connected(&mut self) -> bool { + self.client.check_connection() } - async fn delete_from_index<'a>( - &self, - key: &str, - member: impl ToRedisArgs + Debug + Send + Sync + 'a, - ) -> Result<()> { - let key = format!("{key}:index"); - debug!("Removing {member:#?} from index {key}"); - - let mut con = self.client.get_async_connection().await?; - con.srem(key, member).await?; - - Ok(()) - } - - // guild settings - pub async fn create_guild_settings(&self, settings: Settings) -> Result<()> { - let key = format!("{SETTINGS_KEY}:{}", settings.guild_id); + let guild_key = format!("{SETTINGS_KEY}:{}", settings.guild_id); - self.set_key(&key, &settings).await?; - // adding to index since we need to look all of these up sometimes - self.add_to_index(SETTINGS_KEY, u64::from(settings.guild_id)) + let mut con = self.client.get_multiplexed_async_connection().await?; + redis::pipe() + .set(&guild_key, &settings) + .sadd(SETTINGS_KEY, u64::from(settings.guild_id)) + .query_async(&mut con) .await?; Ok(()) @@ -140,32 +49,43 @@ impl Storage { pub async fn get_guild_settings(&self, guild_id: &GuildId) -> Result { debug!("Fetching guild settings for {guild_id}"); + let guild_key = format!("{SETTINGS_KEY}:{guild_id}"); - let key = format!("{SETTINGS_KEY}:{guild_id}"); - let settings: Settings = self.get_key(&key).await?; + let mut con = self.client.get_multiplexed_async_connection().await?; + let settings: Settings = con.get(&guild_key).await?; Ok(settings) } pub async fn delete_guild_settings(&self, guild_id: &GuildId) -> Result<()> { - let key = format!("{SETTINGS_KEY}:{guild_id}"); - - self.delete_key(&key).await?; - self.delete_from_index(SETTINGS_KEY, u64::from(*guild_id)) + debug!("Deleting guild settings for {guild_id}"); + let guild_key = format!("{SETTINGS_KEY}:{guild_id}"); + + let mut con = self.client.get_multiplexed_async_connection().await?; + redis::pipe() + .del(&guild_key) + .srem(SETTINGS_KEY, u64::from(*guild_id)) + .query_async(&mut con) .await?; Ok(()) } pub async fn guild_settings_exist(&self, guild_id: &GuildId) -> Result { - let key = format!("{SETTINGS_KEY}:{guild_id}"); - self.key_exists(&key).await + debug!("Checking if guild settings for {guild_id} exist"); + let guild_key = format!("{SETTINGS_KEY}:{guild_id}"); + + let mut con = self.client.get_multiplexed_async_connection().await?; + let exists = con.exists(&guild_key).await?; + + Ok(exists) } pub async fn get_all_guild_settings(&self) -> Result> { debug!("Fetching all guild settings"); - let found: Vec = self.get_index(SETTINGS_KEY).await?; + let mut con = self.client.get_multiplexed_async_connection().await?; + let found: Vec = con.smembers(SETTINGS_KEY).await?; let mut guilds = vec![]; for key in found { @@ -196,10 +116,14 @@ impl Storage { guild_id: &GuildId, entry: ReactBoardEntry, ) -> Result<()> { - let key = format!("{REACTBOARD_KEY}:{guild_id}:{}", entry.original_message_id); + debug!( + "Creating reactboard entry for {} in {guild_id}", + &entry.original_message_id + ); + let entry_key = format!("{REACTBOARD_KEY}:{guild_id}:{}", entry.original_message_id); - self.set_key(&key, &entry).await?; - self.expire_key(&key, 30 * 24 * 60 * 60).await?; // 30 days + let mut con = self.client.get_multiplexed_async_connection().await?; + con.set_ex(&entry_key, &entry, 30 * 24 * 60 * 60).await?; // 30 days Ok(()) } @@ -209,10 +133,11 @@ impl Storage { guild_id: &GuildId, message_id: &MessageId, ) -> Result { - debug!("Fetching reactboard entry in {guild_id}"); + debug!("Fetching reactboard entry {message_id} in {guild_id}"); + let entry_key = format!("{REACTBOARD_KEY}:{guild_id}:{message_id}"); - let key = format!("{REACTBOARD_KEY}:{guild_id}:{message_id}"); - let entry: ReactBoardEntry = self.get_key(&key).await?; + let mut con = self.client.get_multiplexed_async_connection().await?; + let entry: ReactBoardEntry = con.get(&entry_key).await?; Ok(entry) } @@ -222,7 +147,20 @@ impl Storage { guild_id: &GuildId, message_id: &MessageId, ) -> Result { - let key = format!("{REACTBOARD_KEY}:{guild_id}:{message_id}"); - self.key_exists(&key).await + debug!("Checking if reactboard entry {message_id} exists in {guild_id}"); + let entry_key = format!("{REACTBOARD_KEY}:{guild_id}:{message_id}"); + + let mut con = self.client.get_multiplexed_async_connection().await?; + let exists = con.exists(&entry_key).await?; + + Ok(exists) + } +} + +impl FromStr for Storage { + type Err = RedisError; + fn from_str(s: &str) -> Result { + let client = Client::open(s)?; + Ok(Self::new(client)) } } diff --git a/src/storage/reactboard.rs b/src/storage/reactboard.rs index 19453df..496739c 100644 --- a/src/storage/reactboard.rs +++ b/src/storage/reactboard.rs @@ -2,8 +2,6 @@ use poise::serenity_prelude::{ChannelId, MessageId}; use redis_macros::{FromRedisValue, ToRedisArgs}; use serde::{Deserialize, Serialize}; -pub const REACTBOARD_KEY: &str = "reactboard-v2"; - #[derive(Clone, Debug, Serialize, Deserialize, FromRedisValue, ToRedisArgs)] pub struct ReactBoardEntry { pub original_message_id: MessageId, diff --git a/src/storage/settings.rs b/src/storage/settings.rs index 76eacc7..cfeda64 100644 --- a/src/storage/settings.rs +++ b/src/storage/settings.rs @@ -2,8 +2,6 @@ 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 Properties { GuildId, @@ -21,12 +19,12 @@ pub enum Properties { pub struct Settings { pub guild_id: GuildId, pub pinboard_channel: Option, - pub pinboard_watch: Option>, pub pinboard_enabled: bool, + pub pinboard_watch: Option>, pub reactboard_channel: Option, + pub reactboard_enabled: bool, pub reactboard_requirement: Option, pub reactboard_reactions: Option>, - pub reactboard_enabled: bool, pub optional_commands_enabled: bool, } -- cgit v1.2.3