From 06b336d57db6da6c17e83acdaed2b9f0f918d1a3 Mon Sep 17 00:00:00 2001 From: ZabihollahNamazi Date: Sun, 24 May 2026 14:26:42 +0100 Subject: [PATCH 1/5] async an await for # --- front-end/views/hashtag.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index 7b7e996..d5dec39 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -14,10 +14,13 @@ import {createHeading} from "../components/heading.mjs"; // Hashtag view: show all tweets containing this tag -function hashtagView(hashtag) { +async function hashtagView(hashtag) { destroy(); - apiService.getBloomsByHashtag(hashtag); + const normalizedHashtag = hashtag.startsWith("#") ? hashtag : `#${hashtag}`; + if (state.currentHashtag !== normalizedHashtag) { + await apiService.getBloomsByHashtag(hashtag); + } renderOne( state.isLoggedIn, From 8c2a677560b32e0f432148092e3180291d6708d3 Mon Sep 17 00:00:00 2001 From: ZabihollahNamazi Date: Sun, 24 May 2026 15:05:57 +0100 Subject: [PATCH 2/5] adding timezone.utc to be able to work with python 3.9 --- backend/data/blooms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/data/blooms.py b/backend/data/blooms.py index 7e280cf..d9082e5 100644 --- a/backend/data/blooms.py +++ b/backend/data/blooms.py @@ -18,7 +18,7 @@ class Bloom: def add_bloom(*, sender: User, content: str) -> Bloom: hashtags = [word[1:] for word in content.split(" ") if word.startswith("#")] - now = datetime.datetime.now(tz=datetime.UTC) + now = datetime.datetime.now(tz=datetime.timezone.utc) bloom_id = int(now.timestamp() * 1000000) with db_cursor() as cur: cur.execute( @@ -27,7 +27,7 @@ def add_bloom(*, sender: User, content: str) -> Bloom: bloom_id=bloom_id, sender_id=sender.id, content=content, - timestamp=datetime.datetime.now(datetime.UTC), + timestamp=datetime.datetime.now(datetime.timezone.utc), ), ) for hashtag in hashtags: From c9cb07aef5c799bb08978bf285b52a9d6eb30d9c Mon Sep 17 00:00:00 2001 From: ZabihollahNamazi Date: Sun, 24 May 2026 15:43:37 +0100 Subject: [PATCH 3/5] gitignore --- .vscode/settings.json | 3 ++- backend/.gitignore | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 166be53..94b8107 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "liveServer.settings.root": "front-end/", - "python.defaultInterpreterPath": "backend/.venv/bin/python" + "python.defaultInterpreterPath": "backend/.venv/bin/python", + "python-envs.defaultEnvManager": "ms-python.python:system" } diff --git a/backend/.gitignore b/backend/.gitignore index 30a3427..ac6bb9d 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,3 @@ /.env -/.venv/ +/venv/ *.pyc From 03462e61057ad33fe4fc3a19d654edb99bfbab40 Mon Sep 17 00:00:00 2001 From: ZabihollahNamazi Date: Sun, 24 May 2026 15:44:09 +0100 Subject: [PATCH 4/5] log in from profile page --- front-end/components/login.mjs | 2 ++ front-end/components/logout.mjs | 1 + 2 files changed, 3 insertions(+) diff --git a/front-end/components/login.mjs b/front-end/components/login.mjs index 165b16a..93911b5 100644 --- a/front-end/components/login.mjs +++ b/front-end/components/login.mjs @@ -30,6 +30,8 @@ async function handleLogin(event) { const password = formData.get("password"); await apiService.login(username, password); + //the browser refreshes on that exact same URL + window.location.reload(); } catch (error) { throw error; } finally { diff --git a/front-end/components/logout.mjs b/front-end/components/logout.mjs index 2c5ebe9..fd395d2 100644 --- a/front-end/components/logout.mjs +++ b/front-end/components/logout.mjs @@ -16,6 +16,7 @@ function createLogout(template, isLoggedIn) { async function handleLogout(event) { try { apiService.logout(); + window.location.href = "/"; } catch (error) { throw error; } From 0ff331b688db5ed38d666589f813fce6eee6fd8b Mon Sep 17 00:00:00 2001 From: ZabihollahNamazi Date: Sun, 24 May 2026 22:21:30 +0100 Subject: [PATCH 5/5] update to limit the bloom lentgh in backend and frontend --- backend/data/blooms.py | 5 +++++ front-end/components/bloom-form.mjs | 4 ++-- front-end/components/bloom.mjs | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/data/blooms.py b/backend/data/blooms.py index d9082e5..b2bc26b 100644 --- a/backend/data/blooms.py +++ b/backend/data/blooms.py @@ -6,6 +6,7 @@ from data.connection import db_cursor from data.users import User +MAX_BLOOM_LENGTH = 280 @dataclass class Bloom: @@ -16,6 +17,10 @@ class Bloom: def add_bloom(*, sender: User, content: str) -> Bloom: + # reject any new posts that are longer than 280 characters + if len(content) > MAX_BLOOM_LENGTH: + raise ValueError("Bloom content cannot exceed 280 characters.") + hashtags = [word[1:] for word in content.split(" ") if word.startswith("#")] now = datetime.datetime.now(tz=datetime.timezone.utc) diff --git a/front-end/components/bloom-form.mjs b/front-end/components/bloom-form.mjs index e047f9a..93561d9 100644 --- a/front-end/components/bloom-form.mjs +++ b/front-end/components/bloom-form.mjs @@ -1,5 +1,5 @@ import {apiService} from "../index.mjs"; - +import { MAX_BLOOM_LENGTH } from "./bloom.mjs"; /** * Create a bloom form component * @param {string} template - The ID of the template to clone @@ -51,7 +51,7 @@ function handleTyping(event) { const counter = textarea .closest("[data-form]") ?.querySelector("[data-counter]"); - const maxLength = parseInt(textarea.getAttribute("maxlength"), 10); + const maxLength = parseInt(textarea.getAttribute("maxlength"), 10) || MAX_BLOOM_LENGTH; counter.textContent = `${textarea.value.length} / ${maxLength}`; } diff --git a/front-end/components/bloom.mjs b/front-end/components/bloom.mjs index 0b4166c..dc5ac65 100644 --- a/front-end/components/bloom.mjs +++ b/front-end/components/bloom.mjs @@ -10,6 +10,7 @@ * "sent_timestamp": "datetime as ISO 8601 formatted string"} */ +export const MAX_BLOOM_LENGTH = 280; const createBloom = (template, bloom) => { if (!bloom) return; const bloomFrag = document.getElementById(template).content.cloneNode(true); @@ -26,6 +27,11 @@ const createBloom = (template, bloom) => { bloomUsername.textContent = bloom.sender; bloomTime.textContent = _formatTimestamp(bloom.sent_timestamp); bloomTimeLink.setAttribute("href", `/bloom/${bloom.id}`); + // FIX: If a bloom from the database is over 280 characters, cut it short + let displayContent = bloom.content || ""; + if (displayContent.length > MAX_BLOOM_LENGTH) { + displayContent = displayContent.slice(0, MAX_BLOOM_LENGTH - 3) + "..."; + } bloomContent.replaceChildren( ...bloomParser.parseFromString(_formatHashtags(bloom.content), "text/html") .body.childNodes