diff --git a/migrations/2025-07-07-131320_add_friends/down.sql b/migrations/2025-07-07-131320_add_friends/down.sql deleted file mode 100644 index 30637b7..0000000 --- a/migrations/2025-07-07-131320_add_friends/down.sql +++ /dev/null @@ -1,4 +0,0 @@ --- This file should undo anything in `up.sql` -DROP TABLE friend_requests; -DROP FUNCTION check_friend_request; -DROP TABLE friends; diff --git a/migrations/2025-07-07-131320_add_friends/up.sql b/migrations/2025-07-07-131320_add_friends/up.sql deleted file mode 100644 index 2ed45ab..0000000 --- a/migrations/2025-07-07-131320_add_friends/up.sql +++ /dev/null @@ -1,35 +0,0 @@ --- Your SQL goes here -CREATE TABLE friends ( - uuid1 UUID REFERENCES users(uuid), - uuid2 UUID REFERENCES users(uuid), - accepted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (uuid1, uuid2), - CHECK (uuid1 < uuid2) -); - -CREATE TABLE friend_requests ( - sender UUID REFERENCES users(uuid), - receiver UUID REFERENCES users(uuid), - requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (sender, receiver), - CHECK (sender <> receiver) -); - --- Create a function to check for existing friendships -CREATE FUNCTION check_friend_request() -RETURNS TRIGGER AS $$ -BEGIN - IF EXISTS ( - SELECT 1 FROM friends - WHERE (uuid1, uuid2) = (LEAST(NEW.sender, NEW.receiver), GREATEST(NEW.sender, NEW.receiver)) - ) THEN - RAISE EXCEPTION 'Users are already friends'; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Create the trigger -CREATE TRIGGER prevent_friend_request_conflict -BEFORE INSERT OR UPDATE ON friend_requests -FOR EACH ROW EXECUTE FUNCTION check_friend_request(); diff --git a/src/api/v1/auth/logout.rs b/src/api/v1/auth/logout.rs index b805d91..524a644 100644 --- a/src/api/v1/auth/logout.rs +++ b/src/api/v1/auth/logout.rs @@ -13,11 +13,11 @@ use crate::{ /// requires auth: kinda, needs refresh token set but no access token is technically required /// /// ### Responses -/// +/// /// 200 Logged out -/// +/// /// 404 Refresh token is invalid -/// +/// /// 401 Unauthorized (no refresh token found) /// #[get("/logout")] @@ -38,9 +38,7 @@ pub async fn res(req: HttpRequest, data: web::Data) -> Result) -> Result { - let headers = req.headers(); - - let auth_header = get_auth_header(headers)?; - - let mut conn = data.pool.get().await?; - - let uuid = check_access_token(auth_header, &mut conn).await?; - - global_checks(&data, uuid).await?; - - let me = Me::get(&mut conn, uuid).await?; - - let friends = me.get_friends(&data).await?; - - Ok(HttpResponse::Ok().json(friends)) -} - -#[derive(Deserialize)] -struct UserReq { - uuid: Uuid, -} - -/// `POST /api/v1/me/friends` Send friend request -/// -/// requires auth? yes -/// -/// ### Request Example: -/// ``` -/// json!({ -/// "uuid": "155d2291-fb23-46bd-a656-ae7c5d8218e6", -/// }); -/// ``` -/// NOTE: UUIDs in this response are made using `uuidgen`, UUIDs made by the actual backend will be UUIDv7 and have extractable timestamps -/// -/// ### Responses -/// 200 Success -/// -/// 404 Not Found -/// -/// 400 Bad Request (usually means users are already friends) -/// -#[post("/friends")] -pub async fn post( - req: HttpRequest, - json: web::Json, - data: web::Data, -) -> Result { - let headers = req.headers(); - - let auth_header = get_auth_header(headers)?; - - let mut conn = data.pool.get().await?; - - let uuid = check_access_token(auth_header, &mut conn).await?; - - global_checks(&data, uuid).await?; - - let me = Me::get(&mut conn, uuid).await?; - - me.add_friend(&mut conn, json.uuid).await?; - - Ok(HttpResponse::Ok().finish()) -} diff --git a/src/api/v1/me/friends/uuid.rs b/src/api/v1/me/friends/uuid.rs deleted file mode 100644 index 34bfeff..0000000 --- a/src/api/v1/me/friends/uuid.rs +++ /dev/null @@ -1,33 +0,0 @@ -use actix_web::{HttpRequest, HttpResponse, delete, web}; -use uuid::Uuid; - -use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - objects::Me, - utils::{get_auth_header, global_checks}, -}; - -#[delete("/friends/{uuid}")] -pub async fn delete( - req: HttpRequest, - path: web::Path<(Uuid,)>, - data: web::Data, -) -> Result { - let headers = req.headers(); - - let auth_header = get_auth_header(headers)?; - - let mut conn = data.pool.get().await?; - - let uuid = check_access_token(auth_header, &mut conn).await?; - - global_checks(&data, uuid).await?; - - let me = Me::get(&mut conn, uuid).await?; - - me.remove_friend(&mut conn, path.0).await?; - - Ok(HttpResponse::Ok().finish()) -} diff --git a/src/api/v1/me/mod.rs b/src/api/v1/me/mod.rs index f667ca4..da5c929 100644 --- a/src/api/v1/me/mod.rs +++ b/src/api/v1/me/mod.rs @@ -10,7 +10,6 @@ use crate::{ utils::{get_auth_header, global_checks}, }; -mod friends; mod guilds; pub fn web() -> Scope { @@ -18,9 +17,6 @@ pub fn web() -> Scope { .service(get) .service(update) .service(guilds::get) - .service(friends::get) - .service(friends::post) - .service(friends::uuid::delete) } #[get("")] diff --git a/src/api/v1/users/uuid.rs b/src/api/v1/users/uuid.rs index 5d36b75..9e602a0 100644 --- a/src/api/v1/users/uuid.rs +++ b/src/api/v1/users/uuid.rs @@ -7,7 +7,7 @@ use crate::{ Data, api::v1::auth::check_access_token, error::Error, - objects::{Me, User}, + objects::User, utils::{get_auth_header, global_checks}, }; @@ -45,9 +45,7 @@ pub async fn get( global_checks(&data, uuid).await?; - let me = Me::get(&mut conn, uuid).await?; - - let user = User::fetch_one_with_friendship(&data, &me, user_uuid).await?; + let user = User::fetch_one(&data, user_uuid).await?; Ok(HttpResponse::Ok().json(user)) } diff --git a/src/objects/friends.rs b/src/objects/friends.rs deleted file mode 100644 index 9d23512..0000000 --- a/src/objects/friends.rs +++ /dev/null @@ -1,24 +0,0 @@ -use chrono::{DateTime, Utc}; -use diesel::{Queryable, Selectable}; -use serde::Serialize; -use uuid::Uuid; - -use crate::schema::{friend_requests, friends}; - -#[derive(Serialize, Queryable, Selectable, Clone)] -#[diesel(table_name = friends)] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Friend { - pub uuid1: Uuid, - pub uuid2: Uuid, - pub accepted_at: DateTime, -} - -#[derive(Serialize, Queryable, Selectable, Clone)] -#[diesel(table_name = friend_requests)] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct FriendRequest { - pub sender: Uuid, - pub receiver: Uuid, - pub requested_at: DateTime, -} diff --git a/src/objects/me.rs b/src/objects/me.rs index 37951ab..e322832 100644 --- a/src/objects/me.rs +++ b/src/objects/me.rs @@ -1,8 +1,5 @@ use actix_web::web::BytesMut; -use diesel::{ - ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, delete, insert_into, - update, -}; +use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper, update}; use diesel_async::RunQueryDsl; use serde::Serialize; use tokio::task; @@ -12,8 +9,7 @@ use uuid::Uuid; use crate::{ Conn, Data, error::Error, - objects::{Friend, FriendRequest, User}, - schema::{friend_requests, friends, guild_members, guilds, users}, + schema::{guild_members, guilds, users}, utils::{EMAIL_REGEX, USERNAME_REGEX, image_check}, }; @@ -128,10 +124,7 @@ impl Me { } pub async fn set_username(&mut self, data: &Data, new_username: String) -> Result<(), Error> { - if !USERNAME_REGEX.is_match(&new_username) - || new_username.len() < 3 - || new_username.len() > 32 - { + if !USERNAME_REGEX.is_match(&new_username) || new_username.len() < 3 || new_username.len() > 32 { return Err(Error::BadRequest("Invalid username".to_string())); } @@ -160,11 +153,13 @@ impl Me { ) -> Result<(), Error> { let mut conn = data.pool.get().await?; - let new_display_name_option = if new_display_name.is_empty() { - None + let new_display_name_option; + + if new_display_name.is_empty() { + new_display_name_option = None; } else { - Some(new_display_name) - }; + new_display_name_option = Some(new_display_name) + } use users::dsl; update(users::table) @@ -241,179 +236,4 @@ impl Me { 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, data: &Data) -> Result, Error> { - use friends::dsl; - - let mut conn = data.pool.get().await?; - - let friends1 = load_or_empty( - dsl::friends - .filter(dsl::uuid1.eq(self.uuid)) - .select(Friend::as_select()) - .load(&mut conn) - .await, - )?; - - let friends2 = load_or_empty( - dsl::friends - .filter(dsl::uuid2.eq(self.uuid)) - .select(Friend::as_select()) - .load(&mut conn) - .await, - )?; - - let friend_futures = friends1.iter().map(async move |friend| { - User::fetch_one_with_friendship(data, self, friend.uuid2).await - }); - - let mut friends = futures::future::try_join_all(friend_futures).await?; - - let friend_futures = friends2.iter().map(async move |friend| { - User::fetch_one_with_friendship(data, self, friend.uuid1).await - }); - - friends.append(&mut futures::future::try_join_all(friend_futures).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() - } - */ } diff --git a/src/objects/member.rs b/src/objects/member.rs index 361e4b3..d33d2b6 100644 --- a/src/objects/member.rs +++ b/src/objects/member.rs @@ -8,7 +8,7 @@ use uuid::Uuid; use crate::{ Conn, Data, error::Error, - objects::{Me, Permissions, Role}, + objects::{Permissions, Role}, schema::guild_members, }; @@ -26,14 +26,8 @@ pub struct MemberBuilder { } impl MemberBuilder { - pub async fn build(&self, data: &Data, me: Option<&Me>) -> Result { - let user; - - if let Some(me) = me { - user = User::fetch_one_with_friendship(data, me, self.user_uuid).await?; - } else { - user = User::fetch_one(data, self.user_uuid).await?; - } + pub async fn build(&self, data: &Data) -> Result { + let user = User::fetch_one(data, self.user_uuid).await?; Ok(Member { uuid: self.uuid, @@ -100,12 +94,7 @@ impl Member { Ok(member_builder) } - pub async fn fetch_one( - data: &Data, - me: &Me, - user_uuid: Uuid, - guild_uuid: Uuid, - ) -> Result { + pub async fn fetch_one(data: &Data, user_uuid: Uuid, guild_uuid: Uuid) -> Result { let mut conn = data.pool.get().await?; use guild_members::dsl; @@ -116,10 +105,10 @@ impl Member { .get_result(&mut conn) .await?; - member.build(data, Some(me)).await + member.build(data).await } - pub async fn fetch_all(data: &Data, me: &Me, guild_uuid: Uuid) -> Result, Error> { + pub async fn fetch_all(data: &Data, guild_uuid: Uuid) -> Result, Error> { let mut conn = data.pool.get().await?; use guild_members::dsl; @@ -133,7 +122,7 @@ impl Member { let member_futures = member_builders .iter() - .map(async move |m| m.build(data, Some(me)).await); + .map(async move |m| m.build(data).await); futures::future::try_join_all(member_futures).await } @@ -156,6 +145,6 @@ impl Member { .execute(&mut conn) .await?; - member.build(data, None).await + member.build(data).await } } diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 9974410..d8de266 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -9,7 +9,6 @@ use uuid::Uuid; mod channel; mod email_token; -mod friends; mod guild; mod invite; mod me; @@ -21,8 +20,6 @@ mod user; pub use channel::Channel; pub use email_token::EmailToken; -pub use friends::Friend; -pub use friends::FriendRequest; pub use guild::Guild; pub use invite::Invite; pub use me::Me; diff --git a/src/objects/user.rs b/src/objects/user.rs index 8e42351..98e5e80 100644 --- a/src/objects/user.rs +++ b/src/objects/user.rs @@ -1,40 +1,15 @@ -use chrono::{DateTime, Utc}; use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper}; use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{Conn, Data, error::Error, objects::Me, schema::users}; +use crate::{Conn, Data, error::Error, schema::users}; use super::load_or_empty; #[derive(Deserialize, Serialize, Clone, Queryable, Selectable)] #[diesel(table_name = users)] #[diesel(check_for_backend(diesel::pg::Pg))] -pub struct UserBuilder { - uuid: Uuid, - username: String, - display_name: Option, - avatar: Option, - pronouns: Option, - about: Option, -} - -impl UserBuilder { - fn build(self) -> User { - User { - uuid: self.uuid, - username: self.username, - display_name: self.display_name, - avatar: self.avatar, - pronouns: self.pronouns, - about: self.about, - friends_since: None, - } - } -} - -#[derive(Deserialize, Serialize, Clone)] pub struct User { uuid: Uuid, username: String, @@ -42,7 +17,6 @@ pub struct User { avatar: Option, pronouns: Option, about: Option, - friends_since: Option>, } impl User { @@ -54,53 +28,33 @@ impl User { } use users::dsl; - let user_builder: UserBuilder = dsl::users + let user: User = dsl::users .filter(dsl::uuid.eq(user_uuid)) - .select(UserBuilder::as_select()) + .select(User::as_select()) .get_result(&mut conn) .await?; - let user = user_builder.build(); - data.set_cache_key(user_uuid.to_string(), user.clone(), 1800) .await?; Ok(user) } - pub async fn fetch_one_with_friendship( - data: &Data, - me: &Me, - user_uuid: Uuid, - ) -> Result { - let mut conn = data.pool.get().await?; - - let mut user = Self::fetch_one(data, user_uuid).await?; - - if let Some(friend) = me.friends_with(&mut conn, user_uuid).await? { - user.friends_since = Some(friend.accepted_at); - } - - Ok(user) - } - pub async fn fetch_amount( conn: &mut Conn, offset: i64, amount: i64, ) -> Result, Error> { use users::dsl; - let user_builders: Vec = load_or_empty( + let users: Vec = load_or_empty( dsl::users .limit(amount) .offset(offset) - .select(UserBuilder::as_select()) + .select(User::as_select()) .load(conn) .await, )?; - let users: Vec = user_builders.iter().map(|u| u.clone().build()).collect(); - Ok(users) } } diff --git a/src/schema.rs b/src/schema.rs index 2693b02..f860b31 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -31,22 +31,6 @@ diesel::table! { } } -diesel::table! { - friend_requests (sender, receiver) { - sender -> Uuid, - receiver -> Uuid, - requested_at -> Timestamptz, - } -} - -diesel::table! { - friends (uuid1, uuid2) { - uuid1 -> Uuid, - uuid2 -> Uuid, - accepted_at -> Timestamptz, - } -} - diesel::table! { guild_members (uuid) { uuid -> Uuid, @@ -169,8 +153,6 @@ diesel::allow_tables_to_appear_in_same_query!( access_tokens, channel_permissions, channels, - friend_requests, - friends, guild_members, guilds, instance_permissions,