Skip to content

Commit

Permalink
Merge pull request #26 from wafflestudio/create_comment
Browse files Browse the repository at this point in the history
create_comment API 추가
  • Loading branch information
odumag99 authored Jan 13, 2025
2 parents 8b2b153 + 8a3424e commit 92ccbfb
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 11 deletions.
10 changes: 9 additions & 1 deletion snuvote/app/vote/dto/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,12 @@ class CreateVoteRequest(BaseModel):

class ParticipateVoteRequest(BaseModel):
participated_choice_ids: List[int]
participation_code: str | None = None
participation_code: str | None = None

def validate_comment_content(value: str) -> str:
if len(value) < 1:
raise InvalidFieldFormatError()
return value

class CommentRequest(BaseModel):
content: Annotated[str, AfterValidator(validate_comment_content)]
50 changes: 48 additions & 2 deletions snuvote/app/vote/dto/responses.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
from functools import wraps
from datetime import datetime, timezone, timedelta
from typing import List, Annotated
from typing import List, Annotated, TypeVar, Callable

from snuvote.database.models import Vote, User, Choice, ChoiceParticipation
from snuvote.database.models import Vote, User, Choice, ChoiceParticipation, Comment
from pydantic import BaseModel
from pydantic.functional_validators import AfterValidator

KST = timezone(timedelta(hours=9), "KST")

T = TypeVar("T")

def skip_none(validator: Callable[[T], T]) -> Callable[[T | None], T | None]:
@wraps(validator)
def wrapper(value: T | None) -> T | None:
if value is None:
return value
return validator(value)

return wrapper

def convert_utc_to_ktc_naive(value: datetime) -> datetime:
value = value.replace(tzinfo=timezone.utc).astimezone(KST).replace(tzinfo=None) # UTC 시간대 주입 후 KST 시간대로 변환한 뒤 offset-naive로 변환
return value
Expand Down Expand Up @@ -86,6 +98,39 @@ def from_choice(choice: Choice, user: User, annonymous_choice, realtime_result)
choice_participants_name=participants_name
)

class CommentDetailResponse(BaseModel):
comment_id: int
writer_name: str
is_writer: bool
comment_content: str
created_datetime: Annotated[datetime, AfterValidator(convert_utc_to_ktc_naive)]
is_edited: bool
edited_datetime: Annotated[datetime|None, AfterValidator(skip_none(convert_utc_to_ktc_naive))] = None

@staticmethod
def from_comment_user(comment: Comment, user: User) -> "CommentDetailResponse":
comment_id = comment.id
writer_name = comment.writer.name
is_writer = (user.id == comment.writer_id)
comment_content = comment.content
created_datetime = comment.create_datetime
is_edited = comment.is_edited

# is_edited가 True인 경우에만 edited_datetime을 주입
edited_datetime = None
if is_edited:
edited_datetime = comment.edited_datetime

return CommentDetailResponse(
comment_id=comment_id,
writer_name=writer_name,
is_writer=is_writer,
comment_content=comment_content,
created_datetime=created_datetime,
is_edited=is_edited,
edited_datetime=edited_datetime
)


class VoteDetailResponse(BaseModel):
vote_id:int
Expand All @@ -100,4 +145,5 @@ class VoteDetailResponse(BaseModel):
create_datetime: Annotated[datetime, AfterValidator(convert_utc_to_ktc_naive)] # UTC 시간대를 KST 시간대로 변환한 뒤 offset-naive로 변환
end_datetime: Annotated[datetime, AfterValidator(convert_utc_to_ktc_naive)] # UTC 시간대를 KST 시간대로 변환한 뒤 offset-naive로 변환
choices: List[ChoiceDetailResponse]
comments: List[CommentDetailResponse]

7 changes: 5 additions & 2 deletions snuvote/app/vote/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from snuvote.database.models import Vote, User, Choice, ChoiceParticipation
from snuvote.app.vote.store import VoteStore
from snuvote.app.vote.errors import ChoiceNotFoundError, InvalidFieldFormatError, MultipleChoicesError, ParticipationCodeError, ParticipationCodeNotProvidedError, WrongParticipationCodeError, EndedVoteError
from snuvote.app.vote.dto.requests import ParticipateVoteRequest
from snuvote.app.vote.dto.requests import ParticipateVoteRequest, CommentRequest

from datetime import datetime, timedelta, timezone

