From 192ea6ffa192d7e4dffee38fe7ada7cfed6b1a75 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Fri, 13 Feb 2026 19:29:15 +0000 Subject: [PATCH] adding an un-follow feature --- backend/data/follows.py | 11 +++++++++++ backend/endpoints.py | 25 ++++++++++++++++++++++++- backend/main.py | 4 +++- front-end/components/profile.mjs | 25 ++++++++++++++++++++++--- front-end/views/profile.mjs | 2 +- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/backend/data/follows.py b/backend/data/follows.py index a4b6314..3c1758e 100644 --- a/backend/data/follows.py +++ b/backend/data/follows.py @@ -21,6 +21,17 @@ 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: diff --git a/backend/endpoints.py b/backend/endpoints.py index 0e177a0..3dc403e 100644 --- a/backend/endpoints.py +++ b/backend/endpoints.py @@ -1,6 +1,11 @@ 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 +155,24 @@ def do_follow(): ) +@jwt_required() +def do_unfollow(follow_username): + current_user = get_current_user() + + follow_user = get_user(follow_username) + if follow_user is None: + return make_response( + (f"Cannot unfollow {follow_username} - user does not exist", 404) + ) + + unfollow(current_user, follow_user) + return jsonify( + { + "success": True, + } + ) + + @jwt_required() def send_bloom(): type_check_error = verify_request_fields({"content": str}) diff --git a/backend/main.py b/backend/main.py index 7ba155f..eab090a 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,7 +55,8 @@ 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("/suggested-follows/", view_func=suggested_follows) + app.add_url_rule("/unfollow/", methods=["POST"], view_func=do_unfollow) + app.add_url_rule("/suggested-follows/", methods=["GET"], 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) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index ec4f200..6711370 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -27,8 +27,13 @@ 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); + followButtonEl.setAttribute( + "data-following", + profileData.is_following ? "true" : "false" + ); + followButtonEl.textContent = profileData.is_following ? "Unfollow" : "Follow"; + followButtonEl.hidden = profileData.is_self; + followButtonEl.addEventListener("click", handleFollowToggle); if (!isLoggedIn) { followButtonEl.style.display = "none"; } @@ -66,4 +71,18 @@ async function handleFollow(event) { await apiService.getWhoToFollow(); } -export {createProfile, handleFollow}; +async function handleFollowToggle(event) { + const button = event.target; + const username = button.getAttribute("data-username"); + if (!username) return; + + const isFollowing = button.getAttribute("data-following") === "true"; + if (isFollowing) { + await apiService.unfollowUser(username); + } else { + await apiService.followUser(username); + } + await apiService.getWhoToFollow(); +} + +export {createProfile, handleFollow, handleFollowToggle}; diff --git a/front-end/views/profile.mjs b/front-end/views/profile.mjs index dd2b92a..0ce0fa0 100644 --- a/front-end/views/profile.mjs +++ b/front-end/views/profile.mjs @@ -9,7 +9,7 @@ import { } from "../index.mjs"; import {createLogin, handleLogin} from "../components/login.mjs"; import {createLogout, handleLogout} from "../components/logout.mjs"; -import {createProfile, handleFollow} from "../components/profile.mjs"; +import {createProfile} from "../components/profile.mjs"; import {createBloom} from "../components/bloom.mjs"; // Profile view - just this person's blooms and their profile