From c7da59dff9ea53ab760c4b5d7aa82777fae8fa46 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Mon, 9 Feb 2026 12:37:14 +0000 Subject: [PATCH 1/3] Added functionality for unfollowing using follow code template from legacy code-Tested via curl to confirm its working correctly. --- backend/data/follows.py | 15 +++++++++++++++ backend/endpoints.py | 28 +++++++++++++++++++++++++++- backend/main.py | 2 ++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/backend/data/follows.py b/backend/data/follows.py index a4b6314..432c5f2 100644 --- a/backend/data/follows.py +++ b/backend/data/follows.py @@ -21,6 +21,21 @@ def follow(follower: User, followee: User): pass +def unfollow(follower: User, followee: User): + with db_cursor() as cur: + cur.execute( + "DELETE FROM follows WHERE follower = %(follower_id)s AND followee = %(followee_id)s", #delete that follow row + dict( + follower_id=follower.id, + followee_id=followee.id, + ), + ) + + + + + + def get_followed_usernames(follower: User) -> List[str]: """get_followed_usernames returns a list of usernames followee follows.""" with db_cursor() as cur: diff --git a/backend/endpoints.py b/backend/endpoints.py index 0e177a0..9c4ee1e 100644 --- a/backend/endpoints.py +++ b/backend/endpoints.py @@ -1,6 +1,6 @@ from typing import Dict, Union from data import blooms -from data.follows import follow, get_followed_usernames, get_inverse_followed_usernames +from data.follows import follow,unfollow, get_followed_usernames, get_inverse_followed_usernames from data.users import ( UserRegistrationError, get_suggested_follows, @@ -149,6 +149,32 @@ def do_follow(): } ) +@jwt_required() +def do_unfollow(): + type_check_error = verify_request_fields({"unfollow_username": str}) + if type_check_error is not None: + return type_check_error + + current_user = get_current_user() + + unfollow_username = request.json["unfollow_username"] + unfollow_user = get_user(unfollow_username) + if unfollow_user is None: + return make_response( + (f"Cannot unfollow {unfollow_username} - user does not exist", 404) + ) + + 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 7ba155f..a437812 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("/unfollow", methods=["POST"], view_func=do_unfollow) app.add_url_rule("/suggested-follows/", view_func=suggested_follows) app.add_url_rule("/bloom", methods=["POST"], view_func=send_bloom) From 46b61fb50de65449bb4405090f1626109280f36f Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Mon, 9 Feb 2026 13:27:14 +0000 Subject: [PATCH 2/3] Refactor profile follow button to support follow/unfollow toggle --- front-end/components/profile.mjs | 36 +++++++++++++++++++++++++------- front-end/lib/api.mjs | 5 ++++- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index ec4f200..692128d 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -27,20 +27,34 @@ 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 (!isLoggedIn) { + //followButtonEl.hidden = profileData.is_self || profileData.is_following; + + + //follow/unfollow toggle + if (profileData.is_self) { + followButtonEl.hidden = true; + } else { + followButtonEl.hidden = false; + followButtonEl.textContent = profileData.is_following ? "Unfollow" : "Follow"; + + } + +if (!isLoggedIn) { followButtonEl.style.display = "none"; } +followButtonEl.addEventListener("click", handleFollow); + if (whoToFollow.length > 0) { const whoToFollowList = whoToFollowContainer.querySelector("[data-who-to-follow]"); const whoToFollowTemplate = document.querySelector("#who-to-follow-chip"); + for (const userToFollow of whoToFollow) { const wtfElement = whoToFollowTemplate.content.cloneNode(true); const usernameLink = wtfElement.querySelector("a[data-username]"); usernameLink.innerText = userToFollow.username; usernameLink.setAttribute("href", `/profile/${userToFollow.username}`); + const followButton = wtfElement.querySelector("button"); followButton.setAttribute("data-username", userToFollow.username); followButton.addEventListener("click", handleFollow); @@ -62,8 +76,16 @@ async function handleFollow(event) { const username = button.getAttribute("data-username"); if (!username) return; - await apiService.followUser(username); - await apiService.getWhoToFollow(); -} - + const isUnfollowing = button.textContent === "Unfollow"; + if (isUnfollowing) { + await apiService.unfollowUser(username); + button.textContent = "Follow"; + } else { + await apiService.followUser(username); + button.textContent = "Unfollow"; + } + await apiService.getWhoToFollow(); + + await apiService.getProfile(username); + } export {createProfile, handleFollow}; diff --git a/front-end/lib/api.mjs b/front-end/lib/api.mjs index f4b5339..159b48d 100644 --- a/front-end/lib/api.mjs +++ b/front-end/lib/api.mjs @@ -261,9 +261,12 @@ async function followUser(username) { async function unfollowUser(username) { try { - const data = await _apiRequest(`/unfollow/${username}`, { + const data = await _apiRequest("/unfollow", { method: "POST", + body: + JSON.stringify({unfollow_username: username}), }); + if (data.success) { // Update both the unfollowed user's profile and the current user's profile From f271bf3adb3cde297105ba45080752aec97b9d86 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Mon, 9 Feb 2026 13:31:34 +0000 Subject: [PATCH 3/3] Removing this await apiService.getProfile(username); to avoid doube fetching and unneccessary rerenders --- front-end/components/profile.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index 692128d..557e09c 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -86,6 +86,6 @@ async function handleFollow(event) { } await apiService.getWhoToFollow(); - await apiService.getProfile(username); + } export {createProfile, handleFollow};