Skip to content

Commit

Permalink
Merge pull request #22 from wafflestudio/participate_vote
Browse files Browse the repository at this point in the history
participate_vote 기능 추가
  • Loading branch information
odumag99 authored Jan 8, 2025
2 parents ace3619 + 5e986be commit 522dab6
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 23 deletions.
7 changes: 6 additions & 1 deletion snuvote/app/vote/dto/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,9 @@ class CreateVoteRequest(BaseModel):
multiple_choice: bool
annonymous_choice: bool
end_datetime: datetime
choices: Annotated[List[str], AfterValidator(validate_choices)]
choices: Annotated[List[str], AfterValidator(validate_choices)]


class ParticipateVoteRequest(BaseModel):
participated_choice_ids: List[int]
participation_code: str | None
15 changes: 14 additions & 1 deletion snuvote/app/vote/dto/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,26 @@ class OnGoingVotesListResponse(BaseModel):
class ChoiceDetailResponse(BaseModel):
choice_id: int
choice_content: str
participated: bool
choice_num_participants: int|None = None
choice_participants_name: List[str]|None = None

# Choice를 받아 ChoiceDetailResponse로 변환
@staticmethod
def from_choice(choice: Choice, annonymous_choice, realtime_result) -> "ChoiceDetailResponse":
def from_choice(choice: Choice, user: User, annonymous_choice, realtime_result) -> "ChoiceDetailResponse":
id = choice.id
content = choice.choice_content
num_participants = len(choice.choice_participations)
participants_name = [choice_participation.user.name for choice_participation in choice.choice_participations]


#로그인한 유저가 선택지를 선택했는지 여부
participated = False
for choice_participation in choice.choice_participations:
if user.id == choice_participation.user_id:
participated = True
break

# 익명 투표인 경우 -> choice_participants_name = None
if annonymous_choice:
participants_name = None
Expand All @@ -64,15 +73,19 @@ def from_choice(choice: Choice, annonymous_choice, realtime_result) -> "ChoiceDe
return ChoiceDetailResponse(
choice_id=id,
choice_content=content,
participated=participated,
choice_num_participants=num_participants,
choice_participants_name=participants_name
)


class VoteDetailResponse(BaseModel):
vote_id:int
writer_name: str
is_writer: bool
title: str
content: str
participation_code_required: bool
realtime_result: bool
multiple_choice: bool
annonymous_choice: bool
Expand Down
18 changes: 17 additions & 1 deletion snuvote/app/vote/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,20 @@ def __init__(self) -> None:

class VoteNotFoundError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_404_NOT_FOUND, "Vote not found")
super().__init__(HTTP_404_NOT_FOUND, "Vote not found")

class MultipleChoicesError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_400_BAD_REQUEST, "Multiple choices not allowed")

class ChoiceNotFoundError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_404_NOT_FOUND, "Choice not found")

class ParticipationCodeNotProvidedError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_403_FORBIDDEN, "Participation code not provided")

class WrongParticipationCodeError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_403_FORBIDDEN, "Wrong participation code")
33 changes: 30 additions & 3 deletions snuvote/app/vote/service.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import Annotated, List

from fastapi import Depends
from snuvote.database.models import Vote
from snuvote.database.models import Vote, User, Choice, ChoiceParticipation
from snuvote.app.vote.store import VoteStore
from snuvote.app.vote.errors import InvalidFieldFormatError, ParticipationCodeError
from snuvote.app.vote.errors import ChoiceNotFoundError, InvalidFieldFormatError, MultipleChoicesError, ParticipationCodeError, ParticipationCodeNotProvidedError, WrongParticipationCodeError
from snuvote.app.vote.dto.requests import ParticipateVoteRequest

from datetime import datetime, timedelta

Expand Down Expand Up @@ -47,4 +48,30 @@ def get_ongoing_list(self) -> List[Vote]:

# 투표글 상세 내용 조회
def get_vote_by_vote_id(self, vote_id: int) -> Vote:
return self.vote_store.get_vote_by_vote_id(vote_id=vote_id)
return self.vote_store.get_vote_by_vote_id(vote_id=vote_id)

def participate_vote(self, vote: Vote, user: User, participate_vote_request: ParticipateVoteRequest) -> None:

# 참여코드가 필요한 투표글인 경우
if vote.participation_code_required:
# 프론트에서 제공되지 않은 경우
if not participate_vote_request.participation_code:
raise ParticipationCodeNotProvidedError()
# 참여코드가 불일치하는 경우
if vote.participation_code != participate_vote_request.participation_code:
raise WrongParticipationCodeError()

