Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions backend/data/follows.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@

from psycopg2.errors import UniqueViolation

# Unfollow

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 follow(follower: User, followee: User):
with db_cursor() as cur:
Expand Down Expand Up @@ -41,3 +53,4 @@ def get_inverse_followed_usernames(followee: User) -> List[str]:
)
rows = cur.fetchall()
return [row[0] for row in rows]

29 changes: 27 additions & 2 deletions backend/endpoints.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -24,6 +24,7 @@ def login():
type_check_error = verify_request_fields({"username": str, "password": str})
if type_check_error is not None:
return type_check_error
assert request.json is not None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit unfriendly: is request.json is None this will probably give a 500 internal server error. Raising an explicit bad request error, with a message, would be better

user = get_user(request.json["username"])
if user is None:
return make_response(({"success": False, "message": "Unknown user"}, 403))
Expand Down Expand Up @@ -150,6 +151,30 @@ def do_follow():
)


# Unfollow
@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})
Expand Down Expand Up @@ -244,4 +269,4 @@ def verify_request_fields(names_to_types: Dict[str, type]) -> Union[Response, No
400,
)
)
return None
return None
3 changes: 2 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os

from endpoints import do_unfollow
from custom_json_provider import CustomJsonProvider
from data.users import lookup_user
from endpoints import (
Expand Down Expand Up @@ -54,6 +54,7 @@ def main():
app.add_url_rule("/profile", view_func=self_profile)
app.add_url_rule("/profile/<profile_username>", 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/<limit_str>", view_func=suggested_follows)

app.add_url_rule("/bloom", methods=["POST"], view_func=send_bloom)
Expand Down
30 changes: 24 additions & 6 deletions front-end/components/profile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@ function createProfile(template, {profileData, whoToFollow, isLoggedIn}) {
const followerCountEl = profileElement.querySelector("[data-follower-count]");
const followButtonEl = profileElement.querySelector("[data-action='follow']");
const whoToFollowContainer = profileElement.querySelector(".profile__who-to-follow");
// Populate with data

usernameEl.querySelector("h2").textContent = profileData.username || "";
usernameEl.setAttribute("href", `/profile/${profileData.username}`);
bloomCountEl.textContent = profileData.total_blooms || 0;
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-action", "follow");
followButtonEl.hidden = profileData.is_self;

if (profileData.is_following) {
followButtonEl.textContent = "Unfollow";
} else {
followButtonEl.textContent = "Follow";
}

// followButtonEl.addEventListener("click", handleFollow);

if (!isLoggedIn) {
followButtonEl.style.display = "none";
}
Expand All @@ -43,11 +53,11 @@ function createProfile(template, {profileData, whoToFollow, isLoggedIn}) {
usernameLink.setAttribute("href", `/profile/${userToFollow.username}`);
const followButton = wtfElement.querySelector("button");
followButton.setAttribute("data-username", userToFollow.username);
followButton.setAttribute("data-action", "follow");
followButton.addEventListener("click", handleFollow);
if (!isLoggedIn) {
followButton.style.display = "none";
}

whoToFollowList.appendChild(wtfElement);
}
} else {
Expand All @@ -62,8 +72,16 @@ async function handleFollow(event) {
const username = button.getAttribute("data-username");
if (!username) return;

await apiService.followUser(username);
if (button.textContent === "Unfollow") {
await apiService.unfollowUser(username);
button.textContent = "Follow";
} else {
await apiService.followUser(username);
button.textContent = "Unfollow";
}

await apiService.getWhoToFollow();
}

export {createProfile, handleFollow};
// export {createProfile, handleFollow};
export {createProfile};
7 changes: 4 additions & 3 deletions front-end/lib/api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,13 @@ 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({follow_username: username}),
});

if (data.success) {
// Update both the unfollowed user's profile and the current user's profile
// Update both the unfollowed user's profile and the current user's profile
await Promise.all([
getProfile(username),
getProfile(state.currentUser),
Expand All @@ -276,7 +277,7 @@ async function unfollowUser(username) {

return data;
} catch (error) {
// Error already handled by _apiRequest
// Error already handled by _apiRequest
return {success: false};
}
}
Expand Down
2 changes: 1 addition & 1 deletion front-end/views/profile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down