Expand Down Expand Up @@ -77,4 +77,7 @@ def participate_vote(self, vote: Vote, user: User, participate_vote_request: Par
user_id = user.id
choice_id_list = participate_vote_request.participated_choice_ids

return self.vote_store.participate_vote(vote=vote, user_id=user_id, choice_id_list=choice_id_list)
return self.vote_store.participate_vote(vote=vote, user_id=user_id, choice_id_list=choice_id_list)

def create_comment(self, vote: Vote, user: User, comment_request: CommentRequest) -> None:
self.vote_store.create_comment(vote_id=vote.id, writed_id=user.id, content=comment_request.content)
11 changes: 9 additions & 2 deletions snuvote/app/vote/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import datetime, timedelta, timezone

from fastapi import Depends
from snuvote.database.models import Vote, Choice, ChoiceParticipation
from snuvote.database.models import Vote, Choice, ChoiceParticipation, Comment

from snuvote.database.connection import get_db_session
from sqlalchemy import select, delete
Expand Down Expand Up @@ -83,4 +83,11 @@ def participate_vote(self, vote: Vote, user_id: int, choice_id_list: List[int])
self.session.add(choice_participation)

self.session.commit()
return vote
return vote

def create_comment(self, vote_id: int, writed_id: int, content: str) -> Vote:
comment = Comment(vote_id=vote_id, writer_id=writed_id, content=content,
create_datetime=datetime.now(timezone.utc),
is_edited=False)
self.session.add(comment)
self.session.commit()
27 changes: 24 additions & 3 deletions snuvote/app/vote/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from pydantic.functional_validators import AfterValidator
from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_401_UNAUTHORIZED

from snuvote.app.vote.dto.requests import CreateVoteRequest, ParticipateVoteRequest
from snuvote.app.vote.dto.responses import OnGoingVotesListResponse, VotesListInfoResponse, VoteDetailResponse, ChoiceDetailResponse
from snuvote.app.vote.dto.requests import CreateVoteRequest, ParticipateVoteRequest, CommentRequest
from snuvote.app.vote.dto.responses import OnGoingVotesListResponse, VotesListInfoResponse, VoteDetailResponse, ChoiceDetailResponse, CommentDetailResponse
from snuvote.app.vote.errors import VoteNotFoundError, MultipleChoicesError, ChoiceNotFoundError

from snuvote.database.models import User
Expand Down Expand Up @@ -85,7 +85,8 @@ def get_vote(
annonymous_choice = vote.annonymous_choice,
create_datetime = vote.create_datetime,
end_datetime = vote.end_datetime,
choices= [ChoiceDetailResponse.from_choice(choice, user, vote.annonymous_choice, vote.realtime_result) for choice in vote.choices]
choices= [ChoiceDetailResponse.from_choice(choice, user, vote.annonymous_choice, vote.realtime_result) for choice in vote.choices],
comments = [CommentDetailResponse.from_comment_user(comment, user) for comment in vote.comments]
)


Expand All @@ -108,4 +109,24 @@ def paricipate_vote(
#투표 참여하기
vote = vote_service.participate_vote(vote, user, participate_vote_request)

return get_vote(vote.id, user, vote_service)

# 댓글 추가하기
@vote_router.post("/{vote_id}/comment", status_code=HTTP_201_CREATED)
def create_comment(
vote_id: int,
vote_service: Annotated[VoteService, Depends()],
user: Annotated[User, Depends(login_with_access_token)],
comment_request: CommentRequest
):
# 해당 vote_id에 해당하는 투표글 조회
vote = vote_service.get_vote_by_vote_id(vote_id = vote_id)

# 해당 vote_id에 해당하는 투표글이 없을 경우 404 Not Found
if not vote:
raise VoteNotFoundError()

# 댓글 추가하기
vote_service.create_comment(vote, user, comment_request)

return get_vote(vote.id, user, vote_service)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Comment에 edit 관련 필드 추가하고, created_datetime과 edited_datetime Timestamp로 변경
Revision ID: 4fa11f1eada9
Revises: 0a5a02cfb40b
Create Date: 2025-01-13 22:34:30.920243
"""
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 = '4fa11f1eada9'
down_revision: Union[str, None] = '0a5a02cfb40b'
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('comment', sa.Column('is_edited', sa.Boolean(), nullable=False))
op.add_column('comment', sa.Column('edited_datetime', sa.DateTime(timezone=True), nullable=True))
op.alter_column('vote', 'create_datetime',
existing_type=mysql.TIMESTAMP(),
type_=sa.DateTime(timezone=True),
existing_nullable=False)
op.alter_column('vote', 'end_datetime',
existing_type=mysql.TIMESTAMP(),
type_=sa.DateTime(timezone=True),
existing_nullable=False)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('vote', 'end_datetime',
existing_type=sa.DateTime(timezone=True),
type_=mysql.TIMESTAMP(),
existing_nullable=False)
op.alter_column('vote', 'create_datetime',
existing_type=sa.DateTime(timezone=True),
type_=mysql.TIMESTAMP(),
existing_nullable=False)
op.drop_column('comment', 'edited_datetime')
op.drop_column('comment', 'is_edited')
# ### end Alembic commands ###
4 changes: 3 additions & 1 deletion snuvote/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ class Comment(Base):
writer: Mapped["User"] = relationship("User", back_populates="comments", uselist=False)

content: Mapped[str] = mapped_column(Text, nullable=False)
create_datetime: Mapped[datetime] = mapped_column(DateTime, nullable=False)
create_datetime: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
is_edited: Mapped[bool] = mapped_column(Boolean, nullable=False)
edited_datetime: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)

class BlockedRefreshToken(Base):
__tablename__ = "blocked_refresh_token"
Expand Down

0 comments on commit 92ccbfb

Please sign in to comment.