From d1de0583ed2966a43c7263dd20cb2c905d721675 Mon Sep 17 00:00:00 2001 From: eiiko6 Date: Thu, 18 Dec 2025 13:36:03 +0100 Subject: [PATCH] added frontend for friendships, and fixed logic around conflicts --- db/init.sql | 2 +- db/mock_data.sql | 11 ++++++++ src/db.rs | 8 ++++++ src/routes/friends.rs | 64 +++++++++++++++++++++++++++++++++++-------- 4 files changed, 72 insertions(+), 13 deletions(-) diff --git a/db/init.sql b/db/init.sql index f09b1c5..3c47a56 100644 --- a/db/init.sql +++ b/db/init.sql @@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS friendship_ ( user_first 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(), - PRIMARY KEY (user_first, user_second), + PRIMARY KEY (user_first, user_second) ); CREATE TABLE IF NOT EXISTS friend_request_ ( diff --git a/db/mock_data.sql b/db/mock_data.sql index 1e08fb7..d9ae68e 100644 --- a/db/mock_data.sql +++ b/db/mock_data.sql @@ -21,3 +21,14 @@ INSERT INTO message_ (sender, room, message_type, content) VALUES (2, 2, 'text', 'Anyone seen the new tech updates?'), (1, 3, 'image', 'Heres a funny meme I found!'), (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'); diff --git a/src/db.rs b/src/db.rs index 1faff3c..4f1011f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -35,6 +35,14 @@ pub async fn username_from_uuid( .map_err(|_| (StatusCode::UNAUTHORIZED, String::from("Wrong token"))) } +pub async fn username_from_id(db: &PgPool, user_id: i32) -> Result { + 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 { sqlx::query_scalar("SELECT id FROM user_ WHERE username = $1") .bind(username) diff --git a/src/routes/friends.rs b/src/routes/friends.rs index 144d12c..0b46d7a 100644 --- a/src/routes/friends.rs +++ b/src/routes/friends.rs @@ -6,7 +6,7 @@ use axum::{ use sqlx::PgPool; 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}; #[derive(sqlx::FromRow, serde::Serialize)] @@ -100,7 +100,7 @@ async fn send_request( headers: HeaderMap, Extension(db): Extension, Json(payload): Json, -) -> Result { +) -> Result<(StatusCode, Json), (StatusCode, String)> { let claims = verify_jwt(headers)?; 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)") .bind(sender_id) .bind(receiver_id) @@ -120,14 +142,20 @@ async fn send_request( .await .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( headers: HeaderMap, Extension(db): Extension, Json(payload): Json, -) -> Result { +) -> Result<(StatusCode, Json), (StatusCode, String)> { let claims = verify_jwt(headers)?; let receiver_id = user_id_from_uuid(&db, claims.sub).await?; @@ -144,13 +172,19 @@ async fn accept_request( .await .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))?; - let rows = sqlx::query("DELETE FROM friend_request_ WHERE sender = $1 AND receiver = $2") - .bind(sender_id) - .bind(receiver_id) - .execute(&mut *tx) - .await - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))? - .rows_affected(); + let rows = sqlx::query( + r#" + DELETE FROM friend_request_ + WHERE sender = $1 AND receiver = $2 + OR sender = $2 AND receiver = $1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(&mut *tx) + .await + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error".into()))? + .rows_affected(); if rows == 0 { 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?, + }), + )) }