From 34fc48e1a5c4a4b45f68dd7e64dfde9dda36d981 Mon Sep 17 00:00:00 2001 From: ZabihollahNamazi Date: Mon, 25 May 2026 14:03:32 +0100 Subject: [PATCH] unfollow --- backend/.gitignore | 2 +- backend/data/follows.py | 9 +++++++++ backend/endpoints.py | 24 ++++++++++++++++++++++++ backend/main.py | 2 ++ front-end/components/profile.mjs | 31 ++++++++++++++++++++++++++++--- 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/backend/.gitignore b/backend/.gitignore index 30a34276..ac6bb9d3 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,3 @@ /.env -/.venv/ +/venv/ *.pyc diff --git a/backend/data/follows.py b/backend/data/follows.py index a4b6314e..55fc7dd0 100644 --- a/backend/data/follows.py +++ b/backend/data/follows.py @@ -41,3 +41,12 @@ def get_inverse_followed_usernames(followee: User) -> List[str]: ) rows = cur.fetchall() return [row[0] for row in rows] + +def unfollow(follower: User, followee: User) -> None: + """Removes a follow relationship from the database.""" + with db_cursor() as cur: + cur.execute( + """DELETE FROM follows + WHERE follower = %(follower_id)s AND followee = %(followee_id)s""", + dict(follower_id=follower.id, followee_id=followee.id), + ) diff --git a/backend/endpoints.py b/backend/endpoints.py index 0e177a07..a01e06a6 100644 --- a/backend/endpoints.py +++ b/backend/endpoints.py @@ -149,6 +149,30 @@ def do_follow(): } ) +@jwt_required() +def do_unfollow(unfollow_username): + """Endpoint to handle unfollowing a user via URL path variable.""" + current_user = get_current_user() + + # Prevent users from managing rules on themselves + if current_user.username == unfollow_username: + return make_response(({"success": False, "message": "You cannot unfollow yourself"}, 400)) + + unfollow_user = get_user(unfollow_username) + if unfollow_user is None: + return make_response( + ({"success": False, "message": f"User {unfollow_username} does not exist"}, 404) + ) + + # Execute database deletion command + from data.follows import unfollow as db_unfollow + db_unfollow(current_user, unfollow_user) + + return jsonify( + { + "success": True, + } + ) @jwt_required() def send_bloom(): diff --git a/backend/main.py b/backend/main.py index 7ba155fa..fbbd089f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,6 +4,7 @@ from data.users import lookup_user from endpoints import ( do_follow, + do_unfollow, get_bloom, hashtag, home_timeline, @@ -54,6 +55,7 @@ def main(): app.add_url_rule("/profile", view_func=self_profile) app.add_url_rule("/profile/", view_func=other_profile) app.add_url_rule("/follow", methods=["POST"], view_func=do_follow) + app.add_url_rule("/api/unfollow/", view_func=do_unfollow, methods=["POST"]) app.add_url_rule("/suggested-follows/", view_func=suggested_follows) app.add_url_rule("/bloom", methods=["POST"], view_func=send_bloom) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index ec4f2009..03fca344 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -27,8 +27,23 @@ function createProfile(template, {profileData, whoToFollow, isLoggedIn}) { followerCountEl.textContent = profileData.followers?.length || 0; followingCountEl.textContent = profileData.follows?.length || 0; followButtonEl.setAttribute("data-username", profileData.username || ""); - followButtonEl.hidden = profileData.is_self || profileData.is_following; - followButtonEl.addEventListener("click", handleFollow); + if (profileData.is_self) { + // Hide the button completely on your own profile page + followButtonEl.hidden = true; + } else if (profileData.is_following) { + // If already following, transform it into an Unfollow button + followButtonEl.hidden = false; + followButtonEl.textContent = "Unfollow"; + followButtonEl.setAttribute("data-action", "unfollow"); + followButtonEl.addEventListener("click", handleUnfollow); // Attach the new delete listener + } else { + // Standard Follow button setup + followButtonEl.hidden = false; + followButtonEl.textContent = "Follow"; + followButtonEl.setAttribute("data-action", "follow"); + followButtonEl.addEventListener("click", handleFollow); + } + if (!isLoggedIn) { followButtonEl.style.display = "none"; } @@ -66,4 +81,14 @@ async function handleFollow(event) { await apiService.getWhoToFollow(); } -export {createProfile, handleFollow}; +async function handleUnfollow(event) { + const button = event.target; + const username = button.getAttribute("data-username"); + if (!username) return; + + // Triggers the apiService method we verified earlier + await apiService.unfollowUser(username); + await apiService.getWhoToFollow(); +} + +export {createProfile, handleFollow, handleUnfollow};