diff --git a/backend/data/follows.py b/backend/data/follows.py index a4b6314..93494f7 100644 --- a/backend/data/follows.py +++ b/backend/data/follows.py @@ -20,6 +20,13 @@ def follow(follower: User, followee: User): # Already following - treat as idempotent request. pass +def unfollow(follower: User, followee: User): + """Remove a follow relationship between two users.""" + with db_cursor() as cur: + cur.execute( + "DELETE FROM follows WHERE follower = %s AND followee = %s", + (follower.id, followee.id), + ) def get_followed_usernames(follower: User) -> List[str]: """get_followed_usernames returns a list of usernames followee follows.""" diff --git a/backend/endpoints.py b/backend/endpoints.py index 0e177a0..e8d005f 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,23 @@ def do_follow(): ) +@jwt_required() +def do_unfollow(unfollow_username): + current_user = get_current_user() + 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}) diff --git a/backend/main.py b/backend/main.py index 7ba155f..db3e93a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -14,6 +14,7 @@ send_bloom, suggested_follows, user_blooms, + do_unfollow, ) from dotenv import load_dotenv @@ -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) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index ec4f200..aff2e21 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -1,4 +1,4 @@ -import {apiService} from "../index.mjs"; +import { apiService } from "../index.mjs"; /** * Create a profile component @@ -27,8 +27,16 @@ 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.hidden = profileData.is_self; + + // Set button text and action based on follow status + if (profileData.is_following) { + followButtonEl.textContent = "Unfollow"; + followButtonEl.addEventListener("click", handleUnfollow); + } else { + followButtonEl.textContent = "Follow"; + followButtonEl.addEventListener("click", handleFollow); + } if (!isLoggedIn) { followButtonEl.style.display = "none"; } @@ -43,7 +51,13 @@ function createProfile(template, {profileData, whoToFollow, isLoggedIn}) { usernameLink.setAttribute("href", `/profile/${userToFollow.username}`); const followButton = wtfElement.querySelector("button"); followButton.setAttribute("data-username", userToFollow.username); - followButton.addEventListener("click", handleFollow); + if (userToFollow.is_following) { + followButton.textContent = "Unfollow"; + followButton.addEventListener("click", handleUnfollow); + } else { + followButton.textContent = "Follow"; + followButton.addEventListener("click", handleFollow); + } if (!isLoggedIn) { followButton.style.display = "none"; } @@ -66,4 +80,13 @@ 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; + + await apiService.unfollowUser(username); + await apiService.getWhoToFollow(); +} + +export {createProfile, handleFollow, handleUnfollow}; diff --git a/front-end/index.html b/front-end/index.html index 89d6b13..b1793d4 100644 --- a/front-end/index.html +++ b/front-end/index.html @@ -4,14 +4,14 @@ Purple Forest - +

Purple Forest Share a Bloom

Please enable JavaScript in your browser.

- + diff --git a/front-end/lib/api.mjs b/front-end/lib/api.mjs index f4b5339..e15244c 100644 --- a/front-end/lib/api.mjs +++ b/front-end/lib/api.mjs @@ -261,7 +261,7 @@ async function followUser(username) { async function unfollowUser(username) { try { - const data = await _apiRequest(`/unfollow/${username}`, { + const data = await _apiRequest("/unfollow", { method: "POST", });