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

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

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

Expand All @@ -13,21 +11,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 +59,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 +73,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 +97,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 +124,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,18 +137,44 @@ 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"
Expand Down
31 changes: 30 additions & 1 deletion backend/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
get_user,
register_user,
)

from flask import Response, jsonify, make_response, request
from flask_jwt_extended import (
create_access_token,
Expand Down Expand Up @@ -212,6 +211,36 @@ def user_blooms(profile_username):
return jsonify(user_blooms)


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,
}
)


@jwt_required()
def suggested_follows(limit_str):
try:
Expand Down
4 changes: 3 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
send_bloom,
suggested_follows,
user_blooms,
update_rebloom_counter,
send_rebloom,
)

from dotenv import load_dotenv
from flask import Flask
from flask_cors import CORS
Expand Down Expand Up @@ -60,6 +61,7 @@ def main():
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
2 changes: 1 addition & 1 deletion backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
38 changes: 35 additions & 3 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 @@ -30,6 +38,23 @@ const createBloom = (template, bloom) => {
...bloomParser.parseFromString(_formatHashtags(bloom.content), "text/html")
.body.childNodes
);
// redo to "bloom.reblooms || 0" once reblooms implemented to object
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
// I had to write another fetch, because getBloom update state, which is causing recursion if I use it here
.fetchBloomData(bloom.original_bloom_id)
.then((originalBloom) => {
const timeStamp = _formatTimestamp(originalBloom.sent_timestamp);
//I used inner html to render the arrow ↪ sign
rebloomInfoEl.innerHTML = `&#8618; Rebloom of the ${originalBloom.sender}'s post, posted ${timeStamp} ago`;
});
}

return bloomFrag;
};
Expand Down Expand Up @@ -84,4 +109,11 @@ 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