Skip to content
Merged
34 changes: 34 additions & 0 deletions rating_api/models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,40 @@ def order_by_like_diff(cls, asc_order: bool = False):
else:
return cls.like_dislike_diff.desc()

@hybrid_method
def has_reaction(self, user_id: int, react: Reaction) -> bool:
return any(reaction.user_id == user_id and reaction.reaction == react for reaction in self.reactions)

@has_reaction.expression
def has_reaction(cls, user_id: int, react: Reaction):
return (
select([true()])
.where(
and_(
CommentReaction.comment_uuid == cls.uuid,
CommentReaction.user_id == user_id,
CommentReaction.reaction == react,
)
)
.exists()
)

@classmethod
def reactions_for_comments(cls, user_id: int, session, comments):
if not user_id or not comments:
return {}
comments_uuid = [c.uuid for c in comments]
result = (
session.query(Comment.uuid, CommentReaction.reaction)
.join(
CommentReaction, and_(Comment.uuid == CommentReaction.comment_uuid, CommentReaction.user_id == user_id)
)
.filter(Comment.uuid.in_(comments_uuid))
.group_by(Comment.uuid, CommentReaction.reaction)
.all()
)
return dict(result)


class LecturerUserComment(BaseDbModel):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
Expand Down
39 changes: 33 additions & 6 deletions rating_api/routes/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,18 @@ async def import_comments(


@comment.get("/{uuid}", response_model=CommentGet)
async def get_comment(uuid: UUID) -> CommentGet:
async def get_comment(uuid: UUID, user=Depends(UnionAuth(auto_error=False, allow_none=False))) -> CommentGet:
"""
Возвращает комментарий по его UUID в базе данных RatingAPI
"""
comment: Comment = Comment.query(session=db.session).filter(Comment.uuid == uuid).one_or_none()
if comment is None:
raise ObjectNotFound(Comment, uuid)
return CommentGet.model_validate(comment)
base_data = CommentGet.model_validate(comment)
if user:
base_data.is_liked=comment.has_reaction(user.get("id"), Reaction.LIKE)
base_data.is_disliked=comment.has_reaction(user.get("id"), Reaction.DISLIKE)
return base_data


@comment.get("", response_model=Union[CommentGetAll, CommentGetAllWithAllInfo, CommentGetAllWithStatus])
Expand All @@ -187,7 +191,7 @@ async def get_comments(
unreviewed: bool = False,
asc_order: bool = False,
user=Depends(UnionAuth(scopes=["rating.comment.review"], auto_error=False, allow_none=False)),
) -> CommentGetAll:
) -> Union[CommentGetAll, CommentGetAllWithAllInfo, CommentGetAllWithStatus]:
"""
Scopes: `["rating.comment.review"]`

Expand Down Expand Up @@ -251,8 +255,23 @@ async def get_comments(
result.comments = [comment for comment in result.comments if comment.review_status is ReviewStatus.APPROVED]

result.total = len(result.comments)
result.comments = [comment_validator.model_validate(comment) for comment in result.comments]
comments_with_like = []

This comment was marked as resolved.

current_user_id = user.get("id") if user else None

if current_user_id and result.comments:
user_reactions = Comment.reactions_for_comments(current_user_id, db.session, result.comments)
else:
user_reactions = {}

for comment in result.comments:
base_data = comment_validator.model_validate(comment)
if current_user_id:
reaction = user_reactions.get(comment.uuid)
base_data.is_liked = (reaction == Reaction.LIKE)
base_data.is_disliked = (reaction == Reaction.DISLIKE)
comments_with_like.append(base_data)

result.comments = comments_with_like
return result


Expand Down Expand Up @@ -300,7 +319,10 @@ async def update_comment(uuid: UUID, comment_update: CommentUpdate, user=Depends
review_status=ReviewStatus.PENDING,
)

return CommentGet.model_validate(updated_comment)
updated_comment = CommentGet.model_validate(updated_comment)
updated_comment.is_liked=comment.has_reaction(user.get("id"), Reaction.LIKE)
updated_comment.is_disliked=comment.has_reaction(user.get("id"), Reaction.DISLIKE)
return updated_comment


@comment.delete("/{uuid}", response_model=StatusResponseModel)
Expand Down Expand Up @@ -365,11 +387,16 @@ async def like_comment(
)
.first()
)
comment = CommentGet.model_validate(comment)
comment.is_liked = (reaction == Reaction.LIKE)
comment.is_disliked = (reaction == Reaction.DISLIKE)

if existing_reaction and existing_reaction.reaction != reaction:
new_reaction = CommentReaction.update(session=db.session, id=existing_reaction.uuid, reaction=reaction)
elif not existing_reaction:
CommentReaction.create(session=db.session, user_id=user.get("id"), comment_uuid=comment.uuid, reaction=reaction)
else:
comment.is_disliked = False
comment.is_liked = False
CommentReaction.delete(session=db.session, id=existing_reaction.uuid)
return CommentGet.model_validate(comment)
return comment
2 changes: 2 additions & 0 deletions rating_api/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class CommentGet(Base):
like_count: int
dislike_count: int
user_fullname: str | None = None
is_liked: bool = False
is_disliked: bool = False


class CommentGetWithStatus(CommentGet):
Expand Down
17 changes: 17 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ def comment(dbsession, lecturer):
dbsession.commit()


@pytest.fixture
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

красиво сделал, мне нравится

def comment_reaction(dbsession, comment):
created_reactions = []

def _create_reaction(user_id: int, react: Reaction):
reaction = CommentReaction(user_id=user_id, comment_uuid=comment.uuid, reaction=react)
dbsession.add(reaction)
dbsession.commit()
created_reactions.append(reaction)

yield _create_reaction

for reaction in created_reactions:
dbsession.delete(reaction)
dbsession.commit()


@pytest.fixture
def unreviewed_comment(dbsession, lecturer):
_comment = Comment(
Expand Down
39 changes: 32 additions & 7 deletions tests/test_routes/test_comment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import datetime
import logging
import uuid

import pytest
from starlette import status
Expand Down Expand Up @@ -196,13 +195,39 @@ def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response
assert user_comment is not None


def test_get_comment(client, comment):
@pytest.mark.parametrize(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

здесь вроде норм, я потестил, все работает
Можно было бы конечно добавить проверку не только на статусы 200 и 404, но думаю это неважно для текущей реализации комментариев

"reaction_data, expected_reaction, comment_user_id",
[
(None, None, 0),
((0, Reaction.LIKE), "is_liked", 0), # my like on my comment
((0, Reaction.DISLIKE), "is_disliked", 0),
((999, Reaction.LIKE), None, 0), # someone else's like on my comment
((999, Reaction.DISLIKE), None, 0),
((0, Reaction.LIKE), "is_liked", 999), # my like on someone else's comment
((0, Reaction.DISLIKE), "is_disliked", 999),
((333, Reaction.LIKE), None, 999), # someone else's like on another person's comment
((333, Reaction.DISLIKE), None, 999),
(None, None, None), # anonymous
],
)
def test_get_comment_with_reaction(client, comment, reaction_data, expected_reaction, comment_user_id, comment_reaction):
comment.user_id = comment_user_id

if reaction_data:
user_id, reaction_type = reaction_data
comment_reaction(user_id, reaction_type)

response_comment = client.get(f'{url}/{comment.uuid}')
print("1")
assert response_comment.status_code == status.HTTP_200_OK
random_uuid = uuid.uuid4()
response = client.get(f'{url}/{random_uuid}')
assert response.status_code == status.HTTP_404_NOT_FOUND

if response_comment:
data = response_comment.json()
if expected_reaction:
assert data[expected_reaction]
else:
assert data["is_liked"] == False
assert data["is_disliked"] == False
else:
assert response_comment.status_code == status.HTTP_404_NOT_FOUND


@pytest.fixture
Expand Down
Loading