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
116 changes: 116 additions & 0 deletions backend/data/blooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class Bloom:
content: str
sent_timestamp: datetime.datetime

rebloomer: Optional[str] = None # new optional field
rebloom_count: int = 0
last_rebloomed_at: Optional[datetime.datetime] = None


def add_bloom(*, sender: User, content: str) -> Bloom:
hashtags = [word[1:] for word in content.split(" ") if word.startswith("#")]
Expand Down Expand Up @@ -54,11 +58,24 @@ def get_blooms_for_user(

cur.execute(
f"""SELECT

blooms.id, users.username, content, send_timestamp
FROM
blooms INNER JOIN users ON users.id = blooms.sender_id
WHERE
username = %(sender_username)s

blooms.id, users.username, content, send_timestamp,
COUNT(r.id) AS rebloom_count,
MAX(r.rebloomed_at) AS last_rebloomed_at
FROM blooms
INNER JOIN users ON users.id = blooms.sender_id
LEFT JOIN reblooms r ON r.original_bloom_id = blooms.id
WHERE
username = %(sender_username)s
GROUP BY
blooms.id, users.username, content, send_timestamp

{before_clause}
ORDER BY send_timestamp DESC
{limit_clause}
Expand All @@ -68,18 +85,103 @@ def get_blooms_for_user(
rows = cur.fetchall()
blooms = []
for row in rows:

bloom_id, sender_username, content, timestamp = row

bloom_id, sender_username, content, timestamp , rebloom_count, last_rebloomed_at= row

blooms.append(
Bloom(
id=bloom_id,
sender=sender_username,
content=content,
sent_timestamp=timestamp,

rebloom_count=rebloom_count,
last_rebloomed_at=last_rebloomed_at,

)
)
return blooms


# Fetch all reblooms made by a user
# returning them as Bloom objects with original content
# but marked as rebloomed by the user
def get_reblooms_for_user(
username: str,
*,
before: Optional[datetime.datetime] = None,
limit: Optional[int] = None,
) -> List[Bloom]:
with db_cursor() as cur:
kwargs = {
"username": username,
}

if before is not None:
before_clause = "AND r.rebloomed_at < %(before_limit)s"
kwargs["before_limit"] = before
else:
before_clause = ""

limit_clause = make_limit_clause(limit, kwargs)

cur.execute(
f"""
SELECT
b.id,
us.username AS original_sender,
b.content,
r.rebloomed_at,
ur.username AS rebloomer_username,
rc.rebloom_count,
rc.last_rebloomed_at
FROM
reblooms r
INNER JOIN blooms b ON r.original_bloom_id = b.id
INNER JOIN users ur ON r.rebloomed_by = ur.id
INNER JOIN users us ON b.sender_id = us.id
INNER JOIN (
SELECT
original_bloom_id,
COUNT(*) AS rebloom_count,
MAX(rebloomed_at) AS last_rebloomed_at
FROM reblooms
GROUP BY original_bloom_id
) rc ON rc.original_bloom_id = b.id
WHERE
ur.username = %(username)s
{before_clause}
ORDER BY
r.rebloomed_at DESC
{limit_clause}

""",
kwargs,
)

rows = cur.fetchall()
reblooms = []

for row in rows:
bloom_id, original_sender, content, timestamp, rebloomer_username , rebloom_count, last_rebloomed_at = row
reblooms.append(
Bloom(
id=bloom_id,
sender=original_sender, # the user who performed the rebloom
content=content, # content of the original bloom
sent_timestamp=timestamp, # time when the rebloom happened
rebloomer=rebloomer_username , # user who rebloomed
rebloom_count=rebloom_count,
last_rebloomed_at=last_rebloomed_at
)
)

return reblooms



def get_bloom(bloom_id: int) -> Optional[Bloom]:
with db_cursor() as cur:
cur.execute(
Expand Down Expand Up @@ -140,3 +242,17 @@ def make_limit_clause(limit: Optional[int], kwargs: Dict[Any, Any]) -> str:
else:
limit_clause = ""
return limit_clause


#Return True if the user has already rebloomed this bloom.
def has_user_rebloomed(original_bloom_id: int, user_id: int) -> bool :
with db_cursor() as cur :
cur.execute("select 1 from reblooms where original_bloom_id=%s and rebloomed_by=%s",(original_bloom_id,user_id))
return cur.fetchone() is not None

#Insert a rebloom record and return its id.
def add_rebloom(original_bloom_id: int, user_id: int) -> int :
with db_cursor() as cur :
cur.execute("insert into reblooms (original_bloom_id, rebloomed_by) values(%s, %s) returning id",(original_bloom_id, user_id))
return cur.fetchone()[0]

7 changes: 7 additions & 0 deletions backend/data/follows.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +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 = %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."""
with db_cursor() as cur:
Expand Down
80 changes: 78 additions & 2 deletions backend/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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,
get_user,
register_user,
)


from flask import Response, jsonify, make_response, request
from flask_jwt_extended import (
create_access_token,
Expand All @@ -17,6 +18,9 @@

from datetime import timedelta

from data.blooms import get_reblooms_for_user


MINIMUM_PASSWORD_LENGTH = 5


Expand Down Expand Up @@ -149,6 +153,24 @@ def do_follow():
}
)

@jwt_required()
def do_unfollow():
type_check_error = verify_request_fields({"follow_username": str})
if type_check_error is not None:
return type_check_error

current_user = get_current_user()
follow_username = request.json["follow_username"]
follow_user = get_user(follow_username)
if follow_user is None:
return make_response(
(f"Cannot unfollow {follow_username} - user does not exist", 404)
)

# Delete record from table follows
unfollow(current_user, follow_user)

return jsonify({"success": True})

@jwt_required()
def send_bloom():
Expand All @@ -167,6 +189,41 @@ def send_bloom():
)


@jwt_required()
def send_rebloom(original_bloom_id):
user = get_current_user()

bloom = blooms.get_bloom(original_bloom_id)
if bloom is None:
return make_response((f"Bloom not found", 404))

# Check if already rebloomed
if blooms.has_user_rebloomed(original_bloom_id, user.id):
return make_response(({"success": False, "message": "Already rebloomed"}, 400))

# Add rebloom
rebloom_id = blooms.add_rebloom(original_bloom_id, user.id)

return jsonify({"success": True, "rebloom_id": rebloom_id})

from data.blooms import get_blooms_for_user
@jwt_required()
def get_reblooms_for_user_endpoint(username):
reblooms = get_reblooms_for_user(username)
return jsonify([
{
"id": r.id,
"sender": r.sender,
"content": r.content,
"sent_timestamp": str(r.sent_timestamp),
"rebloomer": r.rebloomer, # <-- include the new field
"rebloom_count": r.rebloom_count,
"last_rebloomed_at": str(r.last_rebloomed_at) if r.last_rebloomed_at else None
}
for r in reblooms
])


def get_bloom(id_str):
try:
id_int = int(id_str)
Expand Down Expand Up @@ -195,6 +252,7 @@ def home_timeline():
# Get the current user's own blooms
own_blooms = blooms.get_blooms_for_user(current_user.username, limit=50)


# Combine own blooms with followed blooms
all_blooms = followed_blooms + own_blooms

Expand All @@ -203,6 +261,24 @@ def home_timeline():
sorted(all_blooms, key=lambda bloom: bloom.sent_timestamp, reverse=True)
)


all_blooms_dict = {bloom.id: bloom for bloom in followed_blooms + own_blooms}

# Fetch reblooms for the followed users
all_reblooms = []
for user in followed_users + [current_user.username]:
all_reblooms.extend(get_reblooms_for_user(user))

for r in all_reblooms:
if r.id in all_blooms_dict:
all_blooms_dict[r.id].rebloomer = r.rebloomer
all_blooms_dict[r.id].rebloom_count = r.rebloom_count
all_blooms_dict[r.id].last_rebloomed_at = r.last_rebloomed_at


# Sort by timestamp (newest first)
sorted_blooms =sorted(all_blooms_dict.values(), key=lambda b: b.sent_timestamp, reverse=True)

return jsonify(sorted_blooms)


Expand All @@ -220,13 +296,13 @@ def suggested_follows(limit_str):
return make_response((f"Invalid limit", 400))

current_user = get_current_user()

suggestions = [
{"username": username}
for username in get_suggested_follows(current_user, limit_int)
]
return jsonify(suggestions)



def hashtag(hashtag):
return jsonify(blooms.get_blooms_with_hashtag(hashtag))
Expand Down
18 changes: 18 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from data.users import lookup_user
from endpoints import (
do_follow,
do_unfollow,
get_bloom,
hashtag,
home_timeline,
Expand All @@ -12,6 +13,9 @@
register,
self_profile,
send_bloom,

send_rebloom,

suggested_follows,
user_blooms,
)
Expand Down Expand Up @@ -42,6 +46,9 @@ def main():
},
)


from endpoints import get_reblooms_for_user_endpoint

app.config["JWT_SECRET_KEY"] = os.environ["JWT_SECRET_KEY"]
jwt = JWTManager(app)
jwt.user_lookup_loader(lookup_user)
Expand All @@ -54,13 +61,24 @@ 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)
app.add_url_rule("/bloom/<id_str>", methods=["GET"], view_func=get_bloom)
app.add_url_rule("/blooms/<profile_username>", view_func=user_blooms)
app.add_url_rule("/hashtag/<hashtag>", view_func=hashtag)


# Add rebloom route
app.add_url_rule("/rebloom/<int:original_bloom_id>", methods=["POST"], view_func=send_rebloom )

# Add route for fetching user reblooms
app.add_url_rule("/reblooms/user/<username>", view_func=get_reblooms_for_user_endpoint)



app.run(host="0.0.0.0", port="3000", debug=True)


Expand Down
10 changes: 10 additions & 0 deletions db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,13 @@ CREATE TABLE hashtags (
bloom_id BIGINT NOT NULL REFERENCES blooms(id),
UNIQUE(hashtag, bloom_id)
);

CREATE TABLE IF NOT EXISTS reblooms (
id BIGSERIAL PRIMARY KEY,
original_bloom_id BIGINT NOT NULL REFERENCES blooms(id),
rebloomed_by INT NOT NULL REFERENCES users(id),
rebloomed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_rebloom_per_user UNIQUE (original_bloom_id, rebloomed_by)
);


Loading