Skip to content

Commit

Permalink
Merge pull request #10 from minchok125/fix/reviews
Browse files Browse the repository at this point in the history
Fix/reviews
  • Loading branch information
minchok125 authored Jan 13, 2025
2 parents 6840d35 + dddb424 commit 1cc2757
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 30 deletions.
13 changes: 10 additions & 3 deletions watchapedia/app/movie/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,14 @@ def get_movie_by_movie_id(self, movie_id: int) -> Movie | None:
def update_average_rating(self, movie: Movie) -> None:
reviews = movie.reviews
total_rating = 0.0
rating_num = 0

for review in reviews :
total_rating += review.rating
movie.average_rating = round(total_rating / len(reviews), 1)
self.session.flush()
if review.rating is not None :
total_rating += review.rating
rating_num += 1

if rating_num >= 1 :
movie.average_rating = round(total_rating / rating_num, 1)

self.session.flush()
14 changes: 13 additions & 1 deletion watchapedia/app/review/dto/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ def validate_rating(value: float) -> float:
raise InvalidFieldFormatError("rating")
return value

def validate_status(value: str | None) -> str | None:
# status는 None이거나 "WatchList"거나 "Watching"이어야 함
if value is None:
return value
if value not in ["WatchList", "Watching", ""] :
raise InvalidFieldFormatError("status")
return value

class ReviewCreateRequest(BaseModel):
content: Annotated[str | None, AfterValidator(validate_content)] = None
rating: Annotated[float, AfterValidator(validate_rating)]
rating: Annotated[float | None, AfterValidator(validate_rating)] = None
spoiler: bool
status: Annotated[str | None, AfterValidator(validate_status)] = None

class ReviewUpdateRequest(BaseModel):
content: Annotated[str | None, AfterValidator(validate_content)] = None
rating: Annotated[float | None, AfterValidator(validate_rating)] = None
spoiler: bool | None
status: Annotated[str | None, AfterValidator(validate_status)] = None
4 changes: 3 additions & 1 deletion watchapedia/app/review/dto/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ class ReviewResponse(BaseModel):
user_name: str
movie_id: int
content: str | None
rating: float
rating: float | None
likes_count: int
created_at: datetime
spoiler: bool
status: str | None

10 changes: 6 additions & 4 deletions watchapedia/app/review/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import Integer, String, Float, ForeignKey
from sqlalchemy import Integer, String, Float, ForeignKey, Boolean
from sqlalchemy.orm import relationship, Mapped, mapped_column
from datetime import datetime
from watchapedia.database.common import Base
Expand All @@ -12,10 +12,12 @@ class Review(Base):
__tablename__ = 'review'

id: Mapped[int] = mapped_column(Integer, primary_key=True)
content: Mapped[str] = mapped_column(String(500), nullable=False)
rating: Mapped[float] = mapped_column(Float, nullable=False)
content: Mapped[str] = mapped_column(String(500), nullable=True)
rating: Mapped[float] = mapped_column(Float, nullable=True)
likes_count: Mapped[int] = mapped_column(Integer, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
spoiler: Mapped[bool] = mapped_column(Boolean, nullable=False)
status: Mapped[str] = mapped_column(String(20), nullable=True)

user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("user.id"), nullable=False
Expand All @@ -38,4 +40,4 @@ class UserLikesReview(Base):
)
review_id: Mapped[int] = mapped_column(
Integer, ForeignKey("review.id"), nullable=False
)
)
26 changes: 21 additions & 5 deletions watchapedia/app/review/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ def get_review_by_user_and_movie(self, user_id: int, movie_id: int) -> Review |

return self.session.scalar(get_review_query)

