From d1d411adca43f95090b994ff2b4774dfa9d442b1 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:08:21 +0000 Subject: [PATCH 1/6] Fix: Replace psycopg2 with psycopg2-binary for macOS compatibility and update README --- backend/README.md | 3 ++- backend/requirements.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/README.md b/backend/README.md index 8e31e11..08f0aef 100644 --- a/backend/README.md +++ b/backend/README.md @@ -13,7 +13,8 @@ To run: 3. Activate the virtual environment: `. .venv/bin/activate` 4. Install dependencies: `pip install -r requirements.txt` 5. Run the database: `../db/run.sh` (you must have Docker installed and running). -6. Create the database schema: `../db/create-schema.sh` +6.run the server python3 main.py +7. Create the database schema: `../db/create-schema.sh` You may want to run `python3 populate.py` to populate sample data. diff --git a/backend/requirements.txt b/backend/requirements.txt index e03836c..5e11b05 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -11,7 +11,7 @@ idna==3.10 itsdangerous==2.2.0 Jinja2==3.1.5 MarkupSafe==3.0.2 -psycopg2==2.9.10 +psycopg2-binary==2.9.10 pycparser==2.22 PyJWT==2.10.1 python-dotenv==1.0.1 From 8369928a452c410c3715b5c4512b5ea4a4a08a70 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:57:23 +0000 Subject: [PATCH 2/6] feat: add data-layer unfollow function --- backend/data/follows.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/data/follows.py b/backend/data/follows.py index a4b6314..f9e4c30 100644 --- a/backend/data/follows.py +++ b/backend/data/follows.py @@ -21,6 +21,15 @@ 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", + 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 7d9270adb95700d6aca8868f2be0acee8779eec8 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:43:27 +0000 Subject: [PATCH 3/6] feat: add POST /unfollow/ endpoint --- backend/endpoints.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/backend/endpoints.py b/backend/endpoints.py index 0e177a0..c1853c6 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,23 @@ def do_follow(): ) +@jwt_required() +def do_unfollow(profile_username): + + profile_user = get_user(profile_username) + if profile_user is None: + return make_response((f"Cannot unfollow {profile_username} - user does not exist", 404)) + + current_user = get_current_user() + + + if current_user.username == profile_username: + return make_response(("Cannot unfollow yourself", 400)) + + unfollow(current_user, profile_user) + return jsonify({"success": True}) + + @jwt_required() def send_bloom(): type_check_error = verify_request_fields({"content": str}) From 645d4d2d2fb7d4ccf36bea54a20e3b6ff99c951c Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:49:08 +0000 Subject: [PATCH 4/6] feat(routes): register POST /unfollow/ route --- backend/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/main.py b/backend/main.py index 7ba155f..a8e0721 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 227e4ac66306c4f9386d3d5cc0f75858a8e56e6c Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:38:23 +0000 Subject: [PATCH 5/6] feat(profile): toggle follow/unfollow button and call apiService.unfollowUser --- front-end/components/profile.mjs | 34 +++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs index ec4f200..ccd568d 100644 --- a/front-end/components/profile.mjs +++ b/front-end/components/profile.mjs @@ -27,10 +27,25 @@ 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.style.display = "none"; + + + if (profileData.is_self) { + + followButtonEl.hidden = true; + } else { + followButtonEl.hidden = false; + if (profileData.is_following) { + followButtonEl.textContent = "Unfollow"; + followButtonEl.removeEventListener("click", handleFollow); + followButtonEl.addEventListener("click", handleUnfollow); + } else { + followButtonEl.textContent = "Follow"; + followButtonEl.removeEventListener("click", handleUnfollow); + followButtonEl.addEventListener("click", handleFollow); + } + if (!isLoggedIn) { + followButtonEl.style.display = "none"; + } } if (whoToFollow.length > 0) { @@ -66,4 +81,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}; From fcab6f6ca41d8f1281d44d98d856d9384d944490 Mon Sep 17 00:00:00 2001 From: zohrehKazemianpour <129424353+zohrehKazemianpour@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:57:22 +0000 Subject: [PATCH 6/6] fix error --- backend/data/follows.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/data/follows.py b/backend/data/follows.py index f9e4c30..814cc61 100644 --- a/backend/data/follows.py +++ b/backend/data/follows.py @@ -21,13 +21,13 @@ 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", - dict(follower_id=follower.id, followee_id=followee.id), - ) +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", + dict(follower_id=follower.id, followee_id=followee.id), + ) def get_followed_usernames(follower: User) -> List[str]: