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
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ To run:
5. Run the database: `../db/run.sh` (you must have Docker installed and running).
6. Create the database schema: `../db/create-schema.sh`

You may want to run `python3 populate.py` to populate sample data.
You may want to run `python3 populate.py` to populate sample data. 

If you ever need to wipe the database, just delete `../db/pg_data` (and remember to set it up again after).

Expand Down
60 changes: 52 additions & 8 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,18 +139,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
30 changes: 30 additions & 0 deletions backend/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,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
3 changes: 3 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
send_bloom,
suggested_follows,
user_blooms,
update_rebloom_counter,
send_rebloom,
)

from dotenv import load_dotenv
Expand Down Expand Up @@ -60,6 +62,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
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
40 changes: 37 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,13 @@ function _formatTimestamp(timestamp) {
}
}

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

// await apiService.updateRebloomCounter(id);
await apiService.postRebloom(id);
}

export { createBloom, handleRebloom };
Loading