diff --git a/.woodpecker/build-and-publish.yml b/.woodpecker/build-and-publish.yml index 7f7096b..66f377d 100644 --- a/.woodpecker/build-and-publish.yml +++ b/.woodpecker/build-and-publish.yml @@ -5,6 +5,7 @@ steps: - cargo build --release when: - event: push + - event: pull_request - name: build-arm64 image: rust:1.88-bookworm @@ -19,6 +20,7 @@ steps: PKG_CONFIG_PATH: /usr/aarch64-linux-gnu/lib/pkgconfig when: - event: push + - event: pull_request - name: container-build-and-publish image: docker diff --git a/migrations/2025-07-22-195121_add_ban/down.sql b/migrations/2025-07-21-014019_message_is_edited/down.sql similarity index 51% rename from migrations/2025-07-22-195121_add_ban/down.sql rename to migrations/2025-07-21-014019_message_is_edited/down.sql index 62fe554..1a636b9 100644 --- a/migrations/2025-07-22-195121_add_ban/down.sql +++ b/migrations/2025-07-21-014019_message_is_edited/down.sql @@ -1,2 +1,2 @@ -- This file should undo anything in `up.sql` -DROP TABLE guild_bans; +ALTER TABLE messages DROP COLUMN is_edited; diff --git a/migrations/2025-07-21-014019_message_is_edited/up.sql b/migrations/2025-07-21-014019_message_is_edited/up.sql new file mode 100644 index 0000000..e600a55 --- /dev/null +++ b/migrations/2025-07-21-014019_message_is_edited/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE messages ADD COLUMN is_edited BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/migrations/2025-07-22-195121_add_ban/up.sql b/migrations/2025-07-22-195121_add_ban/up.sql deleted file mode 100644 index a590142..0000000 --- a/migrations/2025-07-22-195121_add_ban/up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Your SQL goes here -CREATE TABLE guild_bans ( - guild_uuid uuid NOT NULL REFERENCES guilds(uuid) ON DELETE CASCADE, - user_uuid uuid NOT NULL REFERENCES users(uuid), - reason VARCHAR(200) DEFAULT NULL, - banned_since TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (user_uuid, guild_uuid) -); diff --git a/migrations/2025-07-31-133510_roles_uuid_index/down.sql b/migrations/2025-07-31-133510_roles_uuid_index/down.sql deleted file mode 100644 index efe3f3f..0000000 --- a/migrations/2025-07-31-133510_roles_uuid_index/down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- This file should undo anything in `up.sql` -DROP INDEX roles_guuid_uuid; -ALTER TABLE roles DROP CONSTRAINT roles_pkey; -CREATE UNIQUE INDEX roles_pkey ON roles (uuid, guild_uuid); -ALTER TABLE roles ADD PRIMARY KEY USING INDEX roles_pkey; diff --git a/migrations/2025-07-31-133510_roles_uuid_index/up.sql b/migrations/2025-07-31-133510_roles_uuid_index/up.sql deleted file mode 100644 index 792e7fd..0000000 --- a/migrations/2025-07-31-133510_roles_uuid_index/up.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Your SQL goes here -ALTER TABLE roles DROP CONSTRAINT roles_pkey; -CREATE UNIQUE INDEX roles_pkey ON roles (uuid); -ALTER TABLE roles ADD PRIMARY KEY USING INDEX roles_pkey; -CREATE UNIQUE INDEX roles_guuid_uuid ON roles (uuid, guild_uuid); \ No newline at end of file diff --git a/migrations/2025-08-04-180235_add_status_to_user/down.sql b/migrations/2025-08-04-180235_add_status_to_user/down.sql deleted file mode 100644 index 163f7f1..0000000 --- a/migrations/2025-08-04-180235_add_status_to_user/down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE users DROP COLUMN online_status; diff --git a/migrations/2025-08-04-180235_add_status_to_user/up.sql b/migrations/2025-08-04-180235_add_status_to_user/up.sql deleted file mode 100644 index ac16d77..0000000 --- a/migrations/2025-08-04-180235_add_status_to_user/up.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Your SQL goes here -ALTER TABLE users ADD COLUMN online_status INT2 NOT NULL DEFAULT 0; diff --git a/src/api/v1/auth/register.rs b/src/api/v1/auth/register.rs index 545e5aa..807fab8 100644 --- a/src/api/v1/auth/register.rs +++ b/src/api/v1/auth/register.rs @@ -159,7 +159,7 @@ pub async fn post( .await?; if let Some(initial_guild) = app_state.config.instance.initial_guild { - Member::new(&mut conn, &app_state.cache_pool, uuid, initial_guild).await?; + Member::new(&app_state, uuid, initial_guild).await?; } let mut response = ( diff --git a/src/api/v1/auth/reset_password.rs b/src/api/v1/auth/reset_password.rs index 35c4b41..bac465c 100644 --- a/src/api/v1/auth/reset_password.rs +++ b/src/api/v1/auth/reset_password.rs @@ -38,17 +38,11 @@ pub async fn get( State(app_state): State>, query: Query, ) -> Result { - let mut conn = app_state.pool.get().await?; - - if let Ok(password_reset_token) = PasswordResetToken::get_with_identifier( - &mut conn, - &app_state.cache_pool, - query.identifier.clone(), - ) - .await + if let Ok(password_reset_token) = + PasswordResetToken::get_with_identifier(&app_state, query.identifier.clone()).await { if Utc::now().signed_duration_since(password_reset_token.created_at) > Duration::hours(1) { - password_reset_token.delete(&app_state.cache_pool).await?; + password_reset_token.delete(&app_state).await?; } else { return Err(Error::TooManyRequests( "Please allow 1 hour before sending a new email".to_string(), @@ -56,7 +50,7 @@ pub async fn get( } } - PasswordResetToken::new(&mut conn, &app_state, query.identifier.clone()).await?; + PasswordResetToken::new(&app_state, query.identifier.clone()).await?; Ok(StatusCode::OK) } @@ -93,14 +87,10 @@ pub async fn post( reset_password: Json, ) -> Result { let password_reset_token = - PasswordResetToken::get(&app_state.cache_pool, reset_password.token.clone()).await?; + PasswordResetToken::get(&app_state, reset_password.token.clone()).await?; password_reset_token - .set_password( - &mut app_state.pool.get().await?, - &app_state, - reset_password.password.clone(), - ) + .set_password(&app_state, reset_password.password.clone()) .await?; Ok(StatusCode::OK) diff --git a/src/api/v1/auth/verify_email.rs b/src/api/v1/auth/verify_email.rs index 1cb8aef..0801768 100644 --- a/src/api/v1/auth/verify_email.rs +++ b/src/api/v1/auth/verify_email.rs @@ -55,7 +55,7 @@ pub async fn get( return Ok(StatusCode::NO_CONTENT); } - let email_token = EmailToken::get(&app_state.cache_pool, me.uuid).await?; + let email_token = EmailToken::get(&app_state, me.uuid).await?; if query.token != email_token.token { return Ok(StatusCode::UNAUTHORIZED); @@ -63,7 +63,7 @@ pub async fn get( me.verify_email(&mut conn).await?; - email_token.delete(&app_state.cache_pool).await?; + email_token.delete(&app_state).await?; Ok(StatusCode::OK) } @@ -91,9 +91,9 @@ pub async fn post( return Ok(StatusCode::NO_CONTENT); } - if let Ok(email_token) = EmailToken::get(&app_state.cache_pool, me.uuid).await { + if let Ok(email_token) = EmailToken::get(&app_state, me.uuid).await { if Utc::now().signed_duration_since(email_token.created_at) > Duration::hours(1) { - email_token.delete(&app_state.cache_pool).await?; + email_token.delete(&app_state).await?; } else { return Err(Error::TooManyRequests( "Please allow 1 hour before sending a new email".to_string(), diff --git a/src/api/v1/channels/uuid/messages.rs b/src/api/v1/channels/uuid/messages.rs index 1f9010d..b8f0ad6 100644 --- a/src/api/v1/channels/uuid/messages.rs +++ b/src/api/v1/channels/uuid/messages.rs @@ -60,21 +60,14 @@ pub async fn get( Query(message_request): Query, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let channel = Channel::fetch_one(&app_state, channel_uuid).await?; - let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?; - - Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid).await?; let messages = channel - .fetch_messages( - &mut conn, - &app_state.cache_pool, - message_request.amount, - message_request.offset, - ) + .fetch_messages(&app_state, message_request.amount, message_request.offset) .await?; Ok((StatusCode::OK, Json(messages))) diff --git a/src/api/v1/channels/uuid/mod.rs b/src/api/v1/channels/uuid/mod.rs index f5566b3..373742e 100644 --- a/src/api/v1/channels/uuid/mod.rs +++ b/src/api/v1/channels/uuid/mod.rs @@ -27,13 +27,11 @@ pub async fn get( Path(channel_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let channel = Channel::fetch_one(&app_state, channel_uuid).await?; - let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?; - - Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid).await?; Ok((StatusCode::OK, Json(channel))) } @@ -43,19 +41,19 @@ pub async fn delete( Path(channel_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let channel = Channel::fetch_one(&app_state, channel_uuid).await?; - let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?; - - let member = Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + let member = + Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid) + .await?; member - .check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageChannel) + .check_permission(&app_state, Permissions::ManageChannel) .await?; - channel.delete(&mut conn, &app_state.cache_pool).await?; + channel.delete(&app_state).await?; Ok(StatusCode::OK) } @@ -104,37 +102,31 @@ pub async fn patch( Extension(CurrentUser(uuid)): Extension>, Json(new_info): Json, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut channel = Channel::fetch_one(&app_state, channel_uuid).await?; - let mut channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?; - - let member = Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + let member = + Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid) + .await?; member - .check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageChannel) + .check_permission(&app_state, Permissions::ManageChannel) .await?; if let Some(new_name) = &new_info.name { - channel - .set_name(&mut conn, &app_state.cache_pool, new_name.to_string()) - .await?; + channel.set_name(&app_state, new_name.to_string()).await?; } if let Some(new_description) = &new_info.description { channel - .set_description( - &mut conn, - &app_state.cache_pool, - new_description.to_string(), - ) + .set_description(&app_state, new_description.to_string()) .await?; } if let Some(new_is_above) = &new_info.is_above { channel - .set_description(&mut conn, &app_state.cache_pool, new_is_above.to_string()) + .set_description(&app_state, new_is_above.to_string()) .await?; } diff --git a/src/api/v1/channels/uuid/socket.rs b/src/api/v1/channels/uuid/socket.rs index ac04301..dd020e3 100644 --- a/src/api/v1/channels/uuid/socket.rs +++ b/src/api/v1/channels/uuid/socket.rs @@ -71,9 +71,9 @@ pub async fn ws( // Authorize client using auth header let uuid = check_access_token(auth_header, &mut conn).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + global_checks(&app_state, uuid).await?; - let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?; + let channel = Channel::fetch_one(&app_state, channel_uuid).await?; Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; @@ -103,8 +103,7 @@ pub async fn ws( let message = channel .new_message( - &mut conn, - &app_state.cache_pool, + &app_state, uuid, message_body.message, message_body.reply_to, diff --git a/src/api/v1/guilds/mod.rs b/src/api/v1/guilds/mod.rs index 5b9f089..8118522 100644 --- a/src/api/v1/guilds/mod.rs +++ b/src/api/v1/guilds/mod.rs @@ -128,11 +128,9 @@ pub async fn get_guilds( let start = request_query.start.unwrap_or(0); let amount = request_query.amount.unwrap_or(10); - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; - - let guilds = Guild::fetch_amount(&mut conn, start, amount).await?; + let guilds = Guild::fetch_amount(&app_state.pool, start, amount).await?; Ok((StatusCode::OK, Json(guilds))) } diff --git a/src/api/v1/guilds/uuid/bans.rs b/src/api/v1/guilds/uuid/bans.rs deleted file mode 100644 index 2e31a59..0000000 --- a/src/api/v1/guilds/uuid/bans.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::sync::Arc; - -use axum::{ - Extension, Json, - extract::{Path, State}, - http::StatusCode, - response::IntoResponse, -}; -use uuid::Uuid; - -use crate::{ - AppState, - api::v1::auth::CurrentUser, - error::Error, - objects::{GuildBan, Member, Permissions}, - utils::global_checks, -}; - -pub async fn get( - State(app_state): State>, - Path(guild_uuid): Path, - Extension(CurrentUser(uuid)): Extension>, -) -> Result { - let mut conn = app_state.pool.get().await?; - - global_checks(&mut conn, &app_state.config, uuid).await?; - - let caller = Member::check_membership(&mut conn, uuid, guild_uuid).await?; - caller - .check_permission(&mut conn, &app_state.cache_pool, Permissions::BanMember) - .await?; - - let all_guild_bans = GuildBan::fetch_all(&mut conn, guild_uuid).await?; - - Ok((StatusCode::OK, Json(all_guild_bans))) -} - -pub async fn unban( - State(app_state): State>, - Path((guild_uuid, user_uuid)): Path<(Uuid, Uuid)>, - Extension(CurrentUser(uuid)): Extension>, -) -> Result { - let mut conn = app_state.pool.get().await?; - - global_checks(&mut conn, &app_state.config, uuid).await?; - - let caller = Member::check_membership(&mut conn, uuid, guild_uuid).await?; - caller - .check_permission(&mut conn, &app_state.cache_pool, Permissions::BanMember) - .await?; - - let ban = GuildBan::fetch_one(&mut conn, guild_uuid, user_uuid).await?; - - ban.unban(&mut conn).await?; - - Ok(StatusCode::OK) -} diff --git a/src/api/v1/guilds/uuid/channels.rs b/src/api/v1/guilds/uuid/channels.rs index 1cd7f78..82368b9 100644 --- a/src/api/v1/guilds/uuid/channels.rs +++ b/src/api/v1/guilds/uuid/channels.rs @@ -14,7 +14,7 @@ use crate::{ api::v1::auth::CurrentUser, error::Error, objects::{Channel, Member, Permissions}, - utils::{CacheFns, global_checks, order_by_is_above}, + utils::{global_checks, order_by_is_above}, }; #[derive(Deserialize)] @@ -28,26 +28,23 @@ pub async fn get( Path(guild_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; - - Member::check_membership(&mut conn, uuid, guild_uuid).await?; + Member::check_membership(&mut app_state.pool.get().await?, uuid, guild_uuid).await?; if let Ok(cache_hit) = app_state - .cache_pool - .get_cache_key::>(format!("{guild_uuid}_channels")) + .get_cache_key(format!("{guild_uuid}_channels")) .await + && let Ok(channels) = serde_json::from_str::>(&cache_hit) { - return Ok((StatusCode::OK, Json(cache_hit)).into_response()); + return Ok((StatusCode::OK, Json(channels)).into_response()); } - let channels = Channel::fetch_all(&mut conn, guild_uuid).await?; + let channels = Channel::fetch_all(&app_state.pool, guild_uuid).await?; let channels_ordered = order_by_is_above(channels).await?; app_state - .cache_pool .set_cache_key( format!("{guild_uuid}_channels"), channels_ordered.clone(), @@ -64,19 +61,17 @@ pub async fn create( Extension(CurrentUser(uuid)): Extension>, Json(channel_info): Json, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; - - let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; + let member = + Member::check_membership(&mut app_state.pool.get().await?, uuid, guild_uuid).await?; member - .check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageChannel) + .check_permission(&app_state, Permissions::ManageChannel) .await?; let channel = Channel::new( - &mut conn, - &app_state.cache_pool, + &app_state, guild_uuid, channel_info.name.clone(), channel_info.description.clone(), diff --git a/src/api/v1/guilds/uuid/invites/mod.rs b/src/api/v1/guilds/uuid/invites/mod.rs index fa06f44..649fc16 100644 --- a/src/api/v1/guilds/uuid/invites/mod.rs +++ b/src/api/v1/guilds/uuid/invites/mod.rs @@ -27,9 +27,9 @@ pub async fn get( Path(guild_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; Member::check_membership(&mut conn, uuid, guild_uuid).await?; @@ -46,14 +46,14 @@ pub async fn create( Extension(CurrentUser(uuid)): Extension>, Json(invite_request): Json, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; member - .check_permission(&mut conn, &app_state.cache_pool, Permissions::CreateInvite) + .check_permission(&app_state, Permissions::CreateInvite) .await?; let guild = Guild::fetch_one(&mut conn, guild_uuid).await?; diff --git a/src/api/v1/guilds/uuid/members.rs b/src/api/v1/guilds/uuid/members.rs index 0e1d2bc..3ae10f7 100644 --- a/src/api/v1/guilds/uuid/members.rs +++ b/src/api/v1/guilds/uuid/members.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use ::uuid::Uuid; use axum::{ Extension, Json, - extract::{Path, Query, State}, + extract::{Path, State}, http::StatusCode, response::IntoResponse, }; @@ -12,32 +12,24 @@ use crate::{ AppState, api::v1::auth::CurrentUser, error::Error, - objects::{Me, Member, PaginationRequest}, + objects::{Me, Member}, utils::global_checks, }; pub async fn get( State(app_state): State>, Path(guild_uuid): Path, - Query(pagination): Query, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; Member::check_membership(&mut conn, uuid, guild_uuid).await?; let me = Me::get(&mut conn, uuid).await?; - let members = Member::fetch_page( - &mut conn, - &app_state.cache_pool, - &me, - guild_uuid, - pagination, - ) - .await?; + let members = Member::fetch_all(&app_state, &me, guild_uuid).await?; Ok((StatusCode::OK, Json(members))) } diff --git a/src/api/v1/guilds/uuid/mod.rs b/src/api/v1/guilds/uuid/mod.rs index 53f469b..52f0b64 100644 --- a/src/api/v1/guilds/uuid/mod.rs +++ b/src/api/v1/guilds/uuid/mod.rs @@ -7,12 +7,11 @@ use axum::{ extract::{Multipart, Path, State}, http::StatusCode, response::IntoResponse, - routing::{delete, get, patch, post}, + routing::{get, patch, post}, }; use bytes::Bytes; use uuid::Uuid; -mod bans; mod channels; mod invites; mod members; @@ -43,9 +42,6 @@ pub fn router() -> Router> { .route("/invites", post(invites::create)) // Members .route("/members", get(members::get)) - // Bans - .route("/bans", get(bans::get)) - .route("/bans/{uuid}", delete(bans::unban)) } /// `GET /api/v1/guilds/{uuid}` DESCRIPTION @@ -86,9 +82,9 @@ pub async fn get_guild( Path(guild_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; Member::check_membership(&mut conn, uuid, guild_uuid).await?; @@ -106,14 +102,14 @@ pub async fn edit( Extension(CurrentUser(uuid)): Extension>, mut multipart: Multipart, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; member - .check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageGuild) + .check_permission(&app_state, Permissions::ManageGuild) .await?; let mut guild = Guild::fetch_one(&mut conn, guild_uuid).await?; @@ -131,7 +127,14 @@ pub async fn edit( } if let Some(icon) = icon { - guild.set_icon(&mut conn, &app_state, icon).await?; + guild + .set_icon( + &app_state.bunny_storage, + &mut conn, + app_state.config.bunny.cdn_url.clone(), + icon, + ) + .await?; } Ok(StatusCode::OK) diff --git a/src/api/v1/guilds/uuid/roles/mod.rs b/src/api/v1/guilds/uuid/roles/mod.rs index d3660ce..0e496a0 100644 --- a/src/api/v1/guilds/uuid/roles/mod.rs +++ b/src/api/v1/guilds/uuid/roles/mod.rs @@ -14,7 +14,7 @@ use crate::{ api::v1::auth::CurrentUser, error::Error, objects::{Member, Permissions, Role}, - utils::{CacheFns, global_checks, order_by_is_above}, + utils::{global_checks, order_by_is_above}, }; pub mod uuid; @@ -29,18 +29,16 @@ pub async fn get( Path(guild_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; Member::check_membership(&mut conn, uuid, guild_uuid).await?; - if let Ok(cache_hit) = app_state - .cache_pool - .get_cache_key::>(format!("{guild_uuid}_roles")) - .await + if let Ok(cache_hit) = app_state.get_cache_key(format!("{guild_uuid}_roles")).await + && let Ok(roles) = serde_json::from_str::>(&cache_hit) { - return Ok((StatusCode::OK, Json(cache_hit)).into_response()); + return Ok((StatusCode::OK, Json(roles)).into_response()); } let roles = Role::fetch_all(&mut conn, guild_uuid).await?; @@ -48,7 +46,6 @@ pub async fn get( let roles_ordered = order_by_is_above(roles).await?; app_state - .cache_pool .set_cache_key(format!("{guild_uuid}_roles"), roles_ordered.clone(), 1800) .await?; @@ -61,14 +58,14 @@ pub async fn create( Extension(CurrentUser(uuid)): Extension>, Json(role_info): Json, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; member - .check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageRole) + .check_permission(&app_state, Permissions::ManageRole) .await?; let role = Role::new(&mut conn, guild_uuid, role_info.name.clone()).await?; diff --git a/src/api/v1/guilds/uuid/roles/uuid.rs b/src/api/v1/guilds/uuid/roles/uuid.rs index e7890d0..732d553 100644 --- a/src/api/v1/guilds/uuid/roles/uuid.rs +++ b/src/api/v1/guilds/uuid/roles/uuid.rs @@ -13,7 +13,7 @@ use crate::{ api::v1::auth::CurrentUser, error::Error, objects::{Member, Role}, - utils::{CacheFns, global_checks}, + utils::global_checks, }; pub async fn get( @@ -21,24 +21,21 @@ pub async fn get( Path((guild_uuid, role_uuid)): Path<(Uuid, Uuid)>, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; Member::check_membership(&mut conn, uuid, guild_uuid).await?; - if let Ok(cache_hit) = app_state - .cache_pool - .get_cache_key::(format!("{role_uuid}")) - .await + if let Ok(cache_hit) = app_state.get_cache_key(format!("{role_uuid}")).await + && let Ok(role) = serde_json::from_str::(&cache_hit) { - return Ok((StatusCode::OK, Json(cache_hit)).into_response()); + return Ok((StatusCode::OK, Json(role)).into_response()); } let role = Role::fetch_one(&mut conn, role_uuid).await?; app_state - .cache_pool .set_cache_key(format!("{role_uuid}"), role.clone(), 60) .await?; diff --git a/src/api/v1/invites/id.rs b/src/api/v1/invites/id.rs index 99f177f..72ceea4 100644 --- a/src/api/v1/invites/id.rs +++ b/src/api/v1/invites/id.rs @@ -34,15 +34,15 @@ pub async fn join( Path(invite_id): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; let invite = Invite::fetch_one(&mut conn, invite_id).await?; let guild = Guild::fetch_one(&mut conn, invite.guild_uuid).await?; - Member::new(&mut conn, &app_state.cache_pool, uuid, guild.uuid).await?; + Member::new(&app_state, uuid, guild.uuid).await?; Ok((StatusCode::OK, Json(guild))) } diff --git a/src/api/v1/me/friends/mod.rs b/src/api/v1/me/friends/mod.rs index 904a1f5..a56f8d4 100644 --- a/src/api/v1/me/friends/mod.rs +++ b/src/api/v1/me/friends/mod.rs @@ -19,13 +19,11 @@ pub async fn get( State(app_state): State>, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let me = Me::get(&mut app_state.pool.get().await?, uuid).await?; - let me = Me::get(&mut conn, uuid).await?; - - let friends = me.get_friends(&mut conn, &app_state.cache_pool).await?; + let friends = me.get_friends(&app_state).await?; Ok((StatusCode::OK, Json(friends))) } @@ -59,9 +57,9 @@ pub async fn post( Extension(CurrentUser(uuid)): Extension>, Json(user_request): Json, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; let me = Me::get(&mut conn, uuid).await?; diff --git a/src/api/v1/me/friends/uuid.rs b/src/api/v1/me/friends/uuid.rs index 35f0742..5367435 100644 --- a/src/api/v1/me/friends/uuid.rs +++ b/src/api/v1/me/friends/uuid.rs @@ -17,9 +17,9 @@ pub async fn delete( Path(friend_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; let me = Me::get(&mut conn, uuid).await?; diff --git a/src/api/v1/me/guilds.rs b/src/api/v1/me/guilds.rs index 42d5c21..88dfad9 100644 --- a/src/api/v1/me/guilds.rs +++ b/src/api/v1/me/guilds.rs @@ -58,9 +58,9 @@ pub async fn get( State(app_state): State>, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let mut conn = app_state.pool.get().await?; let me = Me::get(&mut conn, uuid).await?; diff --git a/src/api/v1/me/mod.rs b/src/api/v1/me/mod.rs index 9d75d26..e167d14 100644 --- a/src/api/v1/me/mod.rs +++ b/src/api/v1/me/mod.rs @@ -49,7 +49,6 @@ struct NewInfo { email: Option, pronouns: Option, about: Option, - online_status: Option, } pub async fn update( @@ -74,46 +73,36 @@ pub async fn update( let json = json_raw.unwrap_or_default(); - let mut conn = app_state.pool.get().await?; - if avatar.is_some() || json.username.is_some() || json.display_name.is_some() { - global_checks(&mut conn, &app_state.config, uuid).await?; + global_checks(&app_state, uuid).await?; } - let mut me = Me::get(&mut conn, uuid).await?; + let mut me = Me::get(&mut app_state.pool.get().await?, uuid).await?; if let Some(avatar) = avatar { - me.set_avatar(&mut conn, &app_state, avatar).await?; - } - - if let Some(username) = &json.username { - me.set_username(&mut conn, &app_state.cache_pool, username.clone()) + me.set_avatar(&app_state, app_state.config.bunny.cdn_url.clone(), avatar) .await?; } + if let Some(username) = &json.username { + me.set_username(&app_state, username.clone()).await?; + } + if let Some(display_name) = &json.display_name { - me.set_display_name(&mut conn, &app_state.cache_pool, display_name.clone()) + me.set_display_name(&app_state, display_name.clone()) .await?; } if let Some(email) = &json.email { - me.set_email(&mut conn, &app_state.cache_pool, email.clone()) - .await?; + me.set_email(&app_state, email.clone()).await?; } if let Some(pronouns) = &json.pronouns { - me.set_pronouns(&mut conn, &app_state.cache_pool, pronouns.clone()) - .await?; + me.set_pronouns(&app_state, pronouns.clone()).await?; } if let Some(about) = &json.about { - me.set_about(&mut conn, &app_state.cache_pool, about.clone()) - .await?; - } - - if let Some(online_status) = &json.online_status { - me.set_online_status(&mut conn, &app_state.cache_pool, *online_status) - .await?; + me.set_about(&app_state, about.clone()).await?; } Ok(StatusCode::OK) diff --git a/src/api/v1/members/mod.rs b/src/api/v1/members/mod.rs deleted file mode 100644 index 59ceac2..0000000 --- a/src/api/v1/members/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::sync::Arc; - -use axum::{ - Router, - routing::{delete, get, post}, -}; - -use crate::AppState; - -mod uuid; - -pub fn router() -> Router> { - Router::new() - .route("/{uuid}", get(uuid::get)) - .route("/{uuid}", delete(uuid::delete)) - .route("/{uuid}/ban", post(uuid::ban::post)) -} diff --git a/src/api/v1/members/uuid/ban.rs b/src/api/v1/members/uuid/ban.rs deleted file mode 100644 index e828e69..0000000 --- a/src/api/v1/members/uuid/ban.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::sync::Arc; - -use axum::{ - Extension, - extract::{Json, Path, State}, - http::StatusCode, - response::IntoResponse, -}; -use serde::Deserialize; - -use crate::{ - AppState, - api::v1::auth::CurrentUser, - error::Error, - objects::{Member, Permissions}, - utils::global_checks, -}; - -use uuid::Uuid; - -#[derive(Deserialize)] -pub struct RequstBody { - reason: String, -} - -pub async fn post( - State(app_state): State>, - Path(member_uuid): Path, - Extension(CurrentUser(uuid)): Extension>, - Json(payload): Json, -) -> Result { - let mut conn = app_state.pool.get().await?; - - global_checks(&mut conn, &app_state.config, uuid).await?; - - let member = - Member::fetch_one_with_uuid(&mut conn, &app_state.cache_pool, None, member_uuid).await?; - - let caller = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?; - - caller - .check_permission(&mut conn, &app_state.cache_pool, Permissions::BanMember) - .await?; - - member.ban(&mut conn, &payload.reason).await?; - - Ok(StatusCode::OK) -} diff --git a/src/api/v1/members/uuid/mod.rs b/src/api/v1/members/uuid/mod.rs deleted file mode 100644 index 5bfd129..0000000 --- a/src/api/v1/members/uuid/mod.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! `/api/v1/members/{uuid}` Member specific endpoints - -pub mod ban; - -use std::sync::Arc; - -use crate::{ - AppState, - api::v1::auth::CurrentUser, - error::Error, - objects::{Me, Member, Permissions}, - utils::global_checks, -}; -use axum::{ - Extension, Json, - extract::{Path, State}, - http::StatusCode, - response::IntoResponse, -}; - -use uuid::Uuid; - -pub async fn get( - State(app_state): State>, - Path(member_uuid): Path, - Extension(CurrentUser(uuid)): Extension>, -) -> Result { - let mut conn = app_state.pool.get().await?; - - global_checks(&mut conn, &app_state.config, uuid).await?; - - let me = Me::get(&mut conn, uuid).await?; - - let member = - Member::fetch_one_with_uuid(&mut conn, &app_state.cache_pool, Some(&me), member_uuid) - .await?; - Member::check_membership(&mut conn, uuid, member.guild_uuid).await?; - - Ok((StatusCode::OK, Json(member))) -} - -pub async fn delete( - State(app_state): State>, - Path(member_uuid): Path, - Extension(CurrentUser(uuid)): Extension>, -) -> Result { - let mut conn = app_state.pool.get().await?; - - global_checks(&mut conn, &app_state.config, uuid).await?; - - let me = Me::get(&mut conn, uuid).await?; - - let member = - Member::fetch_one_with_uuid(&mut conn, &app_state.cache_pool, Some(&me), member_uuid) - .await?; - - let deleter = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?; - - deleter - .check_permission(&mut conn, &app_state.cache_pool, Permissions::KickMember) - .await?; - - member.delete(&mut conn).await?; - - Ok(StatusCode::OK) -} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index 70271ef..860944c 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -11,7 +11,6 @@ mod channels; mod guilds; mod invites; mod me; -mod members; mod stats; mod users; @@ -20,7 +19,6 @@ pub fn router(app_state: Arc) -> Router> { .nest("/users", users::router()) .nest("/guilds", guilds::router()) .nest("/invites", invites::router()) - .nest("/members", members::router()) .nest("/me", me::router()) .layer(from_fn_with_state( app_state.clone(), diff --git a/src/api/v1/users/mod.rs b/src/api/v1/users/mod.rs index 999e13f..a4b93ce 100644 --- a/src/api/v1/users/mod.rs +++ b/src/api/v1/users/mod.rs @@ -70,11 +70,9 @@ pub async fn users( return Ok(StatusCode::BAD_REQUEST.into_response()); } - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; - - let users = User::fetch_amount(&mut conn, start, amount).await?; + let users = User::fetch_amount(&mut app_state.pool.get().await?, start, amount).await?; Ok((StatusCode::OK, Json(users)).into_response()) } diff --git a/src/api/v1/users/uuid.rs b/src/api/v1/users/uuid.rs index e015c3c..cee6df0 100644 --- a/src/api/v1/users/uuid.rs +++ b/src/api/v1/users/uuid.rs @@ -39,14 +39,11 @@ pub async fn get( Path(user_uuid): Path, Extension(CurrentUser(uuid)): Extension>, ) -> Result { - let mut conn = app_state.pool.get().await?; + global_checks(&app_state, uuid).await?; - global_checks(&mut conn, &app_state.config, uuid).await?; + let me = Me::get(&mut app_state.pool.get().await?, uuid).await?; - let me = Me::get(&mut conn, uuid).await?; - - let user = - User::fetch_one_with_friendship(&mut conn, &app_state.cache_pool, &me, user_uuid).await?; + let user = User::fetch_one_with_friendship(&app_state, &me, user_uuid).await?; Ok((StatusCode::OK, Json(user))) } diff --git a/src/objects/bans.rs b/src/objects/bans.rs deleted file mode 100644 index 602afa6..0000000 --- a/src/objects/bans.rs +++ /dev/null @@ -1,57 +0,0 @@ -use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use diesel_async::RunQueryDsl; - -use crate::{Conn, error::Error, objects::load_or_empty, schema::guild_bans}; - -#[derive(Selectable, Queryable, Serialize, Deserialize)] -#[diesel(table_name = guild_bans)] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct GuildBan { - pub guild_uuid: Uuid, - pub user_uuid: Uuid, - pub reason: Option, - pub banned_since: chrono::DateTime, -} - -impl GuildBan { - pub async fn fetch_one( - conn: &mut Conn, - guild_uuid: Uuid, - user_uuid: Uuid, - ) -> Result { - use guild_bans::dsl; - let guild_ban = dsl::guild_bans - .filter(dsl::guild_uuid.eq(guild_uuid)) - .filter(dsl::user_uuid.eq(user_uuid)) - .select(GuildBan::as_select()) - .get_result(conn) - .await?; - - Ok(guild_ban) - } - - pub async fn fetch_all(conn: &mut Conn, guild_uuid: Uuid) -> Result, Error> { - use guild_bans::dsl; - let all_guild_bans = load_or_empty( - dsl::guild_bans - .filter(dsl::guild_uuid.eq(guild_uuid)) - .load(conn) - .await, - )?; - - Ok(all_guild_bans) - } - - pub async fn unban(self, conn: &mut Conn) -> Result<(), Error> { - use guild_bans::dsl; - diesel::delete(guild_bans::table) - .filter(dsl::guild_uuid.eq(self.guild_uuid)) - .filter(dsl::user_uuid.eq(self.user_uuid)) - .execute(conn) - .await?; - Ok(()) - } -} diff --git a/src/objects/channel.rs b/src/objects/channel.rs index 03a2cf6..ad16379 100644 --- a/src/objects/channel.rs +++ b/src/objects/channel.rs @@ -2,15 +2,15 @@ use diesel::{ ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, delete, insert_into, update, }; -use diesel_async::RunQueryDsl; +use diesel_async::{RunQueryDsl, pooled_connection::AsyncDieselConnectionManager}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - Conn, + AppState, Conn, error::Error, schema::{channel_permissions, channels, messages}, - utils::{CHANNEL_REGEX, CacheFns, order_by_is_above}, + utils::{CHANNEL_REGEX, order_by_is_above}, }; use super::{HasIsAbove, HasUuid, Message, load_or_empty, message::MessageBuilder}; @@ -79,44 +79,49 @@ impl HasIsAbove for Channel { } impl Channel { - pub async fn fetch_all(conn: &mut Conn, guild_uuid: Uuid) -> Result, Error> { + pub async fn fetch_all( + pool: &deadpool::managed::Pool< + AsyncDieselConnectionManager, + Conn, + >, + guild_uuid: Uuid, + ) -> Result, Error> { + let mut conn = pool.get().await?; + use channels::dsl; let channel_builders: Vec = load_or_empty( dsl::channels .filter(dsl::guild_uuid.eq(guild_uuid)) .select(ChannelBuilder::as_select()) - .load(conn) + .load(&mut conn) .await, )?; - let mut channels = vec![]; + let channel_futures = channel_builders.iter().map(async move |c| { + let mut conn = pool.get().await?; + c.clone().build(&mut conn).await + }); - for builder in channel_builders { - channels.push(builder.build(conn).await?); - } - - Ok(channels) + futures_util::future::try_join_all(channel_futures).await } - pub async fn fetch_one( - conn: &mut Conn, - cache_pool: &redis::Client, - channel_uuid: Uuid, - ) -> Result { - if let Ok(cache_hit) = cache_pool.get_cache_key(channel_uuid.to_string()).await { - return Ok(cache_hit); + pub async fn fetch_one(app_state: &AppState, channel_uuid: Uuid) -> Result { + if let Ok(cache_hit) = app_state.get_cache_key(channel_uuid.to_string()).await { + return Ok(serde_json::from_str(&cache_hit)?); } + let mut conn = app_state.pool.get().await?; + use channels::dsl; let channel_builder: ChannelBuilder = dsl::channels .filter(dsl::uuid.eq(channel_uuid)) .select(ChannelBuilder::as_select()) - .get_result(conn) + .get_result(&mut conn) .await?; - let channel = channel_builder.build(conn).await?; + let channel = channel_builder.build(&mut conn).await?; - cache_pool + app_state .set_cache_key(channel_uuid.to_string(), channel.clone(), 60) .await?; @@ -124,8 +129,7 @@ impl Channel { } pub async fn new( - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, guild_uuid: Uuid, name: String, description: Option, @@ -134,9 +138,11 @@ impl Channel { return Err(Error::BadRequest("Channel name is invalid".to_string())); } + let mut conn = app_state.pool.get().await?; + let channel_uuid = Uuid::now_v7(); - let channels = Self::fetch_all(conn, guild_uuid).await?; + let channels = Self::fetch_all(&app_state.pool, guild_uuid).await?; let channels_ordered = order_by_is_above(channels).await?; @@ -152,7 +158,7 @@ impl Channel { insert_into(channels::table) .values(new_channel.clone()) - .execute(conn) + .execute(&mut conn) .await?; if let Some(old_last_channel) = last_channel { @@ -160,7 +166,7 @@ impl Channel { update(channels::table) .filter(dsl::uuid.eq(old_last_channel.uuid)) .set(dsl::is_above.eq(new_channel.uuid)) - .execute(conn) + .execute(&mut conn) .await?; } @@ -174,16 +180,16 @@ impl Channel { permissions: vec![], }; - cache_pool + app_state .set_cache_key(channel_uuid.to_string(), channel.clone(), 1800) .await?; - if cache_pool - .get_cache_key::>(format!("{guild_uuid}_channels")) + if app_state + .get_cache_key(format!("{guild_uuid}_channels")) .await .is_ok() { - cache_pool + app_state .del_cache_key(format!("{guild_uuid}_channels")) .await?; } @@ -191,12 +197,14 @@ impl Channel { Ok(channel) } - pub async fn delete(self, conn: &mut Conn, cache_pool: &redis::Client) -> Result<(), Error> { + pub async fn delete(self, app_state: &AppState) -> Result<(), Error> { + let mut conn = app_state.pool.get().await?; + use channels::dsl; match update(channels::table) .filter(dsl::is_above.eq(self.uuid)) .set(dsl::is_above.eq(None::)) - .execute(conn) + .execute(&mut conn) .await { Ok(r) => Ok(r), @@ -206,13 +214,13 @@ impl Channel { delete(channels::table) .filter(dsl::uuid.eq(self.uuid)) - .execute(conn) + .execute(&mut conn) .await?; match update(channels::table) .filter(dsl::is_above.eq(self.uuid)) .set(dsl::is_above.eq(self.is_above)) - .execute(conn) + .execute(&mut conn) .await { Ok(r) => Ok(r), @@ -220,20 +228,16 @@ impl Channel { Err(e) => Err(e), }?; - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await?; + if app_state.get_cache_key(self.uuid.to_string()).await.is_ok() { + app_state.del_cache_key(self.uuid.to_string()).await?; } - if cache_pool - .get_cache_key::>(format!("{}_channels", self.guild_uuid)) + if app_state + .get_cache_key(format!("{}_channels", self.guild_uuid)) .await .is_ok() { - cache_pool + app_state .del_cache_key(format!("{}_channels", self.guild_uuid)) .await?; } @@ -243,36 +247,32 @@ impl Channel { pub async fn fetch_messages( &self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, amount: i64, offset: i64, ) -> Result, Error> { + let mut conn = app_state.pool.get().await?; + use messages::dsl; - let message_builders: Vec = load_or_empty( + let messages: Vec = load_or_empty( dsl::messages .filter(dsl::channel_uuid.eq(self.uuid)) .select(MessageBuilder::as_select()) .order(dsl::uuid.desc()) .limit(amount) .offset(offset) - .load(conn) + .load(&mut conn) .await, )?; - let mut messages = vec![]; + let message_futures = messages.iter().map(async move |b| b.build(app_state).await); - for builder in message_builders { - messages.push(builder.build(conn, cache_pool).await?); - } - - Ok(messages) + futures_util::future::try_join_all(message_futures).await } pub async fn new_message( &self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, user_uuid: Uuid, message: String, reply_to: Option, @@ -285,103 +285,85 @@ impl Channel { user_uuid, message, reply_to, + is_edited: false, }; + let mut conn = app_state.pool.get().await?; + insert_into(messages::table) .values(message.clone()) - .execute(conn) + .execute(&mut conn) .await?; - message.build(conn, cache_pool).await + message.build(app_state).await } - pub async fn set_name( - &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, - new_name: String, - ) -> Result<(), Error> { + pub async fn edit_message(&self, app_state: &AppState, user_uuid: Uuid, message_uuid: Uuid, message: String) -> Result { + use messages::dsl; + + let mut conn = app_state.pool.get().await?; + + let message: MessageBuilder = update(messages::table) + .filter(dsl::user_uuid.eq(user_uuid)) + .filter(dsl::uuid.eq(message_uuid)) + .set((dsl::is_edited.eq(true), dsl::message.eq(message))) + .returning(MessageBuilder::as_select()) + .get_result(&mut conn) + .await?; + + message.build(app_state).await + } + + pub async fn set_name(&mut self, app_state: &AppState, new_name: String) -> Result<(), Error> { if !CHANNEL_REGEX.is_match(&new_name) { return Err(Error::BadRequest("Channel name is invalid".to_string())); } + let mut conn = app_state.pool.get().await?; + use channels::dsl; update(channels::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::name.eq(&new_name)) - .execute(conn) + .execute(&mut conn) .await?; self.name = new_name; - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await?; - } - - if cache_pool - .get_cache_key::>(format!("{}_channels", self.guild_uuid)) - .await - .is_ok() - { - cache_pool - .del_cache_key(format!("{}_channels", self.guild_uuid)) - .await?; - } - Ok(()) } pub async fn set_description( &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, new_description: String, ) -> Result<(), Error> { + let mut conn = app_state.pool.get().await?; + use channels::dsl; update(channels::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::description.eq(&new_description)) - .execute(conn) + .execute(&mut conn) .await?; self.description = Some(new_description); - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await?; - } - - if cache_pool - .get_cache_key::>(format!("{}_channels", self.guild_uuid)) - .await - .is_ok() - { - cache_pool - .del_cache_key(format!("{}_channels", self.guild_uuid)) - .await?; - } - Ok(()) } pub async fn move_channel( &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, new_is_above: Uuid, ) -> Result<(), Error> { + let mut conn = app_state.pool.get().await?; + use channels::dsl; let old_above_uuid: Option = match dsl::channels .filter(dsl::is_above.eq(self.uuid)) .select(dsl::uuid) - .get_result(conn) + .get_result(&mut conn) .await { Ok(r) => Ok(Some(r)), @@ -393,14 +375,14 @@ impl Channel { update(channels::table) .filter(dsl::uuid.eq(uuid)) .set(dsl::is_above.eq(None::)) - .execute(conn) + .execute(&mut conn) .await?; } match update(channels::table) .filter(dsl::is_above.eq(new_is_above)) .set(dsl::is_above.eq(self.uuid)) - .execute(conn) + .execute(&mut conn) .await { Ok(r) => Ok(r), @@ -411,37 +393,19 @@ impl Channel { update(channels::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::is_above.eq(new_is_above)) - .execute(conn) + .execute(&mut conn) .await?; if let Some(uuid) = old_above_uuid { update(channels::table) .filter(dsl::uuid.eq(uuid)) .set(dsl::is_above.eq(self.is_above)) - .execute(conn) + .execute(&mut conn) .await?; } self.is_above = Some(new_is_above); - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await?; - } - - if cache_pool - .get_cache_key::>(format!("{}_channels", self.guild_uuid)) - .await - .is_ok() - { - cache_pool - .del_cache_key(format!("{}_channels", self.guild_uuid)) - .await?; - } - Ok(()) } } diff --git a/src/objects/email_token.rs b/src/objects/email_token.rs index c826620..64d2fdb 100644 --- a/src/objects/email_token.rs +++ b/src/objects/email_token.rs @@ -3,11 +3,7 @@ use lettre::message::MultiPart; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - AppState, - error::Error, - utils::{CacheFns, generate_token}, -}; +use crate::{AppState, error::Error, utils::generate_token}; use super::Me; @@ -19,10 +15,12 @@ pub struct EmailToken { } impl EmailToken { - pub async fn get(cache_pool: &redis::Client, user_uuid: Uuid) -> Result { - let email_token = cache_pool - .get_cache_key(format!("{user_uuid}_email_verify")) - .await?; + pub async fn get(app_state: &AppState, user_uuid: Uuid) -> Result { + let email_token = serde_json::from_str( + &app_state + .get_cache_key(format!("{user_uuid}_email_verify")) + .await?, + )?; Ok(email_token) } @@ -39,7 +37,6 @@ impl EmailToken { }; app_state - .cache_pool .set_cache_key(format!("{}_email_verify", me.uuid), email_token, 86400) .await?; @@ -62,8 +59,8 @@ impl EmailToken { Ok(()) } - pub async fn delete(&self, cache_pool: &redis::Client) -> Result<(), Error> { - cache_pool + pub async fn delete(&self, app_state: &AppState) -> Result<(), Error> { + app_state .del_cache_key(format!("{}_email_verify", self.user_uuid)) .await?; diff --git a/src/objects/guild.rs b/src/objects/guild.rs index 9640e28..9514e49 100644 --- a/src/objects/guild.rs +++ b/src/objects/guild.rs @@ -3,14 +3,14 @@ use diesel::{ ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, insert_into, update, }; -use diesel_async::RunQueryDsl; +use diesel_async::{RunQueryDsl, pooled_connection::AsyncDieselConnectionManager}; use serde::Serialize; use tokio::task; use url::Url; use uuid::Uuid; use crate::{ - AppState, Conn, + Conn, error::Error, schema::{guild_members, guilds, invites}, utils::image_check, @@ -68,11 +68,16 @@ impl Guild { } pub async fn fetch_amount( - conn: &mut Conn, + pool: &deadpool::managed::Pool< + AsyncDieselConnectionManager, + Conn, + >, offset: i64, amount: i64, ) -> Result, Error> { // Fetch guild data from database + let mut conn = pool.get().await?; + use guilds::dsl; let guild_builders: Vec = load_or_empty( dsl::guilds @@ -80,17 +85,18 @@ impl Guild { .order_by(dsl::uuid) .offset(offset) .limit(amount) - .load(conn) + .load(&mut conn) .await, )?; - let mut guilds = vec![]; + // Process each guild concurrently + let guild_futures = guild_builders.iter().map(async move |g| { + let mut conn = pool.get().await?; + g.clone().build(&mut conn).await + }); - for builder in guild_builders { - guilds.push(builder.build(conn).await?); - } - - Ok(guilds) + // Execute all futures concurrently and collect results + futures_util::future::try_join_all(guild_futures).await } pub async fn new(conn: &mut Conn, name: String, owner_uuid: Uuid) -> Result { @@ -182,8 +188,9 @@ impl Guild { // FIXME: Horrible security pub async fn set_icon( &mut self, + bunny_storage: &bunny_api_tokio::EdgeStorageClient, conn: &mut Conn, - app_state: &AppState, + cdn_url: Url, icon: Bytes, ) -> Result<(), Error> { let icon_clone = icon.clone(); @@ -192,14 +199,14 @@ impl Guild { if let Some(icon) = &self.icon { let relative_url = icon.path().trim_start_matches('/'); - app_state.bunny_storage.delete(relative_url).await?; + bunny_storage.delete(relative_url).await?; } let path = format!("icons/{}/{}.{}", self.uuid, Uuid::now_v7(), image_type); - app_state.bunny_storage.upload(path.clone(), icon).await?; + bunny_storage.upload(path.clone(), icon).await?; - let icon_url = app_state.config.bunny.cdn_url.join(&path)?; + let icon_url = cdn_url.join(&path)?; use guilds::dsl; update(guilds::table) diff --git a/src/objects/me.rs b/src/objects/me.rs index 167e61e..a0b399d 100644 --- a/src/objects/me.rs +++ b/src/objects/me.rs @@ -14,7 +14,7 @@ use crate::{ error::Error, objects::{Friend, FriendRequest, User}, schema::{friend_requests, friends, guild_members, guilds, users}, - utils::{CacheFns, EMAIL_REGEX, USERNAME_REGEX, image_check}, + utils::{EMAIL_REGEX, USERNAME_REGEX, image_check}, }; use super::{Guild, guild::GuildBuilder, load_or_empty, member::MemberBuilder}; @@ -29,7 +29,6 @@ pub struct Me { avatar: Option, pronouns: Option, about: Option, - online_status: i16, pub email: String, pub email_verified: bool, } @@ -76,13 +75,15 @@ impl Me { pub async fn set_avatar( &mut self, - conn: &mut Conn, app_state: &AppState, + cdn_url: Url, avatar: Bytes, ) -> Result<(), Error> { let avatar_clone = avatar.clone(); let image_type = task::spawn_blocking(move || image_check(avatar_clone)).await??; + let mut conn = app_state.pool.get().await?; + if let Some(avatar) = &self.avatar { let avatar_url: Url = avatar.parse()?; @@ -95,25 +96,17 @@ impl Me { app_state.bunny_storage.upload(path.clone(), avatar).await?; - let avatar_url = app_state.config.bunny.cdn_url.join(&path)?; + let avatar_url = cdn_url.join(&path)?; use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::avatar.eq(avatar_url.as_str())) - .execute(conn) + .execute(&mut conn) .await?; - if app_state - .cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - app_state - .cache_pool - .del_cache_key(self.uuid.to_string()) - .await? + if app_state.get_cache_key(self.uuid.to_string()).await.is_ok() { + app_state.del_cache_key(self.uuid.to_string()).await? } self.avatar = Some(avatar_url.to_string()); @@ -134,8 +127,7 @@ impl Me { pub async fn set_username( &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, new_username: String, ) -> Result<(), Error> { if !USERNAME_REGEX.is_match(&new_username) @@ -145,19 +137,17 @@ impl Me { return Err(Error::BadRequest("Invalid username".to_string())); } + let mut conn = app_state.pool.get().await?; + use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::username.eq(new_username.as_str())) - .execute(conn) + .execute(&mut conn) .await?; - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await? + if app_state.get_cache_key(self.uuid.to_string()).await.is_ok() { + app_state.del_cache_key(self.uuid.to_string()).await? } self.username = new_username; @@ -167,10 +157,11 @@ impl Me { pub async fn set_display_name( &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, new_display_name: String, ) -> Result<(), Error> { + let mut conn = app_state.pool.get().await?; + let new_display_name_option = if new_display_name.is_empty() { None } else { @@ -181,15 +172,11 @@ impl Me { update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::display_name.eq(&new_display_name_option)) - .execute(conn) + .execute(&mut conn) .await?; - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await? + if app_state.get_cache_key(self.uuid.to_string()).await.is_ok() { + app_state.del_cache_key(self.uuid.to_string()).await? } self.display_name = new_display_name_option; @@ -199,14 +186,15 @@ impl Me { pub async fn set_email( &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, new_email: String, ) -> Result<(), Error> { if !EMAIL_REGEX.is_match(&new_email) { return Err(Error::BadRequest("Invalid username".to_string())); } + let mut conn = app_state.pool.get().await?; + use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) @@ -214,15 +202,11 @@ impl Me { dsl::email.eq(new_email.as_str()), dsl::email_verified.eq(false), )) - .execute(conn) + .execute(&mut conn) .await?; - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await? + if app_state.get_cache_key(self.uuid.to_string()).await.is_ok() { + app_state.del_cache_key(self.uuid.to_string()).await? } self.email = new_email; @@ -232,23 +216,20 @@ impl Me { pub async fn set_pronouns( &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, new_pronouns: String, ) -> Result<(), Error> { + let mut conn = app_state.pool.get().await?; + use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set((dsl::pronouns.eq(new_pronouns.as_str()),)) - .execute(conn) + .execute(&mut conn) .await?; - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await? + if app_state.get_cache_key(self.uuid.to_string()).await.is_ok() { + app_state.del_cache_key(self.uuid.to_string()).await? } Ok(()) @@ -256,52 +237,20 @@ impl Me { pub async fn set_about( &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, new_about: String, ) -> Result<(), Error> { + let mut conn = app_state.pool.get().await?; + use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set((dsl::about.eq(new_about.as_str()),)) - .execute(conn) + .execute(&mut conn) .await?; - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await? - } - - Ok(()) - } - - pub async fn set_online_status( - &mut self, - conn: &mut Conn, - cache_pool: &redis::Client, - new_status: i16, - ) -> Result<(), Error> { - if !(0..=4).contains(&new_status) { - return Err(Error::BadRequest("Invalid status code".to_string())); - } - self.online_status = new_status; - - use users::dsl; - update(users::table) - .filter(dsl::uuid.eq(self.uuid)) - .set(dsl::online_status.eq(new_status)) - .execute(conn) - .await?; - - if cache_pool - .get_cache_key::(self.uuid.to_string()) - .await - .is_ok() - { - cache_pool.del_cache_key(self.uuid.to_string()).await? + if app_state.get_cache_key(self.uuid.to_string()).await.is_ok() { + app_state.del_cache_key(self.uuid.to_string()).await? } Ok(()) @@ -417,18 +366,16 @@ impl Me { Ok(()) } - pub async fn get_friends( - &self, - conn: &mut Conn, - cache_pool: &redis::Client, - ) -> Result, Error> { + pub async fn get_friends(&self, app_state: &AppState) -> Result, Error> { use friends::dsl; + let mut conn = app_state.pool.get().await?; + let friends1 = load_or_empty( dsl::friends .filter(dsl::uuid1.eq(self.uuid)) .select(Friend::as_select()) - .load(conn) + .load(&mut conn) .await, )?; @@ -436,21 +383,21 @@ impl Me { dsl::friends .filter(dsl::uuid2.eq(self.uuid)) .select(Friend::as_select()) - .load(conn) + .load(&mut conn) .await, )?; - let mut friends = vec![]; + let friend_futures = friends1.iter().map(async move |friend| { + User::fetch_one_with_friendship(app_state, self, friend.uuid2).await + }); - for friend in friends1 { - friends - .push(User::fetch_one_with_friendship(conn, cache_pool, self, friend.uuid2).await?); - } + let mut friends = futures_util::future::try_join_all(friend_futures).await?; - for friend in friends2 { - friends - .push(User::fetch_one_with_friendship(conn, cache_pool, self, friend.uuid1).await?); - } + let friend_futures = friends2.iter().map(async move |friend| { + User::fetch_one_with_friendship(app_state, self, friend.uuid1).await + }); + + friends.append(&mut futures_util::future::try_join_all(friend_futures).await?); Ok(friends) } diff --git a/src/objects/member.rs b/src/objects/member.rs index f7e56da..50b76b0 100644 --- a/src/objects/member.rs +++ b/src/objects/member.rs @@ -1,31 +1,21 @@ use diesel::{ - Associations, BoolExpressionMethods, ExpressionMethods, Identifiable, Insertable, JoinOnDsl, - QueryDsl, Queryable, Selectable, SelectableHelper, define_sql_function, delete, insert_into, - sql_types::{Nullable, VarChar}, + ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, insert_into, }; use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - Conn, + AppState, Conn, error::Error, - objects::PaginationRequest, - schema::{friends, guild_bans, guild_members, users}, + objects::{Me, Permissions, Role}, + schema::guild_members, }; -use super::{ - Friend, Guild, GuildBan, Me, Pagination, Permissions, Role, User, load_or_empty, - user::UserBuilder, -}; +use super::{User, load_or_empty}; -define_sql_function! { fn coalesce(x: Nullable, y: Nullable, z: VarChar) -> Text; } - -#[derive(Serialize, Queryable, Identifiable, Selectable, Insertable, Associations)] +#[derive(Serialize, Queryable, Selectable, Insertable)] #[diesel(table_name = guild_members)] -#[diesel(belongs_to(UserBuilder, foreign_key = user_uuid))] -#[diesel(belongs_to(Guild, foreign_key = guild_uuid))] -#[diesel(primary_key(uuid))] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct MemberBuilder { pub uuid: Uuid, @@ -36,22 +26,15 @@ pub struct MemberBuilder { } impl MemberBuilder { - pub async fn build( - &self, - conn: &mut Conn, - cache_pool: &redis::Client, - me: Option<&Me>, - ) -> Result { + pub async fn build(&self, app_state: &AppState, me: Option<&Me>) -> Result { let user; if let Some(me) = me { - user = User::fetch_one_with_friendship(conn, cache_pool, me, self.user_uuid).await?; + user = User::fetch_one_with_friendship(app_state, me, self.user_uuid).await?; } else { - user = User::fetch_one(conn, cache_pool, self.user_uuid).await?; + user = User::fetch_one(app_state, self.user_uuid).await?; } - let roles = Role::fetch_from_member(conn, cache_pool, self).await?; - Ok(Member { uuid: self.uuid, nickname: self.nickname.clone(), @@ -59,44 +42,16 @@ impl MemberBuilder { guild_uuid: self.guild_uuid, is_owner: self.is_owner, user, - roles, - }) - } - - async fn build_with_parts( - &self, - conn: &mut Conn, - cache_pool: &redis::Client, - user_builder: UserBuilder, - friend: Option, - ) -> Result { - let mut user = user_builder.build(); - - if let Some(friend) = friend { - user.friends_since = Some(friend.accepted_at); - } - - let roles = Role::fetch_from_member(conn, cache_pool, self).await?; - - Ok(Member { - uuid: self.uuid, - nickname: self.nickname.clone(), - user_uuid: self.user_uuid, - guild_uuid: self.guild_uuid, - is_owner: self.is_owner, - user, - roles, }) } pub async fn check_permission( &self, - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, permission: Permissions, ) -> Result<(), Error> { if !self.is_owner { - let roles = Role::fetch_from_member(conn, cache_pool, self).await?; + let roles = Role::fetch_from_member(app_state, self.uuid).await?; let allowed = roles.iter().any(|r| r.permissions & permission as i64 != 0); if !allowed { return Err(Error::Forbidden("Not allowed".to_string())); @@ -107,16 +62,14 @@ impl MemberBuilder { } } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] pub struct Member { pub uuid: Uuid, pub nickname: Option, - #[serde(skip)] pub user_uuid: Uuid, pub guild_uuid: Uuid, pub is_owner: bool, user: User, - roles: Vec, } impl Member { @@ -148,173 +101,55 @@ impl Member { } pub async fn fetch_one( - conn: &mut Conn, - cache_pool: &redis::Client, - me: Option<&Me>, + app_state: &AppState, + me: &Me, user_uuid: Uuid, guild_uuid: Uuid, ) -> Result { - let member: MemberBuilder; - let user: UserBuilder; - let friend: Option; - use friends::dsl as fdsl; + let mut conn = app_state.pool.get().await?; + use guild_members::dsl; - if let Some(me) = me { - (member, user, friend) = dsl::guild_members - .filter(dsl::guild_uuid.eq(guild_uuid)) - .filter(dsl::user_uuid.eq(user_uuid)) - .inner_join(users::table) - .left_join( - fdsl::friends.on(fdsl::uuid1 - .eq(me.uuid) - .and(fdsl::uuid2.eq(users::uuid)) - .or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))), - ) - .select(( - MemberBuilder::as_select(), - UserBuilder::as_select(), - Option::::as_select(), - )) - .get_result(conn) - .await?; - } else { - (member, user) = dsl::guild_members - .filter(dsl::guild_uuid.eq(guild_uuid)) - .filter(dsl::user_uuid.eq(user_uuid)) - .inner_join(users::table) - .select((MemberBuilder::as_select(), UserBuilder::as_select())) - .get_result(conn) - .await?; + let member: MemberBuilder = dsl::guild_members + .filter(dsl::user_uuid.eq(user_uuid)) + .filter(dsl::guild_uuid.eq(guild_uuid)) + .select(MemberBuilder::as_select()) + .get_result(&mut conn) + .await?; - friend = None; - } - - member - .build_with_parts(conn, cache_pool, user, friend) - .await + member.build(app_state, Some(me)).await } - pub async fn fetch_one_with_uuid( - conn: &mut Conn, - cache_pool: &redis::Client, - me: Option<&Me>, - uuid: Uuid, - ) -> Result { - let member: MemberBuilder; - let user: UserBuilder; - let friend: Option; - use friends::dsl as fdsl; - use guild_members::dsl; - if let Some(me) = me { - (member, user, friend) = dsl::guild_members - .filter(dsl::uuid.eq(uuid)) - .inner_join(users::table) - .left_join( - fdsl::friends.on(fdsl::uuid1 - .eq(me.uuid) - .and(fdsl::uuid2.eq(users::uuid)) - .or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))), - ) - .select(( - MemberBuilder::as_select(), - UserBuilder::as_select(), - Option::::as_select(), - )) - .get_result(conn) - .await?; - } else { - (member, user) = dsl::guild_members - .filter(dsl::uuid.eq(uuid)) - .inner_join(users::table) - .select((MemberBuilder::as_select(), UserBuilder::as_select())) - .get_result(conn) - .await?; - - friend = None; - } - - member - .build_with_parts(conn, cache_pool, user, friend) - .await - } - - pub async fn fetch_page( - conn: &mut Conn, - cache_pool: &redis::Client, + pub async fn fetch_all( + app_state: &AppState, me: &Me, guild_uuid: Uuid, - pagination: PaginationRequest, - ) -> Result, Error> { - let per_page = pagination.per_page.unwrap_or(50); - let page_multiplier: i64 = ((pagination.page - 1) * per_page).into(); + ) -> Result, Error> { + let mut conn = app_state.pool.get().await?; - if !(10..=100).contains(&per_page) { - return Err(Error::BadRequest( - "Invalid amount per page requested".to_string(), - )); - } - - use friends::dsl as fdsl; use guild_members::dsl; - let member_builders: Vec<(MemberBuilder, UserBuilder, Option)> = load_or_empty( + let member_builders: Vec = load_or_empty( dsl::guild_members .filter(dsl::guild_uuid.eq(guild_uuid)) - .inner_join(users::table) - .left_join( - fdsl::friends.on(fdsl::uuid1 - .eq(me.uuid) - .and(fdsl::uuid2.eq(users::uuid)) - .or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))), - ) - .limit(per_page.into()) - .offset(page_multiplier) - .order_by(coalesce( - dsl::nickname, - users::display_name, - users::username, - )) - .select(( - MemberBuilder::as_select(), - UserBuilder::as_select(), - Option::::as_select(), - )) - .load(conn) + .select(MemberBuilder::as_select()) + .load(&mut conn) .await, )?; - let pages = Member::count(conn, guild_uuid).await? as f32 / per_page as f32; + let mut members = vec![]; - let mut members = Pagination:: { - objects: Vec::with_capacity(member_builders.len()), - amount: member_builders.len() as i32, - pages: pages.ceil() as i32, - page: pagination.page, - }; - - for (member, user, friend) in member_builders { - members.objects.push( - member - .build_with_parts(conn, cache_pool, user, friend) - .await?, - ); + for builder in member_builders { + members.push(builder.build(app_state, Some(me)).await?); } Ok(members) } pub async fn new( - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, user_uuid: Uuid, guild_uuid: Uuid, ) -> Result { - let banned = GuildBan::fetch_one(conn, guild_uuid, user_uuid).await; - - match banned { - Ok(_) => Err(Error::Forbidden("User banned".to_string())), - Err(Error::SqlError(diesel::result::Error::NotFound)) => Ok(()), - Err(e) => Err(e), - }?; + let mut conn = app_state.pool.get().await?; let member_uuid = Uuid::now_v7(); @@ -328,51 +163,9 @@ impl Member { insert_into(guild_members::table) .values(&member) - .execute(conn) + .execute(&mut conn) .await?; - member.build(conn, cache_pool, None).await - } - - pub async fn delete(self, conn: &mut Conn) -> Result<(), Error> { - if self.is_owner { - return Err(Error::Forbidden("Can not kick owner".to_string())); - } - delete(guild_members::table) - .filter(guild_members::uuid.eq(self.uuid)) - .execute(conn) - .await?; - - Ok(()) - } - - pub async fn ban(self, conn: &mut Conn, reason: &String) -> Result<(), Error> { - if self.is_owner { - return Err(Error::Forbidden("Can not ban owner".to_string())); - } - - use guild_bans::dsl; - insert_into(guild_bans::table) - .values(( - dsl::guild_uuid.eq(self.guild_uuid), - dsl::user_uuid.eq(self.user_uuid), - dsl::reason.eq(reason), - )) - .execute(conn) - .await?; - - self.delete(conn).await?; - - Ok(()) - } - - pub fn to_builder(&self) -> MemberBuilder { - MemberBuilder { - uuid: self.uuid, - nickname: self.nickname.clone(), - user_uuid: self.user_uuid, - guild_uuid: self.guild_uuid, - is_owner: self.is_owner, - } + member.build(app_state, None).await } } diff --git a/src/objects/message.rs b/src/objects/message.rs index f30f14d..c50f82c 100644 --- a/src/objects/message.rs +++ b/src/objects/message.rs @@ -1,15 +1,10 @@ -use diesel::{ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable}; -use diesel_async::RunQueryDsl; +use diesel::{Insertable, Queryable, Selectable}; use serde::Serialize; use uuid::Uuid; -use crate::{ - Conn, - error::Error, - schema::{channels, guilds, messages}, -}; +use crate::{AppState, error::Error, schema::messages}; -use super::Member; +use super::User; #[derive(Clone, Queryable, Selectable, Insertable)] #[diesel(table_name = messages)] @@ -20,24 +15,12 @@ pub struct MessageBuilder { pub user_uuid: Uuid, pub message: String, pub reply_to: Option, + pub is_edited: bool, } impl MessageBuilder { - pub async fn build( - &self, - conn: &mut Conn, - cache_pool: &redis::Client, - ) -> Result { - use channels::dsl; - - let guild_uuid = dsl::channels - .filter(dsl::uuid.eq(self.channel_uuid)) - .inner_join(guilds::table) - .select(guilds::uuid) - .get_result(conn) - .await?; - - let member = Member::fetch_one(conn, cache_pool, None, self.user_uuid, guild_uuid).await?; + pub async fn build(&self, app_state: &AppState) -> Result { + let user = User::fetch_one(app_state, self.user_uuid).await?; Ok(Message { uuid: self.uuid, @@ -45,7 +28,8 @@ impl MessageBuilder { user_uuid: self.user_uuid, message: self.message.clone(), reply_to: self.reply_to, - member, + is_edited: self.is_edited, + user, }) } } @@ -57,5 +41,6 @@ pub struct Message { user_uuid: Uuid, message: String, reply_to: Option, - member: Member, + is_edited: bool, + user: User, } diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 5a013ca..4af16d8 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -4,10 +4,9 @@ use lettre::{ transport::smtp::authentication::Credentials, }; use log::debug; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use uuid::Uuid; -mod bans; mod channel; mod email_token; mod friends; @@ -20,7 +19,6 @@ mod password_reset_token; mod role; mod user; -pub use bans::GuildBan; pub use channel::Channel; pub use email_token::EmailToken; pub use friends::Friend; @@ -76,20 +74,6 @@ impl Cookies for Request { } */ -#[derive(Serialize)] -pub struct Pagination { - objects: Vec, - amount: i32, - pages: i32, - page: i32, -} - -#[derive(Deserialize)] -pub struct PaginationRequest { - pub page: i32, - pub per_page: Option, -} - fn load_or_empty( query_result: Result, diesel::result::Error>, ) -> Result, diesel::result::Error> { diff --git a/src/objects/password_reset_token.rs b/src/objects/password_reset_token.rs index ca5c62f..04ff43c 100644 --- a/src/objects/password_reset_token.rs +++ b/src/objects/password_reset_token.rs @@ -10,10 +10,10 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - AppState, Conn, + AppState, error::Error, schema::users, - utils::{CacheFns, PASSWORD_REGEX, generate_token, global_checks, user_uuid_from_identifier}, + utils::{PASSWORD_REGEX, generate_token, global_checks, user_uuid_from_identifier}, }; #[derive(Serialize, Deserialize)] @@ -24,49 +24,50 @@ pub struct PasswordResetToken { } impl PasswordResetToken { - pub async fn get( - cache_pool: &redis::Client, - token: String, - ) -> Result { - let user_uuid: Uuid = cache_pool.get_cache_key(token.to_string()).await?; - let password_reset_token = cache_pool - .get_cache_key(format!("{user_uuid}_password_reset")) - .await?; + pub async fn get(app_state: &AppState, token: String) -> Result { + let user_uuid: Uuid = + serde_json::from_str(&app_state.get_cache_key(token.to_string()).await?)?; + let password_reset_token = serde_json::from_str( + &app_state + .get_cache_key(format!("{user_uuid}_password_reset")) + .await?, + )?; Ok(password_reset_token) } pub async fn get_with_identifier( - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, identifier: String, ) -> Result { - let user_uuid = user_uuid_from_identifier(conn, &identifier).await?; + let mut conn = app_state.pool.get().await?; - let password_reset_token = cache_pool - .get_cache_key(format!("{user_uuid}_password_reset")) - .await?; + let user_uuid = user_uuid_from_identifier(&mut conn, &identifier).await?; + + let password_reset_token = serde_json::from_str( + &app_state + .get_cache_key(format!("{user_uuid}_password_reset")) + .await?, + )?; Ok(password_reset_token) } #[allow(clippy::new_ret_no_self)] - pub async fn new( - conn: &mut Conn, - app_state: &AppState, - identifier: String, - ) -> Result<(), Error> { + pub async fn new(app_state: &AppState, identifier: String) -> Result<(), Error> { let token = generate_token::<32>()?; - let user_uuid = user_uuid_from_identifier(conn, &identifier).await?; + let mut conn = app_state.pool.get().await?; - global_checks(conn, &app_state.config, user_uuid).await?; + let user_uuid = user_uuid_from_identifier(&mut conn, &identifier).await?; + + global_checks(app_state, user_uuid).await?; use users::dsl as udsl; let (username, email_address): (String, String) = udsl::users .filter(udsl::uuid.eq(user_uuid)) .select((udsl::username, udsl::email)) - .get_result(conn) + .get_result(&mut conn) .await?; let password_reset_token = PasswordResetToken { @@ -76,7 +77,6 @@ impl PasswordResetToken { }; app_state - .cache_pool .set_cache_key( format!("{user_uuid}_password_reset"), password_reset_token, @@ -84,7 +84,6 @@ impl PasswordResetToken { ) .await?; app_state - .cache_pool .set_cache_key(token.clone(), user_uuid, 86400) .await?; @@ -107,12 +106,7 @@ impl PasswordResetToken { Ok(()) } - pub async fn set_password( - &self, - conn: &mut Conn, - app_state: &AppState, - password: String, - ) -> Result<(), Error> { + pub async fn set_password(&self, app_state: &AppState, password: String) -> Result<(), Error> { if !PASSWORD_REGEX.is_match(&password) { return Err(Error::BadRequest( "Please provide a valid password".to_string(), @@ -126,17 +120,19 @@ impl PasswordResetToken { .hash_password(password.as_bytes(), &salt) .map_err(|e| Error::PasswordHashError(e.to_string()))?; + let mut conn = app_state.pool.get().await?; + use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.user_uuid)) .set(dsl::password.eq(hashed_password.to_string())) - .execute(conn) + .execute(&mut conn) .await?; let (username, email_address): (String, String) = dsl::users .filter(dsl::uuid.eq(self.user_uuid)) .select((dsl::username, dsl::email)) - .get_result(conn) + .get_result(&mut conn) .await?; let login_page = app_state.config.web.frontend_url.join("login")?; @@ -153,14 +149,14 @@ impl PasswordResetToken { app_state.mail_client.send_mail(email).await?; - self.delete(&app_state.cache_pool).await + self.delete(app_state).await } - pub async fn delete(&self, cache_pool: &redis::Client) -> Result<(), Error> { - cache_pool + pub async fn delete(&self, app_state: &AppState) -> Result<(), Error> { + app_state .del_cache_key(format!("{}_password_reset", &self.user_uuid)) .await?; - cache_pool.del_cache_key(self.token.to_string()).await?; + app_state.del_cache_key(self.token.to_string()).await?; Ok(()) } diff --git a/src/objects/role.rs b/src/objects/role.rs index cc71fb2..ea70686 100644 --- a/src/objects/role.rs +++ b/src/objects/role.rs @@ -1,24 +1,22 @@ -use diesel::query_dsl::BelongingToDsl; use diesel::{ - Associations, ExpressionMethods, Identifiable, Insertable, QueryDsl, Queryable, Selectable, - SelectableHelper, insert_into, update, + ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, insert_into, + update, }; use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - Conn, + AppState, Conn, error::Error, schema::{role_members, roles}, - utils::{CacheFns, order_by_is_above}, + utils::order_by_is_above, }; -use super::{HasIsAbove, HasUuid, load_or_empty, member::MemberBuilder}; +use super::{HasIsAbove, HasUuid, load_or_empty}; -#[derive(Deserialize, Serialize, Clone, Identifiable, Queryable, Selectable, Insertable)] +#[derive(Deserialize, Serialize, Clone, Queryable, Selectable, Insertable)] #[diesel(table_name = roles)] -#[diesel(primary_key(uuid))] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Role { uuid: Uuid, @@ -29,17 +27,27 @@ pub struct Role { pub permissions: i64, } -#[derive(Serialize, Clone, Identifiable, Queryable, Selectable, Insertable, Associations)] +#[derive(Serialize, Clone, Queryable, Selectable, Insertable)] #[diesel(table_name = role_members)] -#[diesel(belongs_to(MemberBuilder, foreign_key = member_uuid))] -#[diesel(belongs_to(Role, foreign_key = role_uuid))] -#[diesel(primary_key(role_uuid, member_uuid))] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct RoleMember { role_uuid: Uuid, member_uuid: Uuid, } +impl RoleMember { + async fn fetch_role(&self, conn: &mut Conn) -> Result { + use roles::dsl; + let role: Role = dsl::roles + .filter(dsl::uuid.eq(self.role_uuid)) + .select(Role::as_select()) + .get_result(conn) + .await?; + + Ok(role) + } +} + impl HasUuid for Role { fn uuid(&self) -> &Uuid { self.uuid.as_ref() @@ -67,27 +75,35 @@ impl Role { } pub async fn fetch_from_member( - conn: &mut Conn, - cache_pool: &redis::Client, - member: &MemberBuilder, + app_state: &AppState, + member_uuid: Uuid, ) -> Result, Error> { - if let Ok(roles) = cache_pool - .get_cache_key(format!("{}_roles", member.uuid)) + if let Ok(roles) = app_state + .get_cache_key(format!("{member_uuid}_roles")) .await { - return Ok(roles); + return Ok(serde_json::from_str(&roles)?); } - let roles: Vec = load_or_empty( - RoleMember::belonging_to(member) - .inner_join(roles::table) - .select(Role::as_select()) - .load(conn) + let mut conn = app_state.pool.get().await?; + + use role_members::dsl; + let role_memberships: Vec = load_or_empty( + dsl::role_members + .filter(dsl::member_uuid.eq(member_uuid)) + .select(RoleMember::as_select()) + .load(&mut conn) .await, )?; - cache_pool - .set_cache_key(format!("{}_roles", member.uuid), roles.clone(), 300) + let mut roles = vec![]; + + for membership in role_memberships { + roles.push(membership.fetch_role(&mut conn).await?); + } + + app_state + .set_cache_key(format!("{member_uuid}_roles"), roles.clone(), 300) .await?; Ok(roles) @@ -160,10 +176,6 @@ pub enum Permissions { ManageGuild = 32, /// Lets users change member settings (nickname, etc) ManageMember = 64, - /// Lets users ban members - BanMember = 128, - /// Lets users kick members - KickMember = 256, } impl Permissions { @@ -176,8 +188,6 @@ impl Permissions { Self::ManageInvite, Self::ManageGuild, Self::ManageMember, - Self::BanMember, - Self::KickMember, ]; all_perms diff --git a/src/objects/user.rs b/src/objects/user.rs index c91b809..c1f164d 100644 --- a/src/objects/user.rs +++ b/src/objects/user.rs @@ -4,7 +4,7 @@ use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{Conn, error::Error, objects::Me, schema::users, utils::CacheFns}; +use crate::{AppState, Conn, error::Error, objects::Me, schema::users}; use super::load_or_empty; @@ -18,11 +18,10 @@ pub struct UserBuilder { avatar: Option, pronouns: Option, about: Option, - online_status: i16, } impl UserBuilder { - pub fn build(self) -> User { + fn build(self) -> User { User { uuid: self.uuid, username: self.username, @@ -30,7 +29,6 @@ impl UserBuilder { avatar: self.avatar, pronouns: self.pronouns, about: self.about, - online_status: self.online_status, friends_since: None, } } @@ -38,36 +36,33 @@ impl UserBuilder { #[derive(Deserialize, Serialize, Clone)] pub struct User { - pub uuid: Uuid, + uuid: Uuid, username: String, display_name: Option, avatar: Option, pronouns: Option, about: Option, - online_status: i16, - pub friends_since: Option>, + friends_since: Option>, } impl User { - pub async fn fetch_one( - conn: &mut Conn, - cache_pool: &redis::Client, - user_uuid: Uuid, - ) -> Result { - if let Ok(cache_hit) = cache_pool.get_cache_key(user_uuid.to_string()).await { - return Ok(cache_hit); + pub async fn fetch_one(app_state: &AppState, user_uuid: Uuid) -> Result { + let mut conn = app_state.pool.get().await?; + + if let Ok(cache_hit) = app_state.get_cache_key(user_uuid.to_string()).await { + return Ok(serde_json::from_str(&cache_hit)?); } use users::dsl; let user_builder: UserBuilder = dsl::users .filter(dsl::uuid.eq(user_uuid)) .select(UserBuilder::as_select()) - .get_result(conn) + .get_result(&mut conn) .await?; let user = user_builder.build(); - cache_pool + app_state .set_cache_key(user_uuid.to_string(), user.clone(), 1800) .await?; @@ -75,14 +70,15 @@ impl User { } pub async fn fetch_one_with_friendship( - conn: &mut Conn, - cache_pool: &redis::Client, + app_state: &AppState, me: &Me, user_uuid: Uuid, ) -> Result { - let mut user = Self::fetch_one(conn, cache_pool, user_uuid).await?; + let mut conn = app_state.pool.get().await?; - if let Some(friend) = me.friends_with(conn, user_uuid).await? { + let mut user = Self::fetch_one(app_state, user_uuid).await?; + + if let Some(friend) = me.friends_with(&mut conn, user_uuid).await? { user.friends_since = Some(friend.accepted_at); } diff --git a/src/schema.rs b/src/schema.rs index 88f6155..b3cb3d4 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -47,16 +47,6 @@ diesel::table! { } } -diesel::table! { - guild_bans (user_uuid, guild_uuid) { - guild_uuid -> Uuid, - user_uuid -> Uuid, - #[max_length = 200] - reason -> Nullable, - banned_since -> Timestamptz, - } -} - diesel::table! { guild_members (uuid) { uuid -> Uuid, @@ -104,6 +94,7 @@ diesel::table! { #[max_length = 4000] message -> Varchar, reply_to -> Nullable, + is_edited -> Bool, } } @@ -126,7 +117,7 @@ diesel::table! { } diesel::table! { - roles (uuid) { + roles (uuid, guild_uuid) { uuid -> Uuid, guild_uuid -> Uuid, #[max_length = 50] @@ -157,17 +148,13 @@ diesel::table! { pronouns -> Nullable, #[max_length = 200] about -> Nullable, - online_status -> Int2, } } diesel::joinable!(access_tokens -> refresh_tokens (refresh_token)); diesel::joinable!(access_tokens -> users (uuid)); diesel::joinable!(channel_permissions -> channels (channel_uuid)); -diesel::joinable!(channel_permissions -> roles (role_uuid)); diesel::joinable!(channels -> guilds (guild_uuid)); -diesel::joinable!(guild_bans -> guilds (guild_uuid)); -diesel::joinable!(guild_bans -> users (user_uuid)); diesel::joinable!(guild_members -> guilds (guild_uuid)); diesel::joinable!(guild_members -> users (user_uuid)); diesel::joinable!(instance_permissions -> users (uuid)); @@ -177,7 +164,6 @@ diesel::joinable!(messages -> channels (channel_uuid)); diesel::joinable!(messages -> users (user_uuid)); diesel::joinable!(refresh_tokens -> users (uuid)); diesel::joinable!(role_members -> guild_members (member_uuid)); -diesel::joinable!(role_members -> roles (role_uuid)); diesel::joinable!(roles -> guilds (guild_uuid)); diesel::allow_tables_to_appear_in_same_query!( @@ -186,7 +172,6 @@ diesel::allow_tables_to_appear_in_same_query!( channels, friend_requests, friends, - guild_bans, guild_members, guilds, instance_permissions, diff --git a/src/utils.rs b/src/utils.rs index 7ef880e..e1df906 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,13 +8,14 @@ use diesel::{ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use getrandom::fill; use hex::encode; +use redis::RedisError; use regex::Regex; -use serde::{Serialize, de::DeserializeOwned}; +use serde::Serialize; use time::Duration; use uuid::Uuid; use crate::{ - Conn, + AppState, Conn, config::Config, error::Error, objects::{HasIsAbove, HasUuid}, @@ -114,13 +115,15 @@ pub async fn user_uuid_from_username(conn: &mut Conn, username: &String) -> Resu } } -pub async fn global_checks(conn: &mut Conn, config: &Config, user_uuid: Uuid) -> Result<(), Error> { - if config.instance.require_email_verification { +pub async fn global_checks(app_state: &AppState, user_uuid: Uuid) -> Result<(), Error> { + if app_state.config.instance.require_email_verification { + let mut conn = app_state.pool.get().await?; + use users::dsl; let email_verified: bool = dsl::users .filter(dsl::uuid.eq(user_uuid)) .select(dsl::email_verified) - .get_result(conn) + .get_result(&mut conn) .await?; if !email_verified { @@ -158,28 +161,14 @@ where Ok(ordered) } -#[allow(async_fn_in_trait)] -pub trait CacheFns { - async fn set_cache_key( - &self, - key: String, - value: impl Serialize, - expire: u32, - ) -> Result<(), Error>; - async fn get_cache_key(&self, key: String) -> Result - where - T: DeserializeOwned; - async fn del_cache_key(&self, key: String) -> Result<(), Error>; -} - -impl CacheFns for redis::Client { - async fn set_cache_key( +impl AppState { + pub async fn set_cache_key( &self, key: String, value: impl Serialize, expire: u32, ) -> Result<(), Error> { - let mut conn = self.get_multiplexed_tokio_connection().await?; + let mut conn = self.cache_pool.get_multiplexed_tokio_connection().await?; let key_encoded = encode(key); @@ -198,31 +187,26 @@ impl CacheFns for redis::Client { Ok(()) } - async fn get_cache_key(&self, key: String) -> Result - where - T: DeserializeOwned, - { - let mut conn = self.get_multiplexed_tokio_connection().await?; + pub async fn get_cache_key(&self, key: String) -> Result { + let mut conn = self.cache_pool.get_multiplexed_tokio_connection().await?; let key_encoded = encode(key); - let res: String = redis::cmd("GET") + redis::cmd("GET") .arg(key_encoded) .query_async(&mut conn) - .await?; - - Ok(serde_json::from_str(&res)?) + .await } - async fn del_cache_key(&self, key: String) -> Result<(), Error> { - let mut conn = self.get_multiplexed_tokio_connection().await?; + pub async fn del_cache_key(&self, key: String) -> Result<(), RedisError> { + let mut conn = self.cache_pool.get_multiplexed_tokio_connection().await?; let key_encoded = encode(key); - Ok(redis::cmd("DEL") + redis::cmd("DEL") .arg(key_encoded) .query_async(&mut conn) - .await?) + .await } }