# 중복 투표 불가능인데 중복 투표 했을 때
if not vote.multiple_choice and len(participate_vote_request.participated_choice_ids) > 1:
raise MultipleChoicesError()

# 해당 vote에 Request된 choice_id에 해당하는 선택지가 존재하지 않는 경우
vote_choice_id_list = [choice.id for choice in vote.choices]
for choice_id in participate_vote_request.participated_choice_ids:
if choice_id not in vote_choice_id_list:
raise ChoiceNotFoundError()

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)
26 changes: 24 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

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

from snuvote.database.connection import get_db_session
from sqlalchemy import select, delete
Expand Down Expand Up @@ -60,4 +60,26 @@ def get_ongoing_list(self) -> List[Vote]:

# 투표글 상세 내용 조회
def get_vote_by_vote_id(self, vote_id: int) -> Vote:
return self.session.scalar(select(Vote).where(Vote.id == vote_id))
return self.session.scalar(select(Vote).where(Vote.id == vote_id))


#투표 참여하기
def participate_vote(self, vote: Vote, user_id: int, choice_id_list: List[int]) -> None:

# 참여하려고 하는 투표에 이미 투표를 한 상태라면 이전 선택지 참여는 제거하기
for choice in vote.choices:
choice_participation = self.session.scalar(
select(ChoiceParticipation).where((ChoiceParticipation.choice_id == choice.id) & (ChoiceParticipation.user_id == user_id)))

if choice_participation is not None:
self.session.delete(choice_participation)

self.session.flush()

# 선택한 선택지 생성하기
for choice_id in choice_id_list:
choice_participation = ChoiceParticipation(user_id=user_id, choice_id=choice_id)
self.session.add(choice_participation)

self.session.commit()
return vote
49 changes: 34 additions & 15 deletions snuvote/app/vote/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
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
from snuvote.app.vote.dto.requests import CreateVoteRequest, ParticipateVoteRequest
from snuvote.app.vote.dto.responses import OnGoingVotesListResponse, VotesListInfoResponse, VoteDetailResponse, ChoiceDetailResponse
from snuvote.app.vote.errors import VoteNotFoundError
from snuvote.app.vote.errors import VoteNotFoundError, MultipleChoicesError, ChoiceNotFoundError

from snuvote.database.models import User
from snuvote.app.vote.service import VoteService
Expand Down Expand Up @@ -39,17 +39,7 @@ def create_vote(
choices=create_vote_request.choices
)

return {"id": vote.id,
"title":vote.title,
"content":vote.content,
"choices":vote.choices,
"participation_code":vote.participation_code,
"realtime_result":vote.realtime_result,
"multiple_choice":vote.multiple_choice,
"annonymous_choice":vote.annonymous_choice,
"create_datetime":vote.create_datetime,
"end_datetime":vote.end_datetime
}
return get_vote(vote.id, user, vote_service)

# 진행 중인 투표 리스트 조회
@vote_router.get("/ongoing_list", status_code=HTTP_200_OK)
Expand All @@ -70,6 +60,7 @@ def get_ongoing_list(
@vote_router.get("/{vote_id}", status_code=HTTP_200_OK)
def get_vote(
vote_id: int,
user: Annotated[User, Depends(login_with_access_token)],
vote_service: Annotated[VoteService, Depends()]
):

Expand All @@ -78,15 +69,43 @@ def get_vote(
# 해당 vote_id에 해당하는 투표글이 없을 경우 404 Not Found
if not vote:
raise VoteNotFoundError()

#투표 생성자 아이디와 유저 아이디가 같은 경우
is_writer = vote.writer_id == user.id

return VoteDetailResponse(
vote_id = vote.id,
writer_name = vote.writer.name,
is_writer= is_writer,
title = vote.title,
content = vote.content,
participation_code_required = vote.participation_code_required,
realtime_result = vote.realtime_result,
multiple_choice = vote.multiple_choice,
annonymous_choice = vote.annonymous_choice,
create_datetime = vote.create_datetime,
end_datetime = vote.end_datetime,
choices= [ChoiceDetailResponse.from_choice(choice, 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]
)


#투표 참여하기
@vote_router.post("/{vote_id}/participate", status_code=HTTP_201_CREATED)
def paricipate_vote(
vote_id: int,
user: Annotated[User, Depends(login_with_access_token)],
participate_vote_request: ParticipateVoteRequest,
vote_service: Annotated[VoteService, Depends()]
):

# 해당 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 = vote_service.participate_vote(vote, user, participate_vote_request)

return get_vote(vote.id, user, vote_service)

0 comments on commit 522dab6

Please sign in to comment.