-
-
Notifications
You must be signed in to change notification settings - Fork 69
ZA | 25-SDC-JULY | Luke Manyamazi | Sprint 1 | New Feature Rebloom #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Luke-Manyamazi
wants to merge
1
commit into
CodeYourFuture:main
Choose a base branch
from
Luke-Manyamazi:feature-rebloom
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,46 +6,129 @@ | |
| from data.connection import db_cursor | ||
| from data.users import User | ||
|
|
||
| from psycopg2.errors import UniqueViolation | ||
|
|
||
|
|
||
| class AlreadyRebloomedError(Exception): | ||
| pass | ||
|
|
||
|
|
||
| @dataclass | ||
| class Bloom: | ||
| id: int | ||
| sender: User | ||
| content: str | ||
| sent_timestamp: datetime.datetime | ||
|
|
||
|
|
||
| def add_bloom(*, sender: User, content: str) -> Bloom: | ||
| original_bloom_id: Optional[int] = None | ||
| original_sender: Optional[str] = None | ||
| original_sent_timestamp: Optional[datetime.datetime] = None | ||
| rebloom_count: int = 0 | ||
| rebloomed_by_current_user: bool = False | ||
|
|
||
|
|
||
| # Shared by every read query below: resolves original-bloom attribution and | ||
| # rebloom stats via the self-referencing original_bloom_id column, rather than | ||
| # a separate table or a stored counter. | ||
| _SELECT_FIELDS = """ | ||
| b.id, u.username, b.content, b.send_timestamp, | ||
| b.original_bloom_id, orig_user.username, orig.send_timestamp, | ||
| ( | ||
| SELECT COUNT(*) FROM blooms rb | ||
| WHERE rb.original_bloom_id = COALESCE(b.original_bloom_id, b.id) | ||
| ), | ||
| EXISTS( | ||
| SELECT 1 FROM blooms rb2 | ||
| WHERE rb2.sender_id = %(current_user_id)s | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this clause, will users who aren't logged in still be able to see blooms? |
||
| AND rb2.original_bloom_id = COALESCE(b.original_bloom_id, b.id) | ||
| ) | ||
| """ | ||
|
|
||
| _JOIN_CLAUSE = """ | ||
| blooms b | ||
| INNER JOIN users u ON u.id = b.sender_id | ||
| LEFT JOIN blooms orig ON orig.id = b.original_bloom_id | ||
| LEFT JOIN users orig_user ON orig_user.id = orig.sender_id | ||
| """ | ||
|
|
||
|
|
||
| def _bloom_from_row(row) -> Bloom: | ||
| ( | ||
| bloom_id, | ||
| sender_username, | ||
| content, | ||
| timestamp, | ||
| original_bloom_id, | ||
| original_sender, | ||
| original_sent_timestamp, | ||
| rebloom_count, | ||
| rebloomed_by_current_user, | ||
| ) = row | ||
| return Bloom( | ||
| id=bloom_id, | ||
| sender=sender_username, | ||
| content=content, | ||
| sent_timestamp=timestamp, | ||
| original_bloom_id=original_bloom_id, | ||
| original_sender=original_sender, | ||
| original_sent_timestamp=original_sent_timestamp, | ||
| rebloom_count=rebloom_count, | ||
| rebloomed_by_current_user=rebloomed_by_current_user, | ||
| ) | ||
|
|
||
|
|
||
| def add_bloom( | ||
| *, sender_id: int, content: str, original_bloom_id: Optional[int] = None | ||
| ) -> int: | ||
| hashtags = [word[1:] for word in content.split(" ") if word.startswith("#")] | ||
|
|
||
| now = datetime.datetime.now(tz=datetime.UTC) | ||
| bloom_id = int(now.timestamp() * 1000000) | ||
| 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, original_bloom_id) VALUES (%(bloom_id)s, %(sender_id)s, %(content)s, %(timestamp)s, %(original_bloom_id)s)", | ||
| dict( | ||
| bloom_id=bloom_id, | ||
| sender_id=sender.id, | ||
| sender_id=sender_id, | ||
| content=content, | ||
| timestamp=datetime.datetime.now(datetime.UTC), | ||
| timestamp=now, | ||
| original_bloom_id=original_bloom_id, | ||
| ), | ||
| ) | ||
| for hashtag in hashtags: | ||
| cur.execute( | ||
| "INSERT INTO hashtags (hashtag, bloom_id) VALUES (%(hashtag)s, %(bloom_id)s)", | ||
| dict(hashtag=hashtag, bloom_id=bloom_id), | ||
| ) | ||
| return bloom_id | ||
|
|
||
|
|
||
| def add_rebloom(*, sender_id: int, original_bloom_id: int, content: str) -> int: | ||
| try: | ||
| return add_bloom( | ||
| sender_id=sender_id, | ||
| content=content, | ||
| original_bloom_id=original_bloom_id, | ||
| ) | ||
| except UniqueViolation: | ||
| raise AlreadyRebloomedError( | ||
| f"User {sender_id} has already reblооmed bloom {original_bloom_id}" | ||
| ) | ||
|
|
||
|
|
||
| def get_blooms_for_user( | ||
| username: str, *, before: Optional[int] = None, limit: Optional[int] = None | ||
| username: str, | ||
| *, | ||
| current_user_id: Optional[int] = None, | ||
| before: Optional[int] = None, | ||
| limit: Optional[int] = None, | ||
| ) -> List[Bloom]: | ||
| with db_cursor() as cur: | ||
| kwargs = { | ||
| "sender_username": username, | ||
| "current_user_id": current_user_id, | ||
| } | ||
| if before is not None: | ||
| before_clause = "AND send_timestamp < %(before_limit)s" | ||
| before_clause = "AND b.send_timestamp < %(before_limit)s" | ||
| kwargs["before_limit"] = before | ||
| else: | ||
| before_clause = "" | ||
|
|
@@ -54,83 +137,68 @@ def get_blooms_for_user( | |
|
|
||
| cur.execute( | ||
| f"""SELECT | ||
| blooms.id, users.username, content, send_timestamp | ||
| {_SELECT_FIELDS} | ||
| FROM | ||
| blooms INNER JOIN users ON users.id = blooms.sender_id | ||
| {_JOIN_CLAUSE} | ||
| WHERE | ||
| username = %(sender_username)s | ||
| u.username = %(sender_username)s | ||
| {before_clause} | ||
| ORDER BY send_timestamp DESC | ||
| ORDER BY b.send_timestamp DESC | ||
| {limit_clause} | ||
| """, | ||
| kwargs, | ||
| ) | ||
| 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, | ||
| ) | ||
| ) | ||
| return blooms | ||
| return [_bloom_from_row(row) for row in rows] | ||
|
|
||
|
|
||
| def get_bloom(bloom_id: int) -> Optional[Bloom]: | ||
| def get_bloom( | ||
| bloom_id: int, *, current_user_id: Optional[int] = None | ||
| ) -> 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", | ||
| (bloom_id,), | ||
| f"""SELECT | ||
| {_SELECT_FIELDS} | ||
| FROM | ||
| {_JOIN_CLAUSE} | ||
| WHERE | ||
| b.id = %(bloom_id)s | ||
| """, | ||
| {"bloom_id": bloom_id, "current_user_id": current_user_id}, | ||
| ) | ||
| row = cur.fetchone() | ||
| 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, | ||
| ) | ||
| return _bloom_from_row(row) | ||
|
|
||
|
|
||
| def get_blooms_with_hashtag( | ||
| hashtag_without_leading_hash: str, *, limit: int = None | ||
| hashtag_without_leading_hash: str, | ||
| *, | ||
| current_user_id: Optional[int] = None, | ||
| limit: int = None, | ||
| ) -> List[Bloom]: | ||
| kwargs = { | ||
| "hashtag_without_leading_hash": hashtag_without_leading_hash, | ||
| "current_user_id": current_user_id, | ||
| } | ||
| limit_clause = make_limit_clause(limit, kwargs) | ||
| with db_cursor() as cur: | ||
| cur.execute( | ||
| f"""SELECT | ||
| blooms.id, users.username, content, send_timestamp | ||
| {_SELECT_FIELDS} | ||
| FROM | ||
| blooms INNER JOIN hashtags ON blooms.id = hashtags.bloom_id INNER JOIN users ON blooms.sender_id = users.id | ||
| {_JOIN_CLAUSE} | ||
| INNER JOIN hashtags ON b.id = hashtags.bloom_id | ||
| WHERE | ||
| hashtag = %(hashtag_without_leading_hash)s | ||
| ORDER BY send_timestamp DESC | ||
| ORDER BY b.send_timestamp DESC | ||
| {limit_clause} | ||
| """, | ||
| kwargs, | ||
| ) | ||
| 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, | ||
| ) | ||
| ) | ||
| return blooms | ||
| return [_bloom_from_row(row) for row in rows] | ||
|
|
||
|
|
||
| def make_limit_clause(limit: Optional[int], kwargs: Dict[Any, Any]) -> str: | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than having three Optional properties here which are always either set or not, I'd recommend extracting a
RebloomDetailsclass and having a singleOptional[RebloomDetails]property.That way you don't need to worry in code about the (hopefully impossible!) possibility that
original_bloom_idis set whereoriginal_senderisn't - we make clear through the types the properties are grouped and are all present or all absent.