From a1e37c459a89070491fde4e61b91671447c06e10 Mon Sep 17 00:00:00 2001 From: HassanOHOsman Date: Sun, 22 Feb 2026 23:04:05 +0000 Subject: [PATCH 1/6] Build unfollow function to update the database by removing relevant data from the follows table. --- backend/data/follows.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/data/follows.py b/backend/data/follows.py index a4b6314..a1b4048 100644 --- a/backend/data/follows.py +++ b/backend/data/follows.py @@ -21,6 +21,18 @@ def follow(follower: User, followee: User): pass +def unfollow(follower: User, followee: User) -> None: + 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, + ), + ) + + + def get_followed_usernames(follower: User) -> List[str]: """get_followed_usernames returns a list of usernames followee follows.""" with db_cursor() as cur: From de1b547ab508951914c0961263b9ac74be7f8695 Mon Sep 17 00:00:00 2001 From: HassanOHOsman Date: Sun, 22 Feb 2026 23:13:07 +0000 Subject: [PATCH 2/6] add do_unfollow() - that mirros do_follow() - inside endpoints.py so that a frontend can tell the backend when a user wants to unfollow someone --- backend/endpoints.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/endpoints.py b/backend/endpoints.py index 0e177a0..4b4d4d2 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, @@ -150,6 +150,30 @@ 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(): type_check_error = verify_request_fields({"content": str}) From ac68c45a9e7e0891819ad6c9faa4eceeb349a59c Mon Sep 17 00:00:00 2001 From: HassanOHOsman Date: Sun, 22 Feb 2026 23:21:02 +0000 Subject: [PATCH 3/6] Add an endpoint URL for the unfollow functionality. --- backend/main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/main.py b/backend/main.py index 7ba155f..8c24bdd 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,8 +55,12 @@ 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) app.add_url_rule("/bloom/", methods=["GET"], view_func=get_bloom) app.add_url_rule("/blooms/", view_func=user_blooms) From de332cbbc529aa4ea5fde9c2ee550ea12325c7cc Mon Sep 17 00:00:00 2001 From: HassanOHOsman Date: Mon, 23 Feb 2026 11:09:42 +0000 Subject: [PATCH 4/6] async function handleFollow so that a follow/unfloow action is determined by the text in the follow/unfollow btn --- front-end/components/profile.mjs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index ec4f200..de37fc7 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -27,7 +27,7 @@ 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.hidden = profileData.is_self; followButtonEl.addEventListener("click", handleFollow); if (!isLoggedIn) { followButtonEl.style.display = "none"; @@ -62,7 +62,13 @@ async function handleFollow(event) { const username = button.getAttribute("data-username"); if (!username) return; - await apiService.followUser(username); + if (button.textContent === "Follow") { + await apiService.followUser(username); + } else { + await apiService.unfollowUser(username); + } + + await apiService.getWhoToFollow(); } From ac1c498cc15e7f08af50d1c88393a14ee6d6ec89 Mon Sep 17 00:00:00 2001 From: HassanOHOsman Date: Mon, 23 Feb 2026 11:16:34 +0000 Subject: [PATCH 5/6] show "follow" or "unfollow" text in the button depending on the following status of a profile --- front-end/components/profile.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index de37fc7..abd2eac 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -28,6 +28,7 @@ function createProfile(template, {profileData, whoToFollow, isLoggedIn}) { followingCountEl.textContent = profileData.follows?.length || 0; followButtonEl.setAttribute("data-username", profileData.username || ""); followButtonEl.hidden = profileData.is_self; + followButtonEl.textContent = profileData.is_following ? "Unfollow" : "Follow"; followButtonEl.addEventListener("click", handleFollow); if (!isLoggedIn) { followButtonEl.style.display = "none"; From 2f57e4410754d7a0908c560072a7991ca8fb06b5 Mon Sep 17 00:00:00 2001 From: HassanOHOsman Date: Mon, 23 Feb 2026 11:34:52 +0000 Subject: [PATCH 6/6] fix unfollow API endpoint to use POST with JSON body --- front-end/lib/api.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front-end/lib/api.mjs b/front-end/lib/api.mjs index f4b5339..738d918 100644 --- a/front-end/lib/api.mjs +++ b/front-end/lib/api.mjs @@ -261,8 +261,9 @@ 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) {