use axum::body::Bytes; use diesel::{ ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, delete, insert_into, update, }; use diesel_async::RunQueryDsl; use serde::Serialize; use tokio::task; use url::Url; use uuid::Uuid; use crate::{ AppState, Conn, error::Error, objects::{Friend, FriendRequest, User}, schema::{friend_requests, friends, guild_members, guilds, users}, utils::{CacheFns, EMAIL_REGEX, USERNAME_REGEX, image_check}, }; use super::{Guild, guild::GuildBuilder, load_or_empty, member::MemberBuilder}; #[derive(Serialize, Queryable, Selectable)] #[diesel(table_name = users)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Me { pub uuid: Uuid, pub username: String, pub display_name: Option, avatar: Option, pronouns: Option, about: Option, online_status: i16, pub email: String, pub email_verified: bool, } impl Me { pub async fn get(conn: &mut Conn, user_uuid: Uuid) -> Result { use users::dsl; let me: Me = dsl::users .filter(dsl::uuid.eq(user_uuid)) .select(Me::as_select()) .get_result(conn) .await?; Ok(me) } pub async fn fetch_memberships(&self, conn: &mut Conn) -> Result, Error> { use guild_members::dsl; let memberships: Vec = load_or_empty( dsl::guild_members .filter(dsl::user_uuid.eq(self.uuid)) .select(MemberBuilder::as_select()) .load(conn) .await, )?; let mut guilds: Vec = vec![]; for membership in memberships { use guilds::dsl; guilds.push( dsl::guilds .filter(dsl::uuid.eq(membership.guild_uuid)) .select(GuildBuilder::as_select()) .get_result(conn) .await? .build(conn) .await?, ) } Ok(guilds) } pub async fn set_avatar( &mut self, conn: &mut Conn, app_state: &AppState, avatar: Bytes, ) -> Result<(), Error> { let avatar_clone = avatar.clone(); let image_type = task::spawn_blocking(move || image_check(avatar_clone)).await??; if let Some(avatar) = &self.avatar { let avatar_url: Url = avatar.parse()?; let relative_url = avatar_url.path().trim_start_matches('/'); app_state.bunny_storage.delete(relative_url).await?; } let path = format!("avatar/{}/{}.{}", self.uuid, Uuid::now_v7(), image_type); app_state.bunny_storage.upload(path.clone(), avatar).await?; let avatar_url = app_state.config.bunny.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) .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? } self.avatar = Some(avatar_url.to_string()); Ok(()) } pub async fn verify_email(&self, conn: &mut Conn) -> Result<(), Error> { use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::email_verified.eq(true)) .execute(conn) .await?; Ok(()) } pub async fn set_username( &mut self, conn: &mut Conn, cache_pool: &redis::Client, new_username: String, ) -> Result<(), Error> { if !USERNAME_REGEX.is_match(&new_username) || new_username.len() < 3 || new_username.len() > 32 { return Err(Error::BadRequest("Invalid username".to_string())); } use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::username.eq(new_username.as_str())) .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? } self.username = new_username; Ok(()) } pub async fn set_display_name( &mut self, conn: &mut Conn, cache_pool: &redis::Client, new_display_name: String, ) -> Result<(), Error> { let new_display_name_option = if new_display_name.is_empty() { None } else { Some(new_display_name) }; use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set(dsl::display_name.eq(&new_display_name_option)) .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? } self.display_name = new_display_name_option; Ok(()) } pub async fn set_email( &mut self, conn: &mut Conn, cache_pool: &redis::Client, new_email: String, ) -> Result<(), Error> { if !EMAIL_REGEX.is_match(&new_email) { return Err(Error::BadRequest("Invalid username".to_string())); } use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set(( dsl::email.eq(new_email.as_str()), dsl::email_verified.eq(false), )) .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? } self.email = new_email; Ok(()) } pub async fn set_pronouns( &mut self, conn: &mut Conn, cache_pool: &redis::Client, new_pronouns: String, ) -> Result<(), Error> { use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set((dsl::pronouns.eq(new_pronouns.as_str()),)) .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? } Ok(()) } pub async fn set_about( &mut self, conn: &mut Conn, cache_pool: &redis::Client, new_about: String, ) -> Result<(), Error> { use users::dsl; update(users::table) .filter(dsl::uuid.eq(self.uuid)) .set((dsl::about.eq(new_about.as_str()),)) .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? } 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? } Ok(()) } pub async fn friends_with( &self, conn: &mut Conn, user_uuid: Uuid, ) -> Result, Error> { use friends::dsl; let friends: Vec = if self.uuid < user_uuid { load_or_empty( dsl::friends .filter(dsl::uuid1.eq(self.uuid)) .filter(dsl::uuid2.eq(user_uuid)) .load(conn) .await, )? } else { load_or_empty( dsl::friends .filter(dsl::uuid1.eq(user_uuid)) .filter(dsl::uuid2.eq(self.uuid)) .load(conn) .await, )? }; if friends.is_empty() { return Ok(None); } Ok(Some(friends[0].clone())) } pub async fn add_friend(&self, conn: &mut Conn, user_uuid: Uuid) -> Result<(), Error> { if self.friends_with(conn, user_uuid).await?.is_some() { // TODO: Check if another error should be used return Err(Error::BadRequest("Already friends with user".to_string())); } use friend_requests::dsl; let friend_request: Vec = load_or_empty( dsl::friend_requests .filter(dsl::sender.eq(user_uuid)) .filter(dsl::receiver.eq(self.uuid)) .load(conn) .await, )?; #[allow(clippy::get_first)] if let Some(friend_request) = friend_request.get(0) { use friends::dsl; if self.uuid < user_uuid { insert_into(friends::table) .values((dsl::uuid1.eq(self.uuid), dsl::uuid2.eq(user_uuid))) .execute(conn) .await?; } else { insert_into(friends::table) .values((dsl::uuid1.eq(user_uuid), dsl::uuid2.eq(self.uuid))) .execute(conn) .await?; } use friend_requests::dsl as frdsl; delete(friend_requests::table) .filter(frdsl::sender.eq(friend_request.sender)) .filter(frdsl::receiver.eq(friend_request.receiver)) .execute(conn) .await?; Ok(()) } else { use friend_requests::dsl; insert_into(friend_requests::table) .values((dsl::sender.eq(self.uuid), dsl::receiver.eq(user_uuid))) .execute(conn) .await?; Ok(()) } } pub async fn remove_friend(&self, conn: &mut Conn, user_uuid: Uuid) -> Result<(), Error> { if self.friends_with(conn, user_uuid).await?.is_none() { // TODO: Check if another error should be used return Err(Error::BadRequest("Not friends with user".to_string())); } use friends::dsl; if self.uuid < user_uuid { delete(friends::table) .filter(dsl::uuid1.eq(self.uuid)) .filter(dsl::uuid2.eq(user_uuid)) .execute(conn) .await?; } else { delete(friends::table) .filter(dsl::uuid1.eq(user_uuid)) .filter(dsl::uuid2.eq(self.uuid)) .execute(conn) .await?; } Ok(()) } pub async fn get_friends( &self, conn: &mut Conn, cache_pool: &redis::Client, ) -> Result, Error> { use friends::dsl; let friends1 = load_or_empty( dsl::friends .filter(dsl::uuid1.eq(self.uuid)) .select(Friend::as_select()) .load(conn) .await, )?; let friends2 = load_or_empty( dsl::friends .filter(dsl::uuid2.eq(self.uuid)) .select(Friend::as_select()) .load(conn) .await, )?; let mut friends = vec![]; for friend in friends1 { friends .push(User::fetch_one_with_friendship(conn, cache_pool, self, friend.uuid2).await?); } for friend in friends2 { friends .push(User::fetch_one_with_friendship(conn, cache_pool, self, friend.uuid1).await?); } Ok(friends) } /* TODO pub async fn get_friend_requests(&self, conn: &mut Conn) -> Result, Error> { use friend_requests::dsl; let friend_request: Vec = load_or_empty( dsl::friend_requests .filter(dsl::receiver.eq(self.uuid)) .load(conn) .await )?; Ok() } pub async fn delete_friend_request(&self, conn: &mut Conn, user_uuid: Uuid) -> Result, Error> { use friend_requests::dsl; let friend_request: Vec = load_or_empty( dsl::friend_requests .filter(dsl::sender.eq(user_uuid)) .filter(dsl::receiver.eq(self.uuid)) .load(conn) .await )?; Ok() } */ }