From 465b75db8a116da48636a383344a9ab4b67ae12c Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sat, 9 Jul 2022 08:12:49 +0000 Subject: [PATCH 01/13] perf: Remove useless time equeal function --- Cargo.lock | 93 +++--------------------------- Cargo.toml | 1 - src/routes/auth/sessions/delete.rs | 3 +- 3 files changed, 8 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b97922..8a3a640 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,6 @@ dependencies = [ "reqwest", "rs-snowflake", "rust-argon2", - "rust-crypto", "serde", "serde_json", "serde_repr", @@ -651,7 +650,7 @@ dependencies = [ "native-tls", "parking_lot 0.11.2", "pretty_env_logger", - "rand 0.8.5", + "rand", "redis-protocol", "semver", "sha-1 0.9.8", @@ -662,12 +661,6 @@ dependencies = [ "url", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "futures" version = "0.3.21" @@ -774,12 +767,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generic-array" version = "0.14.5" @@ -814,7 +801,7 @@ dependencies = [ "nonzero_ext", "parking_lot 0.12.1", "quanta", - "rand 0.8.5", + "rand", "smallvec", ] @@ -1220,7 +1207,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -1625,29 +1612,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -1656,7 +1620,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core", ] [[package]] @@ -1666,24 +1630,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.3" @@ -1702,15 +1651,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redis-protocol" version = "4.1.0" @@ -1847,25 +1787,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rust-crypto" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -dependencies = [ - "gcc", - "libc", - "rand 0.3.23", - "rustc-serialize", - "time 0.1.44", -] - -[[package]] -name = "rustc-serialize" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" - [[package]] name = "rustls" version = "0.20.6" @@ -2162,7 +2083,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rand 0.8.5", + "rand", "rustls", "rustls-pemfile", "serde", @@ -2550,7 +2471,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha-1 0.10.0", "thiserror", "url", diff --git a/Cargo.toml b/Cargo.toml index ed5fbad..9f4d64f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ tower-http = { version = "0.3.0", features = ["cors"] } rust-argon2 = "1.0.0" governor = "0.4.2" validator = { version = "0.15", features = ["derive"] } -rust-crypto = "0.2.36" # Utility dotenv = "0.15.0" diff --git a/src/routes/auth/sessions/delete.rs b/src/routes/auth/sessions/delete.rs index 7151c99..8353b88 100644 --- a/src/routes/auth/sessions/delete.rs +++ b/src/routes/auth/sessions/delete.rs @@ -1,7 +1,6 @@ use crate::extractors::*; use crate::structures::*; use crate::utils::*; -use crypto::util::fixed_time_eq; use serde::Deserialize; use validator::Validate; @@ -17,7 +16,7 @@ pub async fn delete( ) -> Result<()> { let session = id.session(user.id).await?; - if !fixed_time_eq(session.token.as_bytes(), data.token.as_bytes()) { + if session.token != data.token { return Err(Error::InvalidToken); } From adc27d4c8f08ced1b0b81e0c18ed8db27b39dc0e Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 07:00:11 +0000 Subject: [PATCH 02/13] feat: Relationships --- src/gateway/events/authenticate.rs | 3 +- src/routes/users/mod.rs | 6 ++- src/routes/users/open_dm.rs | 25 +++++++++ src/routes/users/relationships/add.rs | 64 ++++++++++++++++++++++++ src/routes/users/relationships/block.rs | 45 +++++++++++++++++ src/routes/users/relationships/delete.rs | 41 +++++++++++++++ src/routes/users/relationships/fetch.rs | 7 +++ src/routes/users/relationships/mod.rs | 13 +++++ src/structures/user.rs | 30 +++++++++-- src/utils/error.rs | 6 +++ src/utils/permissions.rs | 12 ++++- 11 files changed, 245 insertions(+), 7 deletions(-) create mode 100644 src/routes/users/open_dm.rs create mode 100644 src/routes/users/relationships/add.rs create mode 100644 src/routes/users/relationships/block.rs create mode 100644 src/routes/users/relationships/delete.rs create mode 100644 src/routes/users/relationships/fetch.rs create mode 100644 src/routes/users/relationships/mod.rs diff --git a/src/gateway/events/authenticate.rs b/src/gateway/events/authenticate.rs index addeb20..76614ef 100644 --- a/src/gateway/events/authenticate.rs +++ b/src/gateway/events/authenticate.rs @@ -33,6 +33,7 @@ pub async fn run(client: &Client, payload: ClientPayload) { let mut permissions = client.permissions.lock().await; let mut channels = user.fetch_channels().await.unwrap(); let servers = user.fetch_servers().await.unwrap(); + let users = user.fetch_relations().await.unwrap(); if !servers.is_empty() { let mut server_ids: String = servers.iter().map(|s| s.id.to_string() + ",").collect(); @@ -83,7 +84,7 @@ pub async fn run(client: &Client, payload: ClientPayload) { client .send(Payload::Ready { user, - users: vec![], // TODO: + users, servers, channels, }) diff --git a/src/routes/users/mod.rs b/src/routes/users/mod.rs index 4578878..5184b05 100644 --- a/src/routes/users/mod.rs +++ b/src/routes/users/mod.rs @@ -1,11 +1,15 @@ -mod fetch; +pub mod fetch; +pub mod open_dm; +pub mod relationships; pub fn routes() -> axum::Router { use crate::middlewares::*; use axum::{middleware, routing::*, Router}; Router::new() + .nest("/@me/relationships", relationships::routes()) .route("/@me", get(fetch::fetch_me)) .route("/:user_id", get(fetch::fetch_one)) + .route("/:user_id/dm", get(open_dm::open_dm)) .layer(middleware::from_fn(ratelimit::handle!(20, 1000 * 5))) } diff --git a/src/routes/users/open_dm.rs b/src/routes/users/open_dm.rs new file mode 100644 index 0000000..41e8199 --- /dev/null +++ b/src/routes/users/open_dm.rs @@ -0,0 +1,25 @@ +use crate::extractors::*; +use crate::structures::*; +use crate::utils::*; + +pub async fn open_dm( + Extension(user): Extension, + Path(id): Path, +) -> Result> { + let channel = Channel::select() + .filter("type = $1 AND recipients @> $2 AND recipients @> $3") + .bind(ChannelTypes::Direct) + .bind(user.id) + .bind(id) + .fetch_one(pool()) + .await; + + if let Ok(channel) = channel { + return Ok(channel.into()); + } + + let target = id.user().await?; + let channel = Channel::new_dm(user.id, target.id); + + Ok(channel.save().await?.into()) +} diff --git a/src/routes/users/relationships/add.rs b/src/routes/users/relationships/add.rs new file mode 100644 index 0000000..b4f57cc --- /dev/null +++ b/src/routes/users/relationships/add.rs @@ -0,0 +1,64 @@ +use crate::extractors::*; +use crate::structures::*; +use crate::utils::*; + +pub async fn add(Extension(mut user): Extension, Path(id): Path) -> Result<()> { + if let Some(&status) = user.relations.0.get(&id) { + if status == RelationshipStatus::Friend { + return Err(Error::AlreadyFriends); + } + + if status == RelationshipStatus::Blocked { + return Err(Error::Blocked); + } + + if status == RelationshipStatus::BlockedByOther { + return Err(Error::BlockedByOther); + } + + if status == RelationshipStatus::Outgoing { + return Err(Error::AlreadySendRequest); + } + } + + let mut target = id.user().await?; + + if let Some(&target_status) = target.relations.0.get(&user.id) { + if target_status == RelationshipStatus::Outgoing { + // Accept friend request + target + .relations + .0 + .insert(user.id, RelationshipStatus::Friend); + user.relations + .0 + .insert(target.id, RelationshipStatus::Friend); + } + } else { + // Send friend request + target + .relations + .0 + .insert(user.id, RelationshipStatus::Incoming); + user.relations + .0 + .insert(target.id, RelationshipStatus::Outgoing); + } + + let mut tx = pool().begin().await?; + + user.update_partial() + .relations(user.relations.clone()) + .update(&mut tx) + .await?; + + target + .update_partial() + .relations(target.relations.clone()) + .update(&mut tx) + .await?; + + tx.commit().await?; + + Ok(()) +} diff --git a/src/routes/users/relationships/block.rs b/src/routes/users/relationships/block.rs new file mode 100644 index 0000000..403d239 --- /dev/null +++ b/src/routes/users/relationships/block.rs @@ -0,0 +1,45 @@ +use crate::extractors::*; +use crate::structures::*; +use crate::utils::*; + +pub async fn block(Extension(mut user): Extension, Path(id): Path) -> Result<()> { + let mut target = id.user().await?; + + if let Some(&status) = user.relations.0.get(&id) { + if status == RelationshipStatus::Blocked { + return Ok(()); + } + + if status == RelationshipStatus::BlockedByOther { + user.relations.0.insert(id, RelationshipStatus::Blocked); + } else { + user.relations.0.insert(id, RelationshipStatus::Blocked); + target + .relations + .0 + .insert(user.id, RelationshipStatus::BlockedByOther); + } + } else { + user.relations.0.insert(id, RelationshipStatus::Blocked); + target + .relations + .0 + .insert(user.id, RelationshipStatus::BlockedByOther); + } + + let mut tx = pool().begin().await?; + + user.update_partial() + .relations(user.relations.clone()) + .update(&mut tx) + .await?; + target + .update_partial() + .relations(target.relations.clone()) + .update(&mut tx) + .await?; + + tx.commit().await?; + + Ok(()) +} diff --git a/src/routes/users/relationships/delete.rs b/src/routes/users/relationships/delete.rs new file mode 100644 index 0000000..6bb3ddf --- /dev/null +++ b/src/routes/users/relationships/delete.rs @@ -0,0 +1,41 @@ +use crate::extractors::*; +use crate::structures::*; +use crate::utils::*; + +pub async fn delete(Extension(mut user): Extension, Path(id): Path) -> Result<()> { + match user.relations.0.get(&id) { + Some(&status) => { + let mut target = id.user().await?; + let target_status = target.relations.0.get(&user.id).unwrap(); + + if status != RelationshipStatus::BlockedByOther { + if target_status == &RelationshipStatus::Blocked { + user.relations + .0 + .insert(target.id, RelationshipStatus::BlockedByOther); + } else { + target.relations.0.remove(&user.id); + user.relations.0.remove(&target.id); + } + + let mut tx = pool().begin().await?; + + user.update_partial() + .relations(user.relations.clone()) + .update(&mut tx) + .await?; + + target + .update_partial() + .relations(target.relations.clone()) + .update(&mut tx) + .await?; + + tx.commit().await?; + } + + Ok(()) + } + _ => Err(Error::UnknownUser), + } +} diff --git a/src/routes/users/relationships/fetch.rs b/src/routes/users/relationships/fetch.rs new file mode 100644 index 0000000..900c593 --- /dev/null +++ b/src/routes/users/relationships/fetch.rs @@ -0,0 +1,7 @@ +use crate::extractors::*; +use crate::structures::*; +use crate::utils::*; + +pub async fn fetch_many(Extension(user): Extension) -> Result>> { + Ok(user.fetch_relations().await?.into()) +} diff --git a/src/routes/users/relationships/mod.rs b/src/routes/users/relationships/mod.rs new file mode 100644 index 0000000..6cf1144 --- /dev/null +++ b/src/routes/users/relationships/mod.rs @@ -0,0 +1,13 @@ +pub mod add; +pub mod block; +pub mod delete; +pub mod fetch; + +pub fn routes() -> axum::Router { + use axum::{routing::*, Router}; + + Router::new().route("/", get(fetch::fetch_many)).route( + "/:user_id", + delete(delete::delete).post(add::add).put(block::block), + ) +} diff --git a/src/structures/user.rs b/src/structures/user.rs index 7fca7b0..7946587 100644 --- a/src/structures/user.rs +++ b/src/structures/user.rs @@ -3,6 +3,19 @@ use crate::database::pool; use crate::utils::{snowflake, Badges}; use ormlite::model::*; use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use sqlx::types::Json; +use std::collections::HashMap; + +#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Copy, PartialEq, OpgModel, sqlx::Type)] +#[repr(i32)] +pub enum RelationshipStatus { + Friend = 0, + Incoming = 1, + Outgoing = 2, + Blocked = 3, + BlockedByOther = 4, +} #[serde_as] #[derive(Debug, Serialize, Deserialize, FromRow, Model, Default, Clone, OpgModel)] @@ -13,11 +26,14 @@ pub struct User { pub id: i64, pub username: String, pub avatar: Option, + pub badges: Badges, + // Private fields #[serde(skip)] - pub password: String, + pub relations: Json>, #[serde(skip)] pub email: String, - pub badges: Badges, + #[serde(skip)] + pub password: String, #[serde(skip)] pub verified: bool, } @@ -29,7 +45,6 @@ impl User { username, email, password, - verified: false, ..Default::default() } } @@ -77,7 +92,14 @@ impl User { .await } - // pub async fn fetch_relations(&self) {} + pub async fn fetch_relations(&self) -> Result, ormlite::Error> { + let ids: Vec = self.relations.0.keys().copied().collect(); + User::select() + .filter("id IN $1") + .bind(ids) + .fetch_all(pool()) + .await + } pub async fn fetch_by_token(token: &str) -> Option { User::select() diff --git a/src/utils/error.rs b/src/utils/error.rs index a686ada..2a23772 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -27,6 +27,12 @@ quick_error! { MissingAccess { display("You missing access to perform this action ") } DatabaseError { display("Database cannot process this operation") } + + Blocked + BlockedByOther + AlreadyFriends + AlreadySendRequest + UnknownAccount UnknownBot UnknownChannel diff --git a/src/utils/permissions.rs b/src/utils/permissions.rs index 016ad82..c96b50f 100644 --- a/src/utils/permissions.rs +++ b/src/utils/permissions.rs @@ -75,7 +75,17 @@ impl Permissions { if let Some(channel) = channel { if channel.is_dm() { p.insert(*DEFAULT_PERMISSION_DM); - // TODO: Check user relations + + for id in channel.recipients.as_ref().unwrap() { + if let Some(&status) = user.relations.0.get(id) { + if status == RelationshipStatus::Blocked + || status == RelationshipStatus::BlockedByOther + { + p.remove(Permissions::SEND_MESSAGES); + break; + } + } + } } if channel.is_group() From d6b41e25f037951f42c19d3afc49717854fe5c5e Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 07:15:20 +0000 Subject: [PATCH 03/13] fix: must be friends to speak to each other --- src/utils/permissions.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/utils/permissions.rs b/src/utils/permissions.rs index c96b50f..80695e2 100644 --- a/src/utils/permissions.rs +++ b/src/utils/permissions.rs @@ -76,15 +76,18 @@ impl Permissions { if channel.is_dm() { p.insert(*DEFAULT_PERMISSION_DM); - for id in channel.recipients.as_ref().unwrap() { - if let Some(&status) = user.relations.0.get(id) { - if status == RelationshipStatus::Blocked - || status == RelationshipStatus::BlockedByOther - { - p.remove(Permissions::SEND_MESSAGES); - break; - } - } + let target_id = channel + .recipients + .as_ref() + .unwrap() + .iter() + .find(|&x| *x != user.id) + .unwrap(); + + let status = user.relations.0.get(target_id).unwrap(); + + if status != &RelationshipStatus::Friend { + p.remove(Permissions::SEND_MESSAGES); } } From ff36971773f03327c60eb111f8b3090faa1f3679 Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 07:15:52 +0000 Subject: [PATCH 04/13] docs: document relationships routes --- src/routes/docs.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/docs.rs b/src/routes/docs.rs index 137fedc..9bf305c 100644 --- a/src/routes/docs.rs +++ b/src/routes/docs.rs @@ -51,7 +51,13 @@ pub fn document(app: Router) -> Router { // Users ("users/@me"): { GET: { 200: User, tags: {users} } }, ("users" / { user_id: u64 }): { GET: { 200: User, tags: {users} } }, - + ("users" / { user_id: u64 } / "dm"): { GET: { 200: Channel, tags: {users} } }, + ("users/@me/relationships"): { GET: { 200: Vec, tags: {users}} }, + ("users/@me/relationships" / { user_id: u64 }): { + POST: { 200: None, tags: {users} }, + PUT: { 200: None, tags: {users} }, + DELETE: { 200: None, tags: {users} } + }, // Channels ("channels"): { From 8102274dfa049f7f145afc6d8ee42f635c3e660a Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 07:20:58 +0000 Subject: [PATCH 05/13] perf: Simplify status checking --- src/routes/docs.rs | 4 ++-- src/utils/permissions.rs | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/routes/docs.rs b/src/routes/docs.rs index 9bf305c..5727b25 100644 --- a/src/routes/docs.rs +++ b/src/routes/docs.rs @@ -53,10 +53,10 @@ pub fn document(app: Router) -> Router { ("users" / { user_id: u64 }): { GET: { 200: User, tags: {users} } }, ("users" / { user_id: u64 } / "dm"): { GET: { 200: Channel, tags: {users} } }, ("users/@me/relationships"): { GET: { 200: Vec, tags: {users}} }, - ("users/@me/relationships" / { user_id: u64 }): { + ("users/@me/relationships" / { user_id: u64 }): { POST: { 200: None, tags: {users} }, PUT: { 200: None, tags: {users} }, - DELETE: { 200: None, tags: {users} } + DELETE: { 200: None, tags: {users} } }, // Channels diff --git a/src/utils/permissions.rs b/src/utils/permissions.rs index 80695e2..1de44fa 100644 --- a/src/utils/permissions.rs +++ b/src/utils/permissions.rs @@ -76,15 +76,7 @@ impl Permissions { if channel.is_dm() { p.insert(*DEFAULT_PERMISSION_DM); - let target_id = channel - .recipients - .as_ref() - .unwrap() - .iter() - .find(|&x| *x != user.id) - .unwrap(); - - let status = user.relations.0.get(target_id).unwrap(); + let status = user.relations.0.values().next().unwrap(); if status != &RelationshipStatus::Friend { p.remove(Permissions::SEND_MESSAGES); From aa098b77cdea036cee1036ee837af990cf521612 Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 08:46:20 +0000 Subject: [PATCH 06/13] fix: Use ANY(*) operator instead of raw query --- src/gateway/events/authenticate.rs | 22 +++++++++------------- src/structures/member.rs | 17 ++++++----------- src/structures/user.rs | 7 ++++++- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/gateway/events/authenticate.rs b/src/gateway/events/authenticate.rs index 76614ef..5abc733 100644 --- a/src/gateway/events/authenticate.rs +++ b/src/gateway/events/authenticate.rs @@ -1,5 +1,3 @@ -use fred::interfaces::PubsubInterface; - use crate::database::pool; use crate::gateway::{ client::Client, @@ -7,6 +5,7 @@ use crate::gateway::{ }; use crate::structures::*; use crate::utils::Permissions; +use fred::interfaces::PubsubInterface; pub async fn run(client: &Client, payload: ClientPayload) { if client.user.lock().await.is_some() { @@ -36,17 +35,14 @@ pub async fn run(client: &Client, payload: ClientPayload) { let users = user.fetch_relations().await.unwrap(); if !servers.is_empty() { - let mut server_ids: String = servers.iter().map(|s| s.id.to_string() + ",").collect(); - server_ids.remove(server_ids.len() - 1); - - let mut other_channels = Channel::query(&format!( - "SELECT * FROM {} WHERE server_id = ({})", - Channel::table_name(), - server_ids - )) - .fetch_all(pool()) - .await - .unwrap(); + let server_ids: Vec = servers.iter().map(|s| s.id).collect(); + + let mut other_channels = Channel::select() + .filter("server_id = ANY($1)") + .bind(server_ids) + .fetch_all(pool()) + .await + .unwrap(); channels.append(&mut other_channels); } diff --git a/src/structures/member.rs b/src/structures/member.rs index 47110a5..22a1e90 100644 --- a/src/structures/member.rs +++ b/src/structures/member.rs @@ -40,17 +40,12 @@ impl Member { return vec![]; } - // FIXME: Do this in efficient way - let mut roles: String = self.roles.iter().map(|&id| id.to_string() + ",").collect(); - roles.remove(roles.len() - 1); - - Role::query(&format!( - "SELECT * FROM roles WHERE server_id IN ({})", - roles - )) - .fetch_all(pool()) - .await - .unwrap() + Role::select() + .filter("server_id IN ANY($1)") + .bind(self.roles.clone()) + .fetch_all(pool()) + .await + .unwrap() } #[cfg(test)] diff --git a/src/structures/user.rs b/src/structures/user.rs index 7946587..983d2e7 100644 --- a/src/structures/user.rs +++ b/src/structures/user.rs @@ -94,8 +94,13 @@ impl User { pub async fn fetch_relations(&self) -> Result, ormlite::Error> { let ids: Vec = self.relations.0.keys().copied().collect(); + + if ids.is_empty() { + return Ok(vec![]); + } + User::select() - .filter("id IN $1") + .filter("id = ANY($1)") .bind(ids) .fetch_all(pool()) .await From 8e2c5abaafa4928552e93c87c777861e89044cff Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 09:24:36 +0000 Subject: [PATCH 07/13] fix: must use equal insead of IN --- src/structures/member.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/member.rs b/src/structures/member.rs index 22a1e90..68bd523 100644 --- a/src/structures/member.rs +++ b/src/structures/member.rs @@ -41,7 +41,7 @@ impl Member { } Role::select() - .filter("server_id IN ANY($1)") + .filter("server_id = ANY($1)") .bind(self.roles.clone()) .fetch_all(pool()) .await From bb730a2d8c0770a6d142884f4053f2ecb69b1351 Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 09:25:54 +0000 Subject: [PATCH 08/13] feat: add fetch relations route --- src/routes/docs.rs | 1 + src/routes/users/fetch.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/routes/docs.rs b/src/routes/docs.rs index 5727b25..2143f7f 100644 --- a/src/routes/docs.rs +++ b/src/routes/docs.rs @@ -49,6 +49,7 @@ pub fn document(app: Router) -> Router { }, // Users + ("users"): { GET: { 200: Vec, tags: {users}} }, ("users/@me"): { GET: { 200: User, tags: {users} } }, ("users" / { user_id: u64 }): { GET: { 200: User, tags: {users} } }, ("users" / { user_id: u64 } / "dm"): { GET: { 200: Channel, tags: {users} } }, diff --git a/src/routes/users/fetch.rs b/src/routes/users/fetch.rs index 30ea25a..858f41d 100644 --- a/src/routes/users/fetch.rs +++ b/src/routes/users/fetch.rs @@ -6,6 +6,16 @@ pub async fn fetch_me(Extension(user): Extension) -> Json { user.into() } -pub async fn fetch_one(Path(id): Path) -> Result> { +pub async fn fetch_one( + Extension(user): Extension, + Path(id): Path, +) -> Result> { + if user.id == id { + return Ok(user.into()); + } Ok(id.user().await?.into()) } + +pub async fn fetch_many(Extension(user): Extension) -> Result>> { + Ok(user.fetch_relations().await?.into()) +} From 49a3b0463e3481f99bcd554698c2673155317c3e Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 13:18:37 +0000 Subject: [PATCH 09/13] feat: add relationship field on user & emit user events --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/gateway/events/authenticate.rs | 11 +++- src/routes/docs.rs | 1 - src/routes/users/mod.rs | 1 + src/routes/users/relationships/add.rs | 62 ++++++++----------- src/routes/users/relationships/block.rs | 51 +++++++++------- src/routes/users/relationships/delete.rs | 76 ++++++++++++++---------- src/routes/users/relationships/fetch.rs | 7 --- src/routes/users/relationships/mod.rs | 3 +- src/structures/user.rs | 7 ++- 11 files changed, 116 insertions(+), 107 deletions(-) delete mode 100644 src/routes/users/relationships/fetch.rs diff --git a/Cargo.lock b/Cargo.lock index 8a3a640..1dcda86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1408,7 +1408,7 @@ dependencies = [ [[package]] name = "ormlite-macro" version = "0.3.1" -source = "git+https://github.com/abdulrahman1s/ormlite#087ed11287e64899996cea779466482013480c00" +source = "git+https://github.com/abdulrahman1s/ormlite?rev=9f8ef977d3516c12e2cf7e35e7a484d7f5911c38#9f8ef977d3516c12e2cf7e35e7a484d7f5911c38" dependencies = [ "derive_builder", "ormlite-core", diff --git a/Cargo.toml b/Cargo.toml index 9f4d64f..20b6796 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,4 +62,4 @@ opg = { git = "https://github.com/abdulrahman1s/opg", rev = "24f72e7cf09da7cd61b [patch.crates-io] -ormlite-macro = { git = "https://github.com/abdulrahman1s/ormlite" } +ormlite-macro = { git = "https://github.com/abdulrahman1s/ormlite", rev = "9f8ef977d3516c12e2cf7e35e7a484d7f5911c38" } diff --git a/src/gateway/events/authenticate.rs b/src/gateway/events/authenticate.rs index 5abc733..5fa14c7 100644 --- a/src/gateway/events/authenticate.rs +++ b/src/gateway/events/authenticate.rs @@ -32,7 +32,16 @@ pub async fn run(client: &Client, payload: ClientPayload) { let mut permissions = client.permissions.lock().await; let mut channels = user.fetch_channels().await.unwrap(); let servers = user.fetch_servers().await.unwrap(); - let users = user.fetch_relations().await.unwrap(); + let users = user + .fetch_relations() + .await + .unwrap() + .into_iter() + .map(|mut u| { + u.relationship = user.relations.0.get(&u.id).copied(); + u + }) + .collect(); if !servers.is_empty() { let server_ids: Vec = servers.iter().map(|s| s.id).collect(); diff --git a/src/routes/docs.rs b/src/routes/docs.rs index 2143f7f..56af627 100644 --- a/src/routes/docs.rs +++ b/src/routes/docs.rs @@ -53,7 +53,6 @@ pub fn document(app: Router) -> Router { ("users/@me"): { GET: { 200: User, tags: {users} } }, ("users" / { user_id: u64 }): { GET: { 200: User, tags: {users} } }, ("users" / { user_id: u64 } / "dm"): { GET: { 200: Channel, tags: {users} } }, - ("users/@me/relationships"): { GET: { 200: Vec, tags: {users}} }, ("users/@me/relationships" / { user_id: u64 }): { POST: { 200: None, tags: {users} }, PUT: { 200: None, tags: {users} }, diff --git a/src/routes/users/mod.rs b/src/routes/users/mod.rs index 5184b05..e043801 100644 --- a/src/routes/users/mod.rs +++ b/src/routes/users/mod.rs @@ -8,6 +8,7 @@ pub fn routes() -> axum::Router { Router::new() .nest("/@me/relationships", relationships::routes()) + .route("/", get(fetch::fetch_many)) .route("/@me", get(fetch::fetch_me)) .route("/:user_id", get(fetch::fetch_one)) .route("/:user_id/dm", get(open_dm::open_dm)) diff --git a/src/routes/users/relationships/add.rs b/src/routes/users/relationships/add.rs index b4f57cc..fefe795 100644 --- a/src/routes/users/relationships/add.rs +++ b/src/routes/users/relationships/add.rs @@ -1,58 +1,42 @@ use crate::extractors::*; +use crate::gateway::*; use crate::structures::*; use crate::utils::*; pub async fn add(Extension(mut user): Extension, Path(id): Path) -> Result<()> { if let Some(&status) = user.relations.0.get(&id) { - if status == RelationshipStatus::Friend { - return Err(Error::AlreadyFriends); - } - - if status == RelationshipStatus::Blocked { - return Err(Error::Blocked); - } - - if status == RelationshipStatus::BlockedByOther { - return Err(Error::BlockedByOther); - } - - if status == RelationshipStatus::Outgoing { - return Err(Error::AlreadySendRequest); - } + match status { + RelationshipStatus::Friend => return Err(Error::AlreadyFriends), + RelationshipStatus::Blocked => return Err(Error::Blocked), + RelationshipStatus::BlockedByOther => return Err(Error::BlockedByOther), + RelationshipStatus::Outgoing => return Err(Error::AlreadySendRequest), + _ => {} + }; } let mut target = id.user().await?; - if let Some(&target_status) = target.relations.0.get(&user.id) { - if target_status == RelationshipStatus::Outgoing { - // Accept friend request - target - .relations - .0 - .insert(user.id, RelationshipStatus::Friend); - user.relations - .0 - .insert(target.id, RelationshipStatus::Friend); - } + // (user_status, target_status) + let status = if Some(&RelationshipStatus::Outgoing) == target.relations.0.get(&user.id) { + // Accept friend request + (RelationshipStatus::Friend, RelationshipStatus::Friend) } else { // Send friend request - target - .relations - .0 - .insert(user.id, RelationshipStatus::Incoming); - user.relations - .0 - .insert(target.id, RelationshipStatus::Outgoing); - } + (RelationshipStatus::Outgoing, RelationshipStatus::Incoming) + }; + + user.relations.0.insert(target.id, status.0); + target.relations.0.insert(user.id, status.1); let mut tx = pool().begin().await?; - user.update_partial() + let mut user = user + .update_partial() .relations(user.relations.clone()) .update(&mut tx) .await?; - target + let mut target = target .update_partial() .relations(target.relations.clone()) .update(&mut tx) @@ -60,5 +44,11 @@ pub async fn add(Extension(mut user): Extension, Path(id): Path) -> R tx.commit().await?; + user.relationship = status.1.into(); + target.relationship = status.0.into(); + + publish(user.id, Payload::UserUpdate(target.clone())).await; + publish(target.id, Payload::UserUpdate(user)).await; + Ok(()) } diff --git a/src/routes/users/relationships/block.rs b/src/routes/users/relationships/block.rs index 403d239..b272875 100644 --- a/src/routes/users/relationships/block.rs +++ b/src/routes/users/relationships/block.rs @@ -1,39 +1,38 @@ use crate::extractors::*; +use crate::gateway::*; use crate::structures::*; use crate::utils::*; pub async fn block(Extension(mut user): Extension, Path(id): Path) -> Result<()> { - let mut target = id.user().await?; + if Some(&RelationshipStatus::Blocked) == user.relations.0.get(&id) { + return Ok(()); + } - if let Some(&status) = user.relations.0.get(&id) { - if status == RelationshipStatus::Blocked { - return Ok(()); - } - - if status == RelationshipStatus::BlockedByOther { - user.relations.0.insert(id, RelationshipStatus::Blocked); - } else { - user.relations.0.insert(id, RelationshipStatus::Blocked); - target - .relations - .0 - .insert(user.id, RelationshipStatus::BlockedByOther); - } + let status = if Some(&RelationshipStatus::BlockedByOther) == user.relations.0.get(&id) { + // The target blocked me, block him is well + (RelationshipStatus::Blocked, RelationshipStatus::Blocked) } else { - user.relations.0.insert(id, RelationshipStatus::Blocked); - target - .relations - .0 - .insert(user.id, RelationshipStatus::BlockedByOther); - } + // Block the target + ( + RelationshipStatus::Blocked, + RelationshipStatus::BlockedByOther, + ) + }; + + let mut target = id.user().await?; + + user.relations.0.insert(target.id, status.0); + target.relations.0.insert(user.id, status.1); let mut tx = pool().begin().await?; - user.update_partial() + let mut user = user + .update_partial() .relations(user.relations.clone()) .update(&mut tx) .await?; - target + + let mut target = target .update_partial() .relations(target.relations.clone()) .update(&mut tx) @@ -41,5 +40,11 @@ pub async fn block(Extension(mut user): Extension, Path(id): Path) -> tx.commit().await?; + user.relationship = status.1.into(); + target.relationship = status.0.into(); + + publish(user.id, Payload::UserUpdate(target.clone())).await; + publish(target.id, Payload::UserUpdate(user)).await; + Ok(()) } diff --git a/src/routes/users/relationships/delete.rs b/src/routes/users/relationships/delete.rs index 6bb3ddf..c7093fa 100644 --- a/src/routes/users/relationships/delete.rs +++ b/src/routes/users/relationships/delete.rs @@ -1,41 +1,51 @@ use crate::extractors::*; +use crate::gateway::*; use crate::structures::*; use crate::utils::*; pub async fn delete(Extension(mut user): Extension, Path(id): Path) -> Result<()> { - match user.relations.0.get(&id) { - Some(&status) => { - let mut target = id.user().await?; - let target_status = target.relations.0.get(&user.id).unwrap(); - - if status != RelationshipStatus::BlockedByOther { - if target_status == &RelationshipStatus::Blocked { - user.relations - .0 - .insert(target.id, RelationshipStatus::BlockedByOther); - } else { - target.relations.0.remove(&user.id); - user.relations.0.remove(&target.id); - } - - let mut tx = pool().begin().await?; - - user.update_partial() - .relations(user.relations.clone()) - .update(&mut tx) - .await?; - - target - .update_partial() - .relations(target.relations.clone()) - .update(&mut tx) - .await?; - - tx.commit().await?; - } - - Ok(()) + let status = user.relations.0.get(&id); + + if status.is_none() { + return Err(Error::UnknownUser); + } + + // He blocked you. you can't remove it by yourself + if status.unwrap() != &RelationshipStatus::BlockedByOther { + let mut target = id.user().await?; + + if target.relations.0.get(&user.id).unwrap() == &RelationshipStatus::Blocked { + // If you trying to unblock him but he also blocked you thats will happen + user.relations + .0 + .insert(target.id, RelationshipStatus::BlockedByOther); + } else { + target.relations.0.remove(&user.id); + user.relations.0.remove(&target.id); } - _ => Err(Error::UnknownUser), + + let mut tx = pool().begin().await?; + + let mut user = user + .update_partial() + .relations(user.relations.clone()) + .update(&mut tx) + .await?; + + let mut target = target + .update_partial() + .relations(target.relations.clone()) + .update(&mut tx) + .await?; + + tx.commit().await?; + + user.relationship = target.relations.0.get(&user.id).copied(); + target.relationship = user.relations.0.get(&target.id).copied(); + + publish(user.id, Payload::UserUpdate(target.clone())).await; + publish(target.id, Payload::UserUpdate(user)).await; } + + Ok(()) } diff --git a/src/routes/users/relationships/fetch.rs b/src/routes/users/relationships/fetch.rs deleted file mode 100644 index 900c593..0000000 --- a/src/routes/users/relationships/fetch.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::extractors::*; -use crate::structures::*; -use crate::utils::*; - -pub async fn fetch_many(Extension(user): Extension) -> Result>> { - Ok(user.fetch_relations().await?.into()) -} diff --git a/src/routes/users/relationships/mod.rs b/src/routes/users/relationships/mod.rs index 6cf1144..9dce4d9 100644 --- a/src/routes/users/relationships/mod.rs +++ b/src/routes/users/relationships/mod.rs @@ -1,12 +1,11 @@ pub mod add; pub mod block; pub mod delete; -pub mod fetch; pub fn routes() -> axum::Router { use axum::{routing::*, Router}; - Router::new().route("/", get(fetch::fetch_many)).route( + Router::new().route( "/:user_id", delete(delete::delete).post(add::add).put(block::block), ) diff --git a/src/structures/user.rs b/src/structures/user.rs index 983d2e7..3705813 100644 --- a/src/structures/user.rs +++ b/src/structures/user.rs @@ -27,9 +27,12 @@ pub struct User { pub username: String, pub avatar: Option, pub badges: Badges, - // Private fields #[serde(skip)] pub relations: Json>, + #[ormlite(skip)] + #[sqlx(default)] + pub relationship: Option, + // Private fields #[serde(skip)] pub email: String, #[serde(skip)] @@ -93,7 +96,7 @@ impl User { } pub async fn fetch_relations(&self) -> Result, ormlite::Error> { - let ids: Vec = self.relations.0.keys().copied().collect(); + let ids: Vec = vec![]; //self.relations.0.keys().copied().collect(); if ids.is_empty() { return Ok(vec![]); From 28417402c6ce8b0c5090351b7b99d41f8b70c88e Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 13:25:17 +0000 Subject: [PATCH 10/13] fix(ws): subscribe to friends --- src/gateway/events/authenticate.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gateway/events/authenticate.rs b/src/gateway/events/authenticate.rs index 5fa14c7..656f869 100644 --- a/src/gateway/events/authenticate.rs +++ b/src/gateway/events/authenticate.rs @@ -32,7 +32,7 @@ pub async fn run(client: &Client, payload: ClientPayload) { let mut permissions = client.permissions.lock().await; let mut channels = user.fetch_channels().await.unwrap(); let servers = user.fetch_servers().await.unwrap(); - let users = user + let users: Vec = user .fetch_relations() .await .unwrap() @@ -56,6 +56,12 @@ pub async fn run(client: &Client, payload: ClientPayload) { channels.append(&mut other_channels); } + for user in &users { + if user.relationship == Some(RelationshipStatus::Friend) { + subscriptions.push(user.id); + } + } + for server in &servers { subscriptions.push(server.id); permissions.insert( From c34f81f83120d804f41f78e1631194736efafbdb Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 13:44:39 +0000 Subject: [PATCH 11/13] feat: apply limitations --- src/config.rs | 1 + src/routes/users/relationships/add.rs | 5 +++++ src/routes/users/relationships/block.rs | 5 +++++ src/utils/error.rs | 2 ++ 4 files changed, 13 insertions(+) diff --git a/src/config.rs b/src/config.rs index 46235c6..37f4e33 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,6 +31,7 @@ lazy_static! { // User related pub static ref MAX_FRIENDS: u64 = get!("MAX_FRIENDS", "1000").parse().unwrap(); pub static ref MAX_BLOCKED: u64 = get!("MAX_BLOCKED", "1000").parse().unwrap(); + pub static ref MAX_FRIEND_REQUESTS: u64 = get!("MAX_FRIEND_REQUESTS", "100").parse().unwrap(); // Group related pub static ref MAX_GROUPS: u64 = get!("MAX_GROUPS", "100").parse().unwrap(); diff --git a/src/routes/users/relationships/add.rs b/src/routes/users/relationships/add.rs index fefe795..b9388f0 100644 --- a/src/routes/users/relationships/add.rs +++ b/src/routes/users/relationships/add.rs @@ -1,9 +1,14 @@ +use crate::config::MAX_FRIEND_REQUESTS; use crate::extractors::*; use crate::gateway::*; use crate::structures::*; use crate::utils::*; pub async fn add(Extension(mut user): Extension, Path(id): Path) -> Result<()> { + if *MAX_FRIEND_REQUESTS <= user.relations.len() as u64 { + return Err(Error::MaximumFriendRequests); + } + if let Some(&status) = user.relations.0.get(&id) { match status { RelationshipStatus::Friend => return Err(Error::AlreadyFriends), diff --git a/src/routes/users/relationships/block.rs b/src/routes/users/relationships/block.rs index b272875..b6f67cc 100644 --- a/src/routes/users/relationships/block.rs +++ b/src/routes/users/relationships/block.rs @@ -1,9 +1,14 @@ +use crate::config::MAX_BLOCKED; use crate::extractors::*; use crate::gateway::*; use crate::structures::*; use crate::utils::*; pub async fn block(Extension(mut user): Extension, Path(id): Path) -> Result<()> { + if *MAX_BLOCKED <= user.relations.len() as u64 { + return Err(Error::MaximumBlocked); + } + if Some(&RelationshipStatus::Blocked) == user.relations.0.get(&id) { return Ok(()); } diff --git a/src/utils/error.rs b/src/utils/error.rs index 2a23772..6828312 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -52,6 +52,8 @@ quick_error! { MaximumChannels { display("Maximum number of channels reached") } MaximumGroupMembers { display("Maximum number of group members reached") } MaximumBots { display("Maximum number of bots reached") } + MaximumFriendRequests { display("Maximum number of friend requests reached") } + MaximumBlocked { display("Maximum number of blocked user reached") } } } From fa7762d76836eea70f996147d522d96f8d8af97b Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 13:45:37 +0000 Subject: [PATCH 12/13] fix: subscribe on user relation update --- src/gateway/events/authenticate.rs | 4 +--- src/gateway/upgrade.rs | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gateway/events/authenticate.rs b/src/gateway/events/authenticate.rs index 656f869..28a6bb9 100644 --- a/src/gateway/events/authenticate.rs +++ b/src/gateway/events/authenticate.rs @@ -57,9 +57,7 @@ pub async fn run(client: &Client, payload: ClientPayload) { } for user in &users { - if user.relationship == Some(RelationshipStatus::Friend) { - subscriptions.push(user.id); - } + subscriptions.push(user.id); } for server in &servers { diff --git a/src/gateway/upgrade.rs b/src/gateway/upgrade.rs index a2811b1..332e2bd 100644 --- a/src/gateway/upgrade.rs +++ b/src/gateway/upgrade.rs @@ -143,6 +143,12 @@ async fn handle(ws: WebSocket) { ); } } + Payload::UserUpdate(u) => { + // Newly friend, blocked, request + if u.id != target_id && u.id != user.id { + client.subscriptions.subscribe(u.id.to_string()).await.ok(); + } + } _ => {} } From e97858f584c5c392c068b15b7fb415a2c4344ff3 Mon Sep 17 00:00:00 2001 From: Abdulrahman Salah Date: Sun, 10 Jul 2022 14:06:33 +0000 Subject: [PATCH 13/13] feat: implement Error for JsonRejection --- src/extractors/validate.rs | 12 +++--------- src/utils/error.rs | 7 +++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/extractors/validate.rs b/src/extractors/validate.rs index 78e4d6a..ac90fc4 100644 --- a/src/extractors/validate.rs +++ b/src/extractors/validate.rs @@ -21,16 +21,10 @@ where type Rejection = Error; async fn from_request(req: &mut RequestParts) -> Result { - let data = Json::from_request(req).await; + let data: Json = Json::from_request(req).await?; - if let Ok(data) = data { - let data: Json = data; + data.validate().map_err(|_| Error::InvalidBody)?; - data.validate().map_err(|_| Error::InvalidBody)?; - - Ok(Self(data.0)) - } else { - Err(Error::InvalidBody) - } + Ok(Self(data.0)) } } diff --git a/src/utils/error.rs b/src/utils/error.rs index 6828312..91aa0be 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -1,5 +1,6 @@ use crate::middlewares::ratelimit::RateLimitInfo; use axum::{ + extract::rejection::JsonRejection, http::StatusCode, response::{IntoResponse, Json, Response}, }; @@ -73,6 +74,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: JsonRejection) -> Self { + Self::InvalidBody + } +} + impl IntoResponse for Error { fn into_response(self) -> Response { let status = match self {