added frontend for friendships, and fixed logic around conflicts

This commit is contained in:
2025-12-18 13:36:03 +01:00
parent af1b9e4d6d
commit d1de0583ed
4 changed files with 72 additions and 13 deletions

View File

@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS friendship_ (
user_first INT NOT NULL REFERENCES user_(id) ON DELETE CASCADE, user_first INT NOT NULL REFERENCES user_(id) ON DELETE CASCADE,
user_second INT NOT NULL REFERENCES user_(id) ON DELETE CASCADE, user_second INT NOT NULL REFERENCES user_(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (user_first, user_second), PRIMARY KEY (user_first, user_second)
); );
CREATE TABLE IF NOT EXISTS friend_request_ ( CREATE TABLE IF NOT EXISTS friend_request_ (

View File

@@ -21,3 +21,14 @@ INSERT INTO message_ (sender, room, message_type, content) VALUES
(2, 2, 'text', 'Anyone seen the new tech updates?'), (2, 2, 'text', 'Anyone seen the new tech updates?'),
(1, 3, 'image', 'Heres a funny meme I found!'), (1, 3, 'image', 'Heres a funny meme I found!'),
(3, 1, 'text', 'I love how active this room is!'); (3, 1, 'text', 'I love how active this room is!');
INSERT INTO friendship_ (user_first, user_second) VALUES
(1, 3), -- Alice and Carol
(2, 3); -- Bob and Carol
INSERT INTO friend_request_ (sender, receiver) VALUES
(2, 1); -- Bob sent a friend request to Alice
INSERT INTO ws_token_ (token, room_id, expires_at) VALUES
('random_token_1', 1, '2025-12-31T23:59:59Z'),
('random_token_2', 2, '2025-12-31T23:59:59Z');

View File

@@ -35,6 +35,14 @@ pub async fn username_from_uuid(
.map_err(|_| (StatusCode::UNAUTHORIZED, String::from("Wrong token"))) .map_err(|_| (StatusCode::UNAUTHORIZED, String::from("Wrong token")))
} }
pub async fn username_from_id(db: &PgPool, user_id: i32) -> Result<String, (StatusCode, String)> {
sqlx::query_scalar("SELECT username FROM user_ WHERE id = $1")
.bind(user_id)
.fetch_one(db)
.await
.map_err(|_| (StatusCode::UNAUTHORIZED, String::from("Wrong token")))
}
pub async fn id_from_username(db: &PgPool, username: String) -> Result<i32, (StatusCode, String)> { pub async fn id_from_username(db: &PgPool, username: String) -> Result<i32, (StatusCode, String)> {
sqlx::query_scalar("SELECT id FROM user_ WHERE username = $1") sqlx::query_scalar("SELECT id FROM user_ WHERE username = $1")
.bind(username) .bind(username)

View File

@@ -6,7 +6,7 @@ use axum::{
use sqlx::PgPool; use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
use crate::db::user_id_from_uuid; use crate::db::{user_id_from_uuid, username_from_id, username_from_uuid};
use crate::{auth::verify_jwt, db::id_from_username}; use crate::{auth::verify_jwt, db::id_from_username};
#[derive(sqlx::FromRow, serde::Serialize)] #[derive(sqlx::FromRow, serde::Serialize)]
@@ -100,7 +100,7 @@ async fn send_request(
headers: HeaderMap, headers: HeaderMap,
Extension(db): Extension<PgPool>, Extension(db): Extension<PgPool>,
Json(payload): Json<SendFriendRequestPayload>, Json(payload): Json<SendFriendRequestPayload>,
) -> Result<StatusCode, (StatusCode, String)> { ) -> Result<(StatusCode, Json<FriendRequest>), (StatusCode, String)> {
let claims = verify_jwt(headers)?; let claims = verify_jwt(headers)?;
let sender_id = user_id_from_uuid(&db, claims.sub).await?; let sender_id = user_id_from_uuid(&db, claims.sub).await?;
@@ -113,6 +113,28 @@ async fn send_request(
)); ));
} }
let is_already_friend = sqlx::query_scalar::<_, bool>(
r#"
SELECT EXISTS (
SELECT 1 FROM friendship_
WHERE (user_first = $1 AND user_second = $2)
OR (user_first = $2 AND user_second = $1)
)
"#,
)
.bind(sender_id)
.bind(receiver_id)
.fetch_one(&db)
.await
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error".into()))?;
if is_already_friend {
return Err((
StatusCode::CONFLICT,
"You are already friends with this user".into(),
));
}
sqlx::query("INSERT INTO friend_request_ (sender, receiver) VALUES ($1, $2)") sqlx::query("INSERT INTO friend_request_ (sender, receiver) VALUES ($1, $2)")
.bind(sender_id) .bind(sender_id)
.bind(receiver_id) .bind(receiver_id)
@@ -120,14 +142,20 @@ async fn send_request(
.await .await
.map_err(|_| (StatusCode::CONFLICT, "Request already exists".into()))?; .map_err(|_| (StatusCode::CONFLICT, "Request already exists".into()))?;
Ok(StatusCode::CREATED) Ok((
StatusCode::CREATED,
Json(FriendRequest {
sender_uuid: claims.sub,
sender_username: username_from_id(&db, receiver_id).await?,
}),
))
} }
async fn accept_request( async fn accept_request(
headers: HeaderMap, headers: HeaderMap,
Extension(db): Extension<PgPool>, Extension(db): Extension<PgPool>,
Json(payload): Json<AcceptFriendRequestPayload>, Json(payload): Json<AcceptFriendRequestPayload>,
) -> Result<StatusCode, (StatusCode, String)> { ) -> Result<(StatusCode, Json<Friend>), (StatusCode, String)> {
let claims = verify_jwt(headers)?; let claims = verify_jwt(headers)?;
let receiver_id = user_id_from_uuid(&db, claims.sub).await?; let receiver_id = user_id_from_uuid(&db, claims.sub).await?;
@@ -144,13 +172,19 @@ async fn accept_request(
.await .await
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))?; .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))?;
let rows = sqlx::query("DELETE FROM friend_request_ WHERE sender = $1 AND receiver = $2") let rows = sqlx::query(
.bind(sender_id) r#"
.bind(receiver_id) DELETE FROM friend_request_
.execute(&mut *tx) WHERE sender = $1 AND receiver = $2
.await OR sender = $2 AND receiver = $1
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))? "#,
.rows_affected(); )
.bind(sender_id)
.bind(receiver_id)
.execute(&mut *tx)
.await
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))?
.rows_affected();
if rows == 0 { if rows == 0 {
return Err((StatusCode::NOT_FOUND, "No such request".into())); return Err((StatusCode::NOT_FOUND, "No such request".into()));
@@ -170,5 +204,11 @@ async fn accept_request(
) )
})?; })?;
Ok(StatusCode::CREATED) Ok((
StatusCode::CREATED,
Json(Friend {
uuid: payload.sender_uuid,
username: username_from_uuid(&db, payload.sender_uuid).await?,
}),
))
} }