Skip to content
Open
62 changes: 53 additions & 9 deletions backend/data/blooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ class Bloom:
sender: User
content: str
sent_timestamp: datetime.datetime
reblooms: int
original_bloom_id: int


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

now = datetime.datetime.now(tz=datetime.UTC)
bloom_id = int(now.timestamp() * 1000000)
print(original_bloom_id)
with db_cursor() as cur:
cur.execute(
"INSERT INTO blooms (id, sender_id, content, send_timestamp) VALUES (%(bloom_id)s, %(sender_id)s, %(content)s, %(timestamp)s)",
"INSERT INTO blooms (id, sender_id, content, send_timestamp, reblooms, original_bloom_id) VALUES (%(bloom_id)s, %(sender_id)s, %(content)s, %(timestamp)s, %(reblooms)s,%(original_bloom_id)s)",
dict(
bloom_id=bloom_id,
sender_id=sender.id,
content=content,
timestamp=datetime.datetime.now(datetime.UTC),
reblooms=0,
original_bloom_id=original_bloom_id,
),
)
for hashtag in hashtags:
Expand All @@ -54,7 +61,7 @@ def get_blooms_for_user(

cur.execute(
f"""SELECT
blooms.id, users.username, content, send_timestamp
blooms.id, users.username, content, send_timestamp, reblooms, original_bloom_id
FROM
blooms INNER JOIN users ON users.id = blooms.sender_id
WHERE
Expand All @@ -68,13 +75,22 @@ 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,
reblooms,
original_bloom_id,
) = row
blooms.append(
Bloom(
id=bloom_id,
sender=sender_username,
content=content,
sent_timestamp=timestamp,
reblooms=reblooms,
original_bloom_id=original_bloom_id,
)
)
return blooms
Expand All @@ -83,18 +99,20 @@ def get_blooms_for_user(
def get_bloom(bloom_id: int) -> Optional[Bloom]:
with db_cursor() as cur:
cur.execute(
"SELECT blooms.id, users.username, content, send_timestamp FROM blooms INNER JOIN users ON users.id = blooms.sender_id WHERE blooms.id = %s",
"SELECT blooms.id, users.username, content, send_timestamp, reblooms, original_bloom_id FROM blooms INNER JOIN users ON users.id = blooms.sender_id WHERE blooms.id = %s",
(bloom_id,),
)
row = cur.fetchone()
if row is None:
return None
bloom_id, sender_username, content, timestamp = row
bloom_id, sender_username, content, timestamp, reblooms, original_bloom_id = row
return Bloom(
id=bloom_id,
sender=sender_username,
content=content,
sent_timestamp=timestamp,
reblooms=reblooms,
original_bloom_id=original_bloom_id,
)


Expand All @@ -108,7 +126,7 @@ def get_blooms_with_hashtag(
with db_cursor() as cur:
cur.execute(
f"""SELECT
blooms.id, users.username, content, send_timestamp
blooms.id, users.username, content, send_timestamp, reblooms, original_bloom_id
FROM
blooms INNER JOIN hashtags ON blooms.id = hashtags.bloom_id INNER JOIN users ON blooms.sender_id = users.id
WHERE
Expand All @@ -121,22 +139,48 @@ def get_blooms_with_hashtag(
rows = cur.fetchall()
blooms = []
for row in rows:
bloom_id, sender_username, content, timestamp = row
(
bloom_id,
sender_username,
content,
timestamp,
reblooms,
original_bloom_id,
) = row
blooms.append(
Bloom(
id=bloom_id,
sender=sender_username,
content=content,
sent_timestamp=timestamp,
reblooms=reblooms,
original_bloom_id=original_bloom_id,
)
)
return blooms


def update_rebloom_counter(bloom_id: int) -> None:
with db_cursor() as cur:
cur.execute(
"UPDATE blooms SET reblooms = reblooms + 1 WHERE blooms.id = %s",
(bloom_id,),
)


def add_rebloom(*, sender: User, id: int) -> None:
original_bloom = get_bloom(id)
if not original_bloom:
return None
content = original_bloom.content
update_rebloom_counter(id)
add_bloom(sender=sender, content=content, original_bloom_id=id)


def make_limit_clause(limit: Optional[int], kwargs: Dict[Any, Any]) -> str:
if limit is not None:
limit_clause = "LIMIT %(limit)s"
kwargs["limit"] = limit
else:
limit_clause = ""
return limit_clause
return limit_clause
9 changes: 9 additions & 0 deletions backend/data/follows.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ def follow(follower: User, followee: User):
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."""
with db_cursor() as cur:
Expand Down
50 changes: 49 additions & 1 deletion 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, get_followed_usernames, get_inverse_followed_usernames, unfollow
from data.users import (
UserRegistrationError,
get_suggested_follows,
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -228,6 +245,37 @@ def suggested_follows(limit_str):
return jsonify(suggestions)


def update_rebloom_counter(bloom_id):
try:
id_int = int(bloom_id)
except ValueError:
return make_response((f"Invalid bloom id", 400))
blooms.update_rebloom_counter(id_int)
return jsonify(
{
"success": True,
}
)


@jwt_required()
def send_rebloom():
user = get_current_user()
bloom_id = request.json["id"]
try:
id_int = int(bloom_id)
except ValueError:
return make_response((f"Invalid bloom id", 400))
blooms.add_rebloom(sender=user, id=id_int)

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



def hashtag(hashtag):
return jsonify(blooms.get_blooms_with_hashtag(hashtag))

Expand Down
7 changes: 7 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 @@ -14,6 +15,8 @@
send_bloom,
suggested_follows,
user_blooms,
update_rebloom_counter,
send_rebloom,
)

from dotenv import load_dotenv
Expand Down Expand Up @@ -54,12 +57,16 @@ 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/<unfollow_username>", 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)
app.add_url_rule("/rebloom", methods=["POST"], view_func=send_rebloom)

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

Expand Down
4 changes: 3 additions & 1 deletion db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ CREATE TABLE blooms (
id BIGSERIAL NOT NULL PRIMARY KEY,
sender_id INT NOT NULL REFERENCES users(id),
content TEXT NOT NULL,
send_timestamp TIMESTAMP NOT NULL
send_timestamp TIMESTAMP NOT NULL,
reblooms INT NOT NULL DEFAULT 0,
original_bloom_id BIGINT REFERENCES blooms(id)
);

CREATE TABLE follows (
Expand Down
48 changes: 41 additions & 7 deletions front-end/components/bloom.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { apiService } from "../index.mjs";

/**
* Create a bloom component
* @param {string} template - The ID of the template to clone
Expand All @@ -7,7 +9,9 @@
* {"id": Number,
* "sender": username,
* "content": "string from textarea",
* "sent_timestamp": "datetime as ISO 8601 formatted string"}
* "sent_timestamp": "datetime as ISO 8601 formatted string"},
* "reblooms": "reblooms count",
* "original_bloom_id": "id of the rebloomed post"

*/
const createBloom = (template, bloom) => {
Expand All @@ -20,8 +24,12 @@ const createBloom = (template, bloom) => {
const bloomTime = bloomFrag.querySelector("[data-time]");
const bloomTimeLink = bloomFrag.querySelector("a:has(> [data-time])");
const bloomContent = bloomFrag.querySelector("[data-content]");
const rebloomButtonEl = bloomFrag.querySelector(
"[data-action='share-bloom']"
);
const rebloomCountEl = bloomFrag.querySelector("[data-rebloom-count]");
const rebloomInfoEl = bloomFrag.querySelector("[data-rebloom-info]");

bloomArticle.setAttribute("data-bloom-id", bloom.id);
bloomUsername.setAttribute("href", `/profile/${bloom.sender}`);
bloomUsername.textContent = bloom.sender;
bloomTime.textContent = _formatTimestamp(bloom.sent_timestamp);
Expand All @@ -31,6 +39,23 @@ const createBloom = (template, bloom) => {
.body.childNodes
);

rebloomCountEl.textContent = `Rebloomed ${bloom.reblooms} times`;
rebloomCountEl.hidden = bloom.reblooms == 0;
rebloomButtonEl.setAttribute("data-id", bloom.id || "");
rebloomButtonEl.addEventListener("click", handleRebloom);
rebloomInfoEl.hidden = bloom.original_bloom_id === null;

if (bloom.original_bloom_id !== null) {
apiService

.fetchBloomData(bloom.original_bloom_id)
.then((originalBloom) => {
const timeStamp = _formatTimestamp(originalBloom.sent_timestamp);

rebloomInfoEl.innerHTML = `&#8618; Rebloom of the ${originalBloom.sender}'s post, posted ${timeStamp} ago`;
});
}

return bloomFrag;
};

Expand All @@ -55,25 +80,22 @@ function _formatTimestamp(timestamp) {
return `${diffSeconds}s`;
}

// Less than an hour
const diffMinutes = Math.floor(diffSeconds / 60);
if (diffMinutes < 60) {
return `${diffMinutes}m`;
}

// Less than a day

const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) {
return `${diffHours}h`;
}

// Less than a week
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 7) {
return `${diffDays}d`;
}

// Format as month and day for older dates
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
Expand All @@ -84,4 +106,16 @@ function _formatTimestamp(timestamp) {
}
}

export {createBloom};
async function handleRebloom(event) {
const button = event.target;
const id = button.getAttribute("data-id");
if (!id) return;

await apiService.postRebloom(id);
}

export { createBloom, handleRebloom };




Loading