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
120 changes: 97 additions & 23 deletions backend/data/blooms.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import datetime

import re
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

from data.connection import db_cursor
from data.users import User
from data.users import get_user


@dataclass
Expand All @@ -13,10 +14,16 @@ class Bloom:
sender: User
content: str
sent_timestamp: datetime.datetime
rebloom_count: int = 0



def add_bloom(*, sender: User, content: str) -> Bloom:
hashtags = [word[1:] for word in content.split(" ") if word.startswith("#")]

if len(content) > 280:
raise ValueError("Bloom content cannot exceed 280 characters")

hashtags = re.findall(r'#(\w+)', content)

now = datetime.datetime.now(tz=datetime.UTC)
bloom_id = int(now.timestamp() * 1000000)
Expand Down Expand Up @@ -54,7 +61,8 @@ def get_blooms_for_user(

cur.execute(
f"""SELECT
blooms.id, users.username, content, send_timestamp
blooms.id, users.username, content, send_timestamp,
COALESCE((SELECT COUNT(*) FROM reblooms r WHERE r.bloom_id = blooms.id), 0) as rebloom_count
FROM
blooms INNER JOIN users ON users.id = blooms.sender_id
WHERE
Expand All @@ -68,15 +76,18 @@ def get_blooms_for_user(
rows = cur.fetchall()
blooms = []
for row in rows:
bloom_id, sender_username, content, timestamp = row
blooms.append(
Bloom(
id=bloom_id,
sender=sender_username,
content=content,
sent_timestamp=timestamp,
)
)
bloom_id, sender_username, content, timestamp, rebloom_count = row
sender_user = get_user(sender_username)
if sender_user:
blooms.append(
Bloom(
id=bloom_id,
sender=sender_user,
content=content,
sent_timestamp=timestamp,
rebloom_count=rebloom_count,
)
)
return blooms


Expand All @@ -90,12 +101,15 @@ def get_bloom(bloom_id: int) -> Optional[Bloom]:
if row is None:
return None
bloom_id, sender_username, content, timestamp = row
return Bloom(
id=bloom_id,
sender=sender_username,
content=content,
sent_timestamp=timestamp,
sender_user = get_user(sender_username)
if sender_user:
return Bloom(
id=bloom_id,
sender=sender_user,
content=content,
sent_timestamp=timestamp,
)
return None


def get_blooms_with_hashtag(
Expand All @@ -122,12 +136,14 @@ def get_blooms_with_hashtag(
blooms = []
for row in rows:
bloom_id, sender_username, content, timestamp = row
blooms.append(
Bloom(
id=bloom_id,
sender=sender_username,
content=content,
sent_timestamp=timestamp,
sender_user = get_user(sender_username)
if sender_user:
blooms.append(
Bloom(
id=bloom_id,
sender=sender_user,
content=content,
sent_timestamp=timestamp,
)
)
return blooms
Expand All @@ -140,3 +156,61 @@ def make_limit_clause(limit: Optional[int], kwargs: Dict[Any, Any]) -> str:
else:
limit_clause = ""
return limit_clause

#rebloom function

def add_rebloom(*, user: User, bloom_id: int) -> None:
"""Adds a rebloom for a user."""
with db_cursor() as cur:
cur.execute(
"""
INSERT INTO reblooms (user_id, bloom_id)
VALUES (%(user_id)s, %(bloom_id)s)
ON CONFLICT (user_id, bloom_id) DO NOTHING
""",
dict(user_id=user.id, bloom_id=bloom_id),
)


def get_rebloom_count(bloom_id: int) -> int:
"""Returns the number of times a bloom has been rebloomed."""
with db_cursor() as cur:
cur.execute(
"SELECT COUNT(*) FROM reblooms WHERE bloom_id = %s",
(bloom_id,),
)
count = cur.fetchone()[0]
return count

def get_user_reblooms(username: str, limit: Optional[int] = 50) -> List[Bloom]:
from data.users import get_user
with db_cursor() as cur:
kwargs = {"username": username}
limit_clause = make_limit_clause(limit, kwargs)

cur.execute(f"""
SELECT DISTINCT
blooms.id, users.username as sender_username, blooms.content, blooms.send_timestamp,
COALESCE((SELECT COUNT(*) FROM reblooms r WHERE r.bloom_id = blooms.id), 0) as rebloom_count
FROM reblooms r
INNER JOIN blooms ON r.bloom_id = blooms.id
INNER JOIN users ON blooms.sender_id = users.id
WHERE r.user_id = (SELECT id FROM users WHERE username = %(username)s)
ORDER BY blooms.send_timestamp DESC
{limit_clause}
""", kwargs)

rows = cur.fetchall()
reblooms = []
for row in rows:
bloom_id, sender_username, content, timestamp, rebloom_count = row
sender_user = get_user(sender_username)
if sender_user:
reblooms.append(Bloom(
id=bloom_id,
sender=sender_user,
content=content,
sent_timestamp=timestamp,
rebloom_count=rebloom_count,
))
return reblooms
101 changes: 66 additions & 35 deletions backend/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Dict, Union
from data import blooms
from data.blooms import get_user_reblooms
from data.follows import follow, 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
Expand Down Expand Up @@ -112,18 +114,21 @@ def other_profile(profile_username):
followers = get_inverse_followed_usernames(profile_user)
all_blooms = blooms.get_blooms_for_user(profile_username)
all_blooms.reverse()
return jsonify(
{
"username": profile_username,
"recent_blooms": all_blooms[:10],
"follows": get_followed_usernames(profile_user),
"followers": list(followers),
"is_following": current_user is not None
and current_user.username in followers,
"is_self": current_user is not None
and current_user.username == profile_username,
"total_blooms": len(all_blooms),
}
return jsonify({
"username": profile_username,
"recent_blooms": [{
"id": b.id,
"sender": {"id": b.sender.id, "username": b.sender.username},
"content": b.content,
"sent_timestamp": b.sent_timestamp.isoformat(),
"rebloom_count": getattr(b, 'rebloom_count', 0)
} for b in all_blooms[:10]],
"follows": get_followed_usernames(profile_user),
"followers": list(followers),
"is_following": current_user is not None and current_user.username in followers,
"is_self": current_user is not None and current_user.username == profile_username,
"total_blooms": len(all_blooms),
}
)


Expand Down Expand Up @@ -177,39 +182,65 @@ def get_bloom(id_str):
return make_response((f"Bloom not found", 404))
return jsonify(bloom)

@jwt_required()
def rebloom():
type_check_error = verify_request_fields({"bloom_id": int})
if type_check_error is not None:
return type_check_error

current_user = get_current_user()
bloom_id = request.json["bloom_id"]

# Add the rebloom
blooms.add_rebloom(user=current_user, bloom_id=bloom_id)

#return new rebloom count
count = blooms.get_rebloom_count(bloom_id)

return jsonify({
"success": True,
"rebloom_count": count
})

def user_blooms(profile_username):
"""Get all blooms for user profile"""
user_blooms_list = blooms.get_blooms_for_user(profile_username)
user_blooms_list.reverse()
return jsonify(user_blooms_list)

@jwt_required()
def home_timeline():
current_user = get_current_user()

# Get blooms from followed users

# 1. Get reblooms
user_reblooms = blooms.get_user_reblooms(current_user.username, limit=50)

# 2. Get blooms from followed users
followed_users = get_followed_usernames(current_user)
nested_user_blooms = [
blooms.get_blooms_for_user(followed_user, limit=50)
for followed_user in followed_users
]

# Flatten list of blooms from followed users
followed_blooms = [bloom for blooms in nested_user_blooms for bloom in blooms]

# Get the current user's own blooms
# 3. Flatten followed blooms
followed_blooms = [bloom for blooms_list in nested_user_blooms for bloom in blooms_list]
# 4. Get original 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

# Sort by timestamp (newest first)
sorted_blooms = list(
sorted(all_blooms, key=lambda bloom: bloom.sent_timestamp, reverse=True)
)

return jsonify(sorted_blooms)


def user_blooms(profile_username):
user_blooms = blooms.get_blooms_for_user(profile_username)
user_blooms.reverse()
return jsonify(user_blooms)

# 5. COMBINE: followed + own + reblooms
all_blooms = followed_blooms + own_blooms + user_reblooms

# 6. Sort newest first
sorted_blooms = sorted(all_blooms, key=lambda b: b.sent_timestamp, reverse=True)

return jsonify([{
"id": b.id,
"sender": {"id": b.sender.id, "username": b.sender.username},
"content": b.content,
"sent_timestamp": b.sent_timestamp.isoformat(),
"rebloom_count": b.rebloom_count
} for b in sorted_blooms])


@jwt_required()
Expand Down
6 changes: 5 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
home_timeline,
login,
other_profile,
rebloom,
register,
self_profile,
send_bloom,
Expand Down Expand Up @@ -58,8 +59,11 @@ def main():

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)
app.add_url_rule("/reblooms", methods=["POST"], view_func=rebloom)
app.add_url_rule("/blooms/<profile_username>", view_func=user_blooms)



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

Expand Down
42 changes: 42 additions & 0 deletions backend/node_modules/.package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions backend/node_modules/cors/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading