Skip to content
Closed
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
38 changes: 38 additions & 0 deletions migrations/versions/7e40ff9486ce_like_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""like_db

Revision ID: 7e40ff9486ce
Revises: 5cf69f1026d9
Create Date: 2025-02-14 08:24:17.387577

"""

import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision = '7e40ff9486ce'
down_revision = '5cf69f1026d9'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'comment_like',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('comment_uuid', sa.UUID(), nullable=False),
sa.Column('create_ts', sa.DateTime(), nullable=False),
sa.Column('is_deleted', sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(['comment_uuid'], ['comment.uuid']),
sa.PrimaryKeyConstraint('id'),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('likes')
# ### end Alembic commands ###
2 changes: 1 addition & 1 deletion rating_api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from .db import *


__all__ = ["Base", "BaseDbModel", "Lecturer", "LecturerUserComment", "Comment"]
__all__ = ["Base", "BaseDbModel", "Lecturer", "LecturerUserComment", "Comment", "Like"]
10 changes: 9 additions & 1 deletion rating_api/models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from fastapi_sqlalchemy import db
from sqlalchemy import UUID, Boolean, DateTime
from sqlalchemy import Enum as DbEnum
from sqlalchemy import ForeignKey, Integer, String, UnaryExpression, and_, func, nulls_last, or_, true
from sqlalchemy import ForeignKey, Integer, String, UnaryExpression, and_, func, nulls_last, or_, select, true
Copy link
Member

Choose a reason for hiding this comment

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

зачем селект

from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.attributes import InstrumentedAttribute
Expand Down Expand Up @@ -130,3 +130,11 @@ class LecturerUserComment(BaseDbModel):
create_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, nullable=False)
update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, nullable=False)
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)


class CommentLike(BaseDbModel):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, nullable=False)
comment_uuid: Mapped[uuid.UUID] = mapped_column(UUID, nullable=False)
Copy link
Member

Choose a reason for hiding this comment

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

а мб отношение полноценное сделаем?

create_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, nullable=False)
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
2 changes: 2 additions & 0 deletions rating_api/routes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from rating_api import __version__
from rating_api.routes.comment import comment
from rating_api.routes.lecturer import lecturer
from rating_api.routes.like import like
from rating_api.settings import Settings, get_settings
from rating_api.utils.logging_utils import get_request_body, log_request

Expand Down Expand Up @@ -36,6 +37,7 @@

app.include_router(lecturer)
app.include_router(comment)
app.include_router(like)


@app.middleware("http")
Expand Down
74 changes: 47 additions & 27 deletions rating_api/routes/comment.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import datetime
import re
from typing import Literal, Union
Expand All @@ -7,6 +7,9 @@
from auth_lib.fastapi import UnionAuth
from fastapi import APIRouter, Depends, Query
from fastapi_sqlalchemy import db
from sqlalchemy import func, select
from sqlalchemy.orm import aliased


from rating_api.exceptions import (
CommentTooLong,
Expand All @@ -17,7 +20,7 @@
TooManyCommentsToLecturer,
UpdateError,
)
from rating_api.models import Comment, Lecturer, LecturerUserComment, ReviewStatus
from rating_api.models import Comment, CommentLike, Lecturer, LecturerUserComment, ReviewStatus
from rating_api.schemas.base import StatusResponseModel
from rating_api.schemas.models import (
CommentGet,
Expand Down Expand Up @@ -160,8 +163,10 @@
Возвращает комментарий по его UUID в базе данных RatingAPI
"""
comment: Comment = Comment.query(session=db.session).filter(Comment.uuid == uuid).one_or_none()
like_count = CommentLike.query(session=db.session).filter(CommentLike.comment_uuid == uuid).count()
if comment is None:
raise ObjectNotFound(Comment, uuid)
comment.like_count = like_count
return CommentGet.model_validate(comment)


Expand All @@ -180,33 +185,33 @@

`limit` - максимальное количество возвращаемых комментариев

`offset` - смещение, определяющее, с какого по порядку комментария начинать выборку.
Если без смещения возвращается комментарий с условным номером N,
то при значении offset = X будет возвращаться комментарий с номером N + X
`offset` - смещение, определяющее, с какого по порядку комментария начинать выборку.

`order_by` - возможное значение `'create_ts'` - возвращается список комментариев отсортированных по времени создания
`order_by` - возможное значение `'create_ts'` - возвращается список комментариев, отсортированных по времени создания.

`lecturer_id` - вернет все комментарии для преподавателя с конкретным id, по дефолту возвращает вообще все аппрувнутые комментарии.
`lecturer_id` - вернет все комментарии для преподавателя с конкретным id.

`user_id` - вернет все комментарии пользователя с конкретным id
`user_id` - вернет все комментарии пользователя с конкретным id.