def create_review(self, user_id: int, movie_id: int, content: str, rating: float, created_at) -> Review:
def create_review(self, user_id: int, movie_id: int, content: str | None, rating: float | None,
created_at, spoiler: bool, status: str | None
) -> Review:
review = Review(
user_id=user_id,
movie_id=movie_id,
content=content,
rating=rating,
likes_count=0,
created_at=created_at
created_at=created_at,
spoiler=spoiler,
status=status
)
self.session.add(review)
self.session.flush()
Expand All @@ -36,21 +40,33 @@ def create_review(self, user_id: int, movie_id: int, content: str, rating: float

return review

def update_review(self, review, content: str | None, rating: float | None) -> Review:
def update_review(self, review, content: str | None, rating: float | None,
spoiler: bool | None, status: str | None
) -> Review:
if content is not None :
review.content = content

if rating is not None :
review.rating = rating

if spoiler is not None :
review.spoiler = spoiler

if status is not None :
review.status = status

self.session.flush()

return review

def get_reviews(self, movie_id: int) -> Sequence[Review]:
def get_reviews_by_movie_id(self, movie_id: int) -> Sequence[Review]:
reviews_list_query = select(Review).where(Review.movie_id == movie_id)
return self.session.scalars(reviews_list_query).all()

def get_reviews_by_user_id(self, user_id: int) -> Sequence[Review]:
reviews_list_query = select(Review).where(Review.user_id == user_id)
return self.session.scalars(reviews_list_query).all()

def get_review_by_review_id(self, review_id: int) -> Review:
review = self.session.get(Review, review_id)
if review is None :
Expand Down Expand Up @@ -81,4 +97,4 @@ def like_review(self, user_id: int, review: Review) -> None:

self.session.flush()

return review
return review
28 changes: 20 additions & 8 deletions watchapedia/app/review/service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Annotated
from fastapi import Depends
from watchapedia.common.errors import PermissionDeniedError
from watchapedia.app.user.models import User
from watchapedia.app.movie.repository import MovieRepository
from watchapedia.app.movie.errors import MovieNotFoundError
from watchapedia.app.review.dto.responses import ReviewResponse
Expand All @@ -17,7 +18,9 @@ def __init__(self,
self.movie_repository = movie_repository
self.review_repository = review_repository

def create_review(self, user_id: int, movie_id: int, content: str, rating: float | None) -> ReviewResponse:
def create_review(self, user_id: int, movie_id: int, content: str | None,
rating: float | None, spoiler: bool, status: str | None
) -> ReviewResponse:
movie = self.movie_repository.get_movie_by_movie_id(movie_id)
if movie is None :
raise MovieNotFoundError()
Expand All @@ -26,30 +29,37 @@ def create_review(self, user_id: int, movie_id: int, content: str, rating: float
if review is not None :
raise RedundantReviewError()

new_review = self.review_repository.create_review(user_id=user_id, movie_id=movie_id,
content=content, rating=rating, created_at=datetime.now())
new_review = self.review_repository.create_review(user_id=user_id, movie_id=movie_id, content=content, rating=rating,
created_at=datetime.now(), spoiler=spoiler, status=status)
self.movie_repository.update_average_rating(movie)

return self._process_review_response(new_review)

def update_review(self, user_id: int, review_id: int, content: str | None, rating: float | None) -> ReviewResponse:
def update_review(self, user_id: int, review_id: int, content: str | None,
rating: float | None, spoiler: bool | None, status: str | None
) -> ReviewResponse:
review = self.review_repository.get_review_by_review_id(review_id)
if not review.user_id == user_id :
raise PermissionDeniedError()

movie = review.movie

updated_review = self.review_repository.update_review(review, content=content, rating=rating)
updated_review = self.review_repository.update_review(review, content=content, rating=rating,
spoiler=spoiler, status=status)
self.movie_repository.update_average_rating(movie)

return self._process_review_response(updated_review)

def list_reviews(self, movie_id: int) -> list[ReviewResponse]:
def movie_reviews(self, movie_id: int) -> list[ReviewResponse]:
movie = self.movie_repository.get_movie_by_movie_id(movie_id)
if movie is None :
raise MovieNotFoundError()

reviews = self.review_repository.get_reviews(movie_id)
reviews = self.review_repository.get_reviews_by_movie_id(movie_id)
return [self._process_review_response(review) for review in reviews]

def user_reviews(self, user: User) -> list[ReviewResponse]:
reviews = self.review_repository.get_reviews_by_user_id(user.id)
return [self._process_review_response(review) for review in reviews]

def like_review(self, user_id: int, review_id: int) -> ReviewResponse :
Expand All @@ -66,5 +76,7 @@ def _process_review_response(self, review: Review) -> ReviewResponse:
content=review.content,
rating=review.rating,
likes_count=review.likes_count,
created_at=review.created_at
created_at=review.created_at,
spoiler=review.spoiler,
status=review.status
)
29 changes: 21 additions & 8 deletions watchapedia/app/review/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@
@review_router.post('/{movie_id}',
status_code=201,
summary="리뷰 작성",
description="movie_id, content와 rating을 받아 리뷰를 작성하고 성공 시 username을 포함하여 리뷰를 반환합니다.",
description="movie_id, content와 rating, spoiler 여부를 받아 리뷰를 작성하고 성공 시 username을 포함하여 리뷰를 반환합니다.",
)
def create_review(
user: Annotated[User, Depends(login_with_header)],
movie_id: int,
review_service: Annotated[ReviewService, Depends()],
review: ReviewCreateRequest,
) -> ReviewResponse:
return review_service.create_review(user.id, movie_id, review.content, review.rating)
return review_service.create_review(user.id, movie_id, review.content,
review.rating, review.spoiler, review.status)

@review_router.patch('/{review_id}',
status_code=200,
summary="리뷰 수정",
description="review_id와 content, rating을 받아 리뷰를 수정하고 반환합니다.",
description="review_id와 content, rating, spoiler 여부를 받아 리뷰를 수정하고 반환합니다.",
response_model=ReviewResponse
)
def update_review(
Expand All @@ -38,20 +39,32 @@ def update_review(
review_service: Annotated[ReviewService, Depends()],
):
return review_service.update_review(
user.id, review_id, review.content, review.rating
user.id, review_id, review.content, review.rating, review.spoiler, review.status
)

@review_router.get('/{movie_id}',
@review_router.get('/list',
status_code=200,
summary="리뷰 출력",
summary="유저 리뷰 출력",
description="유저가 남긴 모든 리뷰들을 반환합니다다",
response_model=list[ReviewResponse]
)
def get_reviews_by_user(
user: Annotated[User, Depends(login_with_header)],
review_service: Annotated[ReviewService, Depends()],
):
return review_service.user_reviews(user)

@review_router.get('/list/{movie_id}',
status_code=200,
summary="영화 리뷰 출력",
description="movie_id를 받아 해당 영화에 달린 리뷰들을 반환합니다",
response_model=list[ReviewResponse]
)
def get_reviews(
def get_reviews_by_movie(
movie_id: int,
review_service: Annotated[ReviewService, Depends()],
):
return review_service.list_reviews(movie_id)
return review_service.movie_reviews(movie_id)

@review_router.patch('/like/{review_id}',
status_code=200,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""empty message
Revision ID: f2d47c4d0eb0
Revises: 1b8847f3edf8
Create Date: 2025-01-10 14:37:24.227611
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'f2d47c4d0eb0'
down_revision: Union[str, None] = '1b8847f3edf8'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('review', sa.Column('spoiler', sa.Boolean(), nullable=False))
op.add_column('review', sa.Column('status', sa.String(length=20), nullable=False))
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('review', 'status')
op.drop_column('review', 'spoiler')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""empty message
Revision ID: 2eb81e46ebd3
Revises: f2d47c4d0eb0
Create Date: 2025-01-10 14:46:58.894464
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision: str = '2eb81e46ebd3'
down_revision: Union[str, None] = 'f2d47c4d0eb0'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('review', 'content',
existing_type=mysql.VARCHAR(length=500),
nullable=True)
op.alter_column('review', 'status',
existing_type=mysql.VARCHAR(length=20),
nullable=True)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('review', 'status',
existing_type=mysql.VARCHAR(length=20),
nullable=False)
op.alter_column('review', 'content',
existing_type=mysql.VARCHAR(length=500),
nullable=False)
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""empty message
Revision ID: 3c1bdf2a174a
Revises: 2eb81e46ebd3
Create Date: 2025-01-10 15:08:31.817905
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision: str = '3c1bdf2a174a'
down_revision: Union[str, None] = '2eb81e46ebd3'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('review', 'rating',
existing_type=mysql.FLOAT(),
nullable=True)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('review', 'rating',
existing_type=mysql.FLOAT(),
nullable=False)
# ### end Alembic commands ###

0 comments on commit 1cc2757

Please sign in to comment.