Skip to content

Commit

Permalink
Create basic framework for REST API implementation (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
banocean authored Jan 15, 2024
1 parent ce882b2 commit 193cb88
Show file tree
Hide file tree
Showing 25 changed files with 699 additions and 241 deletions.
40 changes: 21 additions & 19 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,45 @@ name = "custom"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
twilight-gateway = { version = "0.15", optional = true }
twilight-model = "0.15"
twilight-http = "0.15"
twilight-util = { version = "0.15", features = ["full"] }
twilight-validate = "0.15"

serde_json = "1.0.78"
serde_repr = "0.1.9"
serde = "1.0.136"
serde_urlencoded = { version = "0.7", optional = true }
serde_json = "1.0"
serde_repr = "0.1"
serde = "1.0"

async-trait = "0.1.57"
futures-util = "0.3.19"
tokio = "1.16.1"

mongodb = "2.1.0"
redis = "0.21.6"
redis = { version = "0.24", features = ["aio", "tokio-comp"] }

tokio-tungstenite = { version = "0.21", features = ["native-tls"] }

tokio-tungstenite = { version = "0.17.1", features = ["native-tls"] }
hyper = { version = "0.14.20", optional = true }
humantime = "2.1"
chrono = "0.4"

humantime = "2.1.0"
chrono = "0.4.19"
ed25519-dalek = { version = "1.0", optional = true }
dotenv = "0.15"
reqwest = { version = "0.11" }
regex = { version = "1.5", optional = true }
dashmap = "5.2"
hex = { version = "0.4", optional = true }

ed25519-dalek = { version = "1.0.1", optional = true }
dotenv = "0.15.0"
reqwest = "0.11.9"
regex = { version = "1.5.4", optional = true }
dashmap = "5.2.0"
hex = { version = "0.4.3", optional = true }
warp = { version = "0.3", optional = true }
rusty_paseto = { version = "0.6", features = ["core", "v4_local"], optional = true }
anyhow = { version = "1.0", optional = true }

[features]
all = ["custom-clients", "tasks", "http-interactions", "gateway"]
all = ["custom-clients", "tasks", "http-interactions", "api", "gateway"]
custom-clients = []
tasks = []
http-interactions = ["dep:hyper", "dep:hex", "dep:ed25519-dalek", "hyper/full"]
http-interactions = ["dep:warp", "dep:hex", "dep:anyhow", "dep:ed25519-dalek"]
gateway = ["dep:regex", "dep:twilight-gateway"]

api = ["dep:warp", "dep:rusty_paseto", "dep:serde_urlencoded", "dep:anyhow", "reqwest/json"]
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ fn main() {
#[cfg(not(feature = "custom-clients"))]
#[cfg(not(feature = "gateway"))]
#[cfg(not(feature = "tasks"))]
#[cfg(not(feature = "api"))]
{
eprintln!("You need to specify features before compiling code\n\tTo compile everything try using `cargo build --features all`");
std::process::exit(1);
Expand Down
6 changes: 3 additions & 3 deletions src/commands/moderation/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub async fn run(
).await.map_err(Error::from)?;

if let Some(target_member) = &target_member {
if !check_position(&context.redis, guild_id, target_member, member)? {
if !check_position(&context.redis, guild_id, target_member, member).await? {
return Err(
Error::from("Missing Permissions: Cannot execute moderation action on user with higher role")
)
Expand Down Expand Up @@ -251,13 +251,13 @@ fn get_highest_role_pos(
}

/// Checks is position of the moderator role higher then position of the target role
fn check_position(
async fn check_position(
redis: &RedisConnection,
guild_id: Id<GuildMarker>,
target_member: &Member,
member: PartialMember
) -> Result<bool, Error> {
let guild = redis.get_guild(guild_id).map_err(Error::from)?;
let guild = redis.get_guild(guild_id).await.map_err(Error::from)?;

let target_role_index = get_highest_role_pos(
&guild.roles,
Expand Down
4 changes: 3 additions & 1 deletion src/commands/top/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub async fn run(
return Err(Error::from("Invalid command"))
}

let leaderboard = context.redis.get_all(format!("top_{week_or_day}.{guild_id}"), 3).map_err(Error::from)?;
let leaderboard = context.redis.get_all(
format!("top_{week_or_day}.{guild_id}"), 3
).await.map_err(Error::from)?;

let leaderboard_string = leaderboard
.iter()
Expand Down
8 changes: 4 additions & 4 deletions src/commands/top/me.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ pub async fn run(

let (user_score, user_position) = context.redis.get_by_user(
format!("top_{week_or_day}.{guild_id}"), user.id
).map_err(Error::from)?;
).await.map_err(Error::from)?;

let mut result = format!("You are **{}** with **{user_score}** messages", user_position + 1);

let leaderboard = context.redis.get_all(
format!("top_{week_or_day}.{guild_id}"), 3
).map_err(Error::from)?;
).await.map_err(Error::from)?;

let leaderboard_string = leaderboard.iter().enumerate()
.map(|(index, top)|
Expand All @@ -46,13 +46,13 @@ pub async fn run(
if user_position > 0 {
let user_after = context.redis.get_by_position(
format!("top_{week_or_day}.{guild_id}"), (user_position - 1) as usize
).map_err(Error::from)?.ok_or("There is no user_after")?;
).await.map_err(Error::from)?.ok_or("There is no user_after")?;
result = format!("{result}**{user_after}** messages to beat next user\n");
}

let user_before = context.redis.get_by_position(
format!("top_{week_or_day}.{guild_id}"), (user_position + 1) as usize
).map_err(Error::from)?.ok_or("There is no `user_before`")?;
).await.map_err(Error::from)?.ok_or("There is no `user_before`")?;
result = format!("{result}**{user_before}** messages for user before (to you)");

Ok((
Expand Down
13 changes: 9 additions & 4 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use crate::{all_macro, application::Application, database::{mongodb::MongoDBConnection, redis::RedisConnection}};
use crate::{
all_macro,
env_unwrap,
application::Application,
database::{mongodb::MongoDBConnection, redis::RedisConnection},
};

all_macro!(
cfg(feature = "gateway");
Expand All @@ -18,10 +23,10 @@ pub struct Context {

impl Context {
pub async fn new() -> Self {
let mongodb_url = std::env::var("MONGODB_URL").expect("Cannot load MONGODB_URL from .env");
let redis_url = std::env::var("REDIS_URL").expect("Cannot load REDIS_URL from .env");
let mongodb_uri = env_unwrap!("MONGODB_URI");
let redis_url = env_unwrap!("REDIS_URL");

let mongodb = MongoDBConnection::connect(mongodb_url).await.unwrap();
let mongodb = MongoDBConnection::connect(mongodb_uri).await.unwrap();
let redis = RedisConnection::connect(redis_url).unwrap();

#[cfg(feature = "gateway")]
Expand Down
7 changes: 3 additions & 4 deletions src/database/mongodb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ pub struct MongoDBConnection {

impl MongoDBConnection {

pub async fn connect(url: String) -> Result<Self, mongodb::error::Error> {

let client = Client::with_uri_str(url).await?;
pub async fn connect(uri: String) -> Result<Self, mongodb::error::Error> {
let client = Client::with_uri_str(uri).await?;
let db = client.database("custom");
let configs = db.collection::<GuildConfig>("configs");
let cases = db.collection("cases");
Expand Down Expand Up @@ -96,7 +95,7 @@ impl MongoDBConnection {
let channel = discord_http.create_private_channel(member_id)
.await.map_err(Error::from)?
.model().await.map_err(Error::from)?;
let embed = case.to_dm_embed(redis).map_err(Error::from)?;
let embed = case.to_dm_embed(redis).await.map_err(Error::from)?;
discord_http.create_message(channel.id)
.embeds(&[embed]).map_err(Error::from)?
.await.map_err(Error::from)?
Expand Down
60 changes: 27 additions & 33 deletions src/database/redis.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use redis::{Client, Commands, RedisError};
use redis::{Client, RedisError};
use serde_json::json;
use twilight_model::id::marker::{GuildMarker, RoleMarker, UserMarker};
use twilight_model::id::Id;
use twilight_model::util::ImageHash;
use serde::{Serialize, Deserialize};
use crate::utils::errors::Error;
use redis::AsyncCommands;

#[derive(Serialize, Deserialize, Debug)]
pub struct PartialGuild {
Expand All @@ -13,8 +14,9 @@ pub struct PartialGuild {
pub roles: Vec<Id<RoleMarker>>
}

#[derive(Clone)]
pub struct RedisConnection {
client: Client,
pub client: Client,
}

impl RedisConnection {
Expand All @@ -23,69 +25,61 @@ impl RedisConnection {
Ok(Self { client })
}

pub fn set_guild(&self, id: Id<GuildMarker>, guild: PartialGuild) -> Result<(), RedisError> {
let mut connection = self.client.get_connection()?;
pub async fn set_guild(&self, id: Id<GuildMarker>, guild: PartialGuild) -> Result<(), RedisError> {
let mut connection = self.client.get_async_connection().await?;
let data = json!(guild).to_string();
connection.set(format!("guilds.{id}"), data)
connection.set(format!("guilds.{id}"), data).await
}

pub fn get_guild(&self, id: Id<GuildMarker>) -> Result<PartialGuild, Error> {
let mut connection = self.client.get_connection().map_err(Error::from)?;
let data: String = connection.get(format!("guilds.{id}")).map_err(Error::from)?;
pub async fn get_guild(&self, id: Id<GuildMarker>) -> Result<PartialGuild, Error> {
let mut connection = self.client.get_async_connection().await.map_err(Error::from)?;
let data: String = connection.get(format!("guilds.{id}")).await.map_err(Error::from)?;
serde_json::from_str(data.as_str()).map_err(Error::from)
}

pub fn delete_guild(&self, id: Id<GuildMarker>) -> Result<(), RedisError> {
let mut connection = self.client.get_connection()?;
connection.del(format!("guilds.{id}"))
pub async fn delete_guild(&self, id: Id<GuildMarker>) -> Result<(), RedisError> {
let mut connection = self.client.get_async_connection().await?;
connection.del(format!("guilds.{id}")).await
}

pub fn get_by_position(
pub async fn get_by_position(
&self,
path: String,
position: usize,
) -> Result<Option<u32>, RedisError> {
let mut connection = self.client.get_connection()?;
let mut connection = self.client.get_async_connection().await?;
let result: Vec<u32> = connection.zrevrange_withscores(
path,
(position - 1) as isize,
(position - 1) as isize,
)?;
).await?;
Ok(result.first().cloned())
}

pub fn get_by_user(
pub async fn get_by_user(
&self,
path: String,
user_id: Id<UserMarker>,
) -> Result<(u32, u32), RedisError> {
let mut connection = self.client.get_connection()?;
let mut connection = self.client.get_async_connection().await?;
let user_id = user_id.to_string();
let score = connection.zscore(path.clone(), user_id.clone())?;
let position = connection.zrevrank(path, user_id)?;
let score = connection.zscore(path.clone(), user_id.clone()).await?;
let position = connection.zrevrank(path, user_id).await?;
Ok((score, position))
}

pub fn get_all(&self, path: String, limit: isize) -> Result<Vec<(String, u32)>, RedisError> {
let mut connection = self.client.get_connection()?;
connection.zrevrange_withscores(path, 0, limit - 1)
pub async fn get_all(&self, path: String, limit: isize) -> Result<Vec<(String, u32)>, RedisError> {
let mut connection = self.client.get_async_connection().await?;
connection.zrevrange_withscores(path, 0, limit - 1).await
}

pub fn increase(
pub async fn increase(
&self,
path: String,
user_id: Id<UserMarker>,
count: u8,
) -> Result<(), RedisError> {
let mut connection = self.client.get_connection()?;
connection.zincr(path, user_id.to_string(), count)
let mut connection = self.client.get_async_connection().await?;
connection.zincr(path, user_id.to_string(), count).await
}
}

impl Clone for RedisConnection {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
}
}
}
}
18 changes: 9 additions & 9 deletions src/events/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,33 @@ pub async fn fetch_and_set(
name: guild.name,
icon: guild.icon,
roles: guild.roles.iter().map(|role| role.id).collect()
})
}).await
}

pub fn on_guild_create(redis: &RedisConnection, event: Box<GuildCreate>) -> Result<(), ()> {
pub async fn on_guild_create(redis: &RedisConnection, event: Box<GuildCreate>) -> Result<(), ()> {
let mut roles = event.roles.to_owned();
roles.sort_by_cached_key(|role| role.position);
set_guild(redis, event.id, PartialGuild {
name: event.name.to_owned(),
icon: event.icon,
roles: roles.iter().map(|role| role.id).collect()
})
}).await
}

pub fn on_guild_update(redis: &RedisConnection, event: Box<GuildUpdate>) -> Result<(), ()> {
pub async fn on_guild_update(redis: &RedisConnection, event: Box<GuildUpdate>) -> Result<(), ()> {
let mut roles = event.roles.to_owned();
roles.sort_by_cached_key(|role| role.position);
set_guild(redis, event.id, PartialGuild {
name: event.name.to_owned(),
icon: event.icon,
roles: roles.iter().map(|role| role.id).collect()
})
}).await
}

pub fn set_guild(redis: &RedisConnection, id: Id<GuildMarker>, guild: PartialGuild) -> Result<(), ()> {
redis.set_guild(id, guild).map_err(|_| ())
pub async fn set_guild(redis: &RedisConnection, id: Id<GuildMarker>, guild: PartialGuild) -> Result<(), ()> {
redis.set_guild(id, guild).await.map_err(|_| ())
}

pub fn delete_guild(redis: &RedisConnection, id: Id<GuildMarker>) -> Result<(), ()> {
redis.delete_guild(id).map_err(|_| ())
pub async fn delete_guild(redis: &RedisConnection, id: Id<GuildMarker>) -> Result<(), ()> {
redis.delete_guild(id).await.map_err(|_| ())
}
6 changes: 3 additions & 3 deletions src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ pub async fn on_event(
}
Event::GuildCreate(event) => {
tokio::spawn(self::setup::run(event.id, event.joined_at, discord_http));
self::cache::on_guild_create(&context.redis, event).ok();
self::cache::on_guild_create(&context.redis, event).await.ok();
},
Event::GuildUpdate(event) => {
self::cache::on_guild_update(&context.redis, event).ok();
self::cache::on_guild_update(&context.redis, event).await.ok();
},
Event::GuildDelete(event) => {
self::cache::delete_guild(&context.redis, event.id).ok();
self::cache::delete_guild(&context.redis, event.id).await.ok();
},
Event::RoleCreate(event) => {
self::cache::fetch_and_set(&context.redis, discord_http, event.guild_id).await.ok();
Expand Down
2 changes: 2 additions & 0 deletions src/events/top.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ pub async fn run(
if config.top.week {
context.redis
.increase(format!("top_week.{guild_id}"), author_id, 1)
.await
.map_err(|_| ())?;
}

if config.top.day {
context.redis
.increase(format!("top_day.{guild_id}"), author_id, 1)
.await
.map_err(|_| ())?;
}

Expand Down
Loading

0 comments on commit 193cb88

Please sign in to comment.