`unreviewed` - вернет все непроверенные комментарии, если True. По дефолту False.
"""
comments = Comment.query(session=db.session).all()
if not comments:
raise ObjectNotFound(Comment, 'all')
if "rating.comment.review" in [scope['name'] for scope in user.get('session_scopes')] or user.get('id') == user_id:
result = CommentGetAllWithStatus(limit=limit, offset=offset, total=len(comments))
comment_validator = CommentGetWithStatus
else:
result = CommentGetAll(limit=limit, offset=offset, total=len(comments))
comment_validator = CommentGet
result.comments = comments
if user_id is not None:
result.comments = [comment for comment in result.comments if comment.user_id == user_id]
# Subquery чтобы посчитать лайки
like_counts = (
select(CommentLike.comment_uuid, func.count(CommentLike.id).label('like_count'))
.where(CommentLike.is_deleted == False)
.group_by(CommentLike.comment_uuid)
.alias("like_counts")
)
# получаем комменты с лайками к ним
comments_query = select(Comment, func.coalesce(like_counts.c.like_count, 0).label('like_count')).outerjoin(
like_counts, Comment.uuid == like_counts.c.comment_uuid
)
Comment on lines +199 to +208
Copy link
Member

Choose a reason for hiding this comment

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

мб вынесем всю эту логику в гибрид-метод?

Copy link
Member Author

Choose a reason for hiding this comment

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

это я так, на скорую руку писал, нужно будет переделать, да

Copy link
Member Author

Choose a reason for hiding this comment

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

мне кажется, я тогда с джоином не разобрался как его без селекта сделать


if lecturer_id is not None:
result.comments = [comment for comment in result.comments if comment.lecturer_id == lecturer_id]
comments_query = comments_query.where(Comment.lecturer_id == lecturer_id)

if user_id is not None:
comments_query = comments_query.where(Comment.user_id == user_id)

if unreviewed:
if not user:
Expand All @@ -219,16 +224,31 @@
]
else:
raise ForbiddenAction(Comment)
comments_query = comments_query.where(Comment.review_status == ReviewStatus.PENDING)
Copy link
Member

Choose a reason for hiding this comment

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

можно через фильтр а не через where, чтобы стилистику кода сохранять

else:
result.comments = [comment for comment in result.comments if comment.review_status is ReviewStatus.APPROVED]

result.comments = result.comments[offset : limit + offset]
comments_query = comments_query.where(Comment.review_status == ReviewStatus.APPROVED)

if "create_ts" in order_by:
result.comments.sort(key=lambda comment: comment.create_ts, reverse=True)
result.total = len(result.comments)
result.comments = [comment_validator.model_validate(comment) for comment in result.comments]
result.comments.sort(key=lambda comment: comment.create_ts, reverse=True)
comments_query = comments_query.order_by(Comment.create_ts.desc())

comments_query = comments_query.offset(offset).limit(limit)

comments_with_likes = db.session.execute(comments_query).all()
Copy link
Member

Choose a reason for hiding this comment

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

ну вот так не пишем, тоже не наш стиль бэкенда


if not comments_with_likes:
raise ObjectNotFound(Comment, 'all')
# добавляем лайки к комментам
comments = []
for comment, like_count in comments_with_likes:
comment.like_count = like_count
comments.append(CommentGet.model_validate(comment))

result = CommentGetAll(
limit=limit,
offset=offset,
total=len(comments),
comments=comments,
)
return result


Expand Down
65 changes: 65 additions & 0 deletions rating_api/routes/like.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import datetime
from typing import Literal
from uuid import UUID

import aiohttp
from auth_lib.fastapi import UnionAuth
from fastapi import APIRouter, Depends, Query
from fastapi_sqlalchemy import db

from rating_api.exceptions import AlreadyExists, ForbiddenAction, ObjectNotFound
from rating_api.models import Comment, CommentLike, Lecturer, LecturerUserComment, ReviewStatus
from rating_api.schemas.base import StatusResponseModel
from rating_api.schemas.models import LikeGet
from rating_api.settings import Settings, get_settings


settings: Settings = get_settings()
like = APIRouter(prefix="/like", tags=["Like"])


@like.post("/{comment_uuid}", response_model=LikeGet)
async def create_like(comment_uuid, user=Depends(UnionAuth())) -> LikeGet:
"""
Создает лайк на коммент
"""
comment: Comment = Comment.query(session=db.session).filter(Comment.uuid == comment_uuid).one_or_none()
if comment is None:
raise ObjectNotFound(Comment, comment_uuid)

existing_like: CommentLike = (
CommentLike.query(session=db.session)
.filter(
CommentLike.comment_uuid == comment_uuid,
CommentLike.user_id == user.get('id'),
CommentLike.is_deleted == False,
)
.one_or_none()
)
if existing_like:
raise AlreadyExists(CommentLike, comment_uuid)

new_like = CommentLike.create(session=db.session, user_id=user.get('id'), comment_uuid=comment_uuid)
return LikeGet.model_validate(new_like)


@like.delete("/{comment_uuid}", response_model=StatusResponseModel)
async def delete_like(comment_uuid: UUID, user=Depends(UnionAuth())):
"""
Удалить свой лайк на коммент
"""
like = (
CommentLike.query(session=db.session)
.filter(
CommentLike.comment_uuid == comment_uuid,
CommentLike.user_id == user.get('id'),
CommentLike.is_deleted == False,
)
.one_or_none()
)
if not like:
raise ObjectNotFound(CommentLike, comment_uuid)
CommentLike.delete(session=db.session, id=like.id)
return StatusResponseModel(
status="Success", message="Like has been deleted", ru="Лайк на данный комментарий был удален"
)
8 changes: 8 additions & 0 deletions rating_api/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CommentGet(Base):
mark_clarity: int
mark_general: float
lecturer_id: int
like_count: int | None = None


class CommentGetWithStatus(Base):
Expand Down Expand Up @@ -148,3 +149,10 @@ class LecturerPatch(Base):
middle_name: str | None = None
avatar_link: str | None = None
timetable_id: int | None = None


class LikeGet(Base):
id: int
user_id: int
comment_uuid: UUID
create_ts: datetime.datetime
Loading