Skip to content

[feat] 투표하기 api 구현#127

Merged
buzz0331 merged 15 commits intodevelopfrom
feat/#125-vote
Aug 2, 2025
Merged

[feat] 투표하기 api 구현#127
buzz0331 merged 15 commits intodevelopfrom
feat/#125-vote

Conversation

@buzz0331
Copy link
Contributor

@buzz0331 buzz0331 commented Aug 1, 2025

#️⃣ 연관된 이슈

closes #125

📝 작업 내용

api 흐름은 다음과 같습니다.

  1. 방 참가자인지 검증
  2. type에 따른 분기처리
  • 투표하기 인 경우
    • 사용자가 해당 투표에 참여했는지 확인
      • 이미 투표한 경우 -> RoomParticipant에서 voteItem fk 수정 (같은 투표 항목인 경우 예외처리) => VoteItem의 count 그대로
      • 투표 처음하는 경우 -> 새로운 RoomParticipant 생성 => VoteItem의 count 증가
  • 투표 취소하기 인 경우
    • 사용자가 해당 투표 항목에 참여했는지 확인
      • 투표 하지 않은 경우 예외처리
      • 투표한 경우 RoomParticipant 삭제 => VoteItem의 count 감소

📸 스크린샷

스크린샷 2025-08-02 오전 2 29 46

💬 리뷰 요구사항

  • 로직이 복잡하니 제가 제대로 상태 유효성 검증 또는 변경을 수행했는지 검토해주시면 감사할게요 😅

  • 추가적으로 RoomParticipantValidator에 existByUserIdAndRoomId 쿼리에 파라미터의 순서를 바꿔서 전달하고 있는 오류를 수정했습니다. 지금까지 테스트가 어떻게 통과되고 있었는지 의문이네요 하핳,,

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신규 기능

    • 투표 기능이 추가되어 사용자가 방 내 특정 투표 항목에 투표하거나 투표를 취소할 수 있습니다.
    • 투표 요청 및 응답에 대한 데이터 구조가 추가되었습니다.
    • 투표 관련 다양한 예외 상황에 대한 에러 코드가 추가되었습니다.
  • 버그 수정

    • 일부 메서드 호출 시 인자 순서 및 메서드명이 올바르게 수정되었습니다.
  • 문서화

    • Swagger 문서에 투표 관련 응답 및 예외 설명이 추가되었습니다.
  • 테스트

    • 투표 기능에 대한 통합 및 단위 테스트가 추가되어 다양한 시나리오가 검증됩니다.
  • 리팩터링/정리

    • 불필요한 더미 클래스가 삭제되었습니다.
    • 내부 쿼리 및 예외 코드 명칭이 명확하게 변경되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Aug 1, 2025

Walkthrough

투표 기능이 전체적으로 도입되었습니다. 투표 API, 서비스, 포트, 어댑터, 도메인, 예외 코드가 추가 및 확장되었고, 투표 관련 엔티티 및 레포지토리도 보강되었습니다. 기존 팔로우 예외 코드가 일부 변경되었으며, 테스트 코드가 대폭 추가되어 투표 로직의 정상 및 예외 흐름을 검증합니다.

Changes

Cohort / File(s) Change Summary
투표 API 및 서비스 계층 추가
src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java, .../web/request/VoteRequest.java, .../web/response/VoteResponse.java, .../application/service/VoteService.java, .../application/port/in/VoteUseCase.java, .../application/port/in/dto/VoteCommand.java, .../application/port/in/dto/VoteResult.java
투표 엔드포인트, 요청/응답 DTO, 서비스, 유스케이스, 커맨드/결과 DTO 신설 및 연결
투표 도메인 및 예외 처리
src/main/java/konkuk/thip/vote/domain/VoteItem.java, .../domain/VoteParticipant.java, src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
투표 카운트 증감 메서드, 중복 투표 방지, 예외 코드 확장 및 신규 투표 관련 예외 추가
투표 영속성 계층 확장
.../out/persistence/VoteCommandPersistenceAdapter.java, .../out/persistence/repository/VoteParticipantJpaRepository.java, .../out/persistence/repository/VoteParticipantQueryRepository.java, .../out/persistence/repository/VoteParticipantQueryRepositoryImpl.java, .../out/jpa/VoteItemJpaEntity.java, .../out/jpa/VoteParticipantJpaEntity.java, .../application/port/out/VoteCommandPort.java
투표/참여자 조회·저장·수정·삭제 로직 및 레포지토리, JPQL 쿼리, 매퍼, 포트 인터페이스 확장
Swagger/예외 코드/유효성 검증 관련
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java, src/main/java/konkuk/thip/user/domain/User.java, src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java, src/main/java/konkuk/thip/room/adapter/out/persistence/RoomParticipantQueryPersistenceAdapter.java
예외 코드 명칭 변경, Swagger 응답 설명 추가, 유효성 검사 메서드 인자 순서 및 메서드명 오타 수정
불필요 클래스 삭제
src/main/java/konkuk/thip/vote/application/port/in/dto/DummyQuery.java, .../dto/DummyResult.java
불필요한 Dummy 클래스 삭제
테스트 코드 추가 및 수정
src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java, .../vote/application/service/VoteServiceTest.java, .../vote/domain/VoteParticipantTest.java, src/test/java/konkuk/thip/common/util/TestEntityFactory.java, src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java
투표 API/도메인/서비스 단위 및 통합 테스트 신설, 테스트 유틸리티 및 기존 테스트 오타 수정

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant VoteCommandController
    participant VoteUseCase (VoteService)
    participant RoomParticipantValidator
    participant VoteCommandPort
    participant DB

    Client->>VoteCommandController: POST /rooms/{roomId}/vote/{voteId} (VoteRequest)
    VoteCommandController->>VoteUseCase: vote(VoteCommand)
    VoteUseCase->>RoomParticipantValidator: validateUserIsRoomMember(userId, roomId)
    RoomParticipantValidator-->>VoteUseCase: (검증 성공/실패)
    VoteUseCase->>VoteCommandPort: findVoteParticipantByUserIdAndVoteId(userId, voteId)
    alt 최초 투표
        VoteCommandPort-->>VoteUseCase: Optional.empty
        VoteUseCase->>VoteCommandPort: saveVoteParticipant
        VoteUseCase->>VoteCommandPort: updateVoteItem (increaseCount)
    else 이미 투표함
        VoteCommandPort-->>VoteUseCase: Optional<VoteParticipant>
        alt 다른 항목에 투표
            VoteUseCase->>VoteCommandPort: updateVoteParticipant
            VoteUseCase->>VoteCommandPort: updateVoteItem (increase/decrease Count)
        else 같은 항목에 투표
            VoteUseCase-->>VoteCommandController: 예외 발생 (중복 투표)
        end
    end
    VoteUseCase-->>VoteCommandController: VoteResult
    VoteCommandController-->>Client: BaseResponse<VoteResponse>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
사용자가 방에 참여중인지 유효성 검증 (#125)
기존 투표되어있는 항목있는지 확인 -> 있는데 투표한 경우 취소하고 투표 (#125)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
없음

Suggested reviewers

  • hd0rable
  • seongjunnoh

Poem

투표 토끼가 깡총깡총,
새로운 API에 설렘을 실어 봐요!
중복 투표엔 "안돼!"라 말하고,
방 멤버 검증도 꼼꼼히 체크해요.
테스트도 빵빵, 예외도 든든,
오늘도 코드밭에 당근 한 아름! 🥕

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 364189b and 924186c.

📒 Files selected for processing (3)
  • src/main/java/konkuk/thip/vote/application/service/VoteService.java (1 hunks)
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/vote/application/service/VoteServiceTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/konkuk/thip/vote/application/service/VoteService.java
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java
  • src/test/java/konkuk/thip/vote/application/service/VoteServiceTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#125-vote

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
src/main/java/konkuk/thip/vote/application/service/dto/VoteResult.java (2)

6-6: type 필드의 의미가 불분명합니다.

Boolean 타입의 type 필드가 무엇을 나타내는지 명확하지 않습니다. 투표/취소를 나타낸다면 enum을 사용하거나 더 명확한 필드명을 고려해보세요.

-        Boolean type
+        VoteType type  // enum VoteType { VOTE, CANCEL }

또는

-        Boolean type
+        Boolean isVoted  // 더 명확한 필드명

8-10: static 팩토리 메서드가 불필요합니다.

Record는 기본적으로 생성자를 제공하므로 단순히 필드를 전달하는 static 팩토리 메서드는 중복입니다.

이 메서드를 제거하고 직접 생성자를 사용하는 것을 고려해보세요:

-    public static VoteResult of(Long voteItemId, Long roomId, Boolean type) {
-        return new VoteResult(voteItemId, roomId, type);
-    }
src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.java (1)

5-9: 계층 간 DTO 분리가 잘 되어 있습니다.

서비스 계층의 VoteResult와 웹 계층의 VoteResponse를 분리한 것이 좋습니다. 다만 VoteResult와 동일하게 type 필드의 의미가 불분명합니다.

src/main/java/konkuk/thip/vote/domain/VoteParticipant.java (1)

27-34: 투표 항목 변경 로직이 올바르게 구현되었습니다.

동일한 항목에 대한 중복 투표를 방지하는 검증 로직이 PR 목표에 맞게 잘 구현되었습니다.

null 안전성을 위해 다음과 같은 개선을 고려해보세요:

 public void changeVoteItem(Long voteItemId) {
+    if (voteItemId == null) {
+        throw new IllegalArgumentException("voteItemId cannot be null");
+    }
     // 같은 항목을 투표하려고 하는 경우 예외처리
-    if(this.voteItemId.equals(voteItemId)) {
+    if(Objects.equals(this.voteItemId, voteItemId)) {
         throw new InvalidStateException(ErrorCode.VOTE_ITEM_ALREADY_VOTED);
     }
     // 투표 항목 변경
     this.voteItemId = voteItemId;
 }
src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java (1)

12-13: JPQL 쿼리 대신 메서드 명명 규칙 사용 검토

Spring Data JPA의 메서드 명명 규칙을 사용하면 @query 어노테이션 없이도 동일한 기능을 구현할 수 있습니다.

-    @Query("SELECT vp FROM VoteParticipantJpaEntity vp WHERE vp.userJpaEntity.userId = :userId AND vp.voteItemJpaEntity.voteItemId = :voteItemId")
-    Optional<VoteParticipantJpaEntity> findVoteParticipantByUserIdAndVoteItemId(Long userId, Long voteItemId);
+    Optional<VoteParticipantJpaEntity> findByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(Long userId, Long voteItemId);

다만 현재 JPQL 쿼리도 명확하고 성능상 차이가 없으므로, 팀의 선호도에 따라 결정하시면 됩니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90e6fa1 and 8ed13d6.

📒 Files selected for processing (25)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (2 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2 hunks)
  • src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java (1 hunks)
  • src/main/java/konkuk/thip/user/domain/User.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java (2 hunks)
  • src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteItemJpaEntity.java (2 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteParticipantJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java (3 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepository.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.java (1 hunks)
  • src/main/java/konkuk/thip/vote/application/port/in/VoteUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (2 hunks)
  • src/main/java/konkuk/thip/vote/application/service/VoteService.java (1 hunks)
  • src/main/java/konkuk/thip/vote/application/service/dto/VoteCommand.java (1 hunks)
  • src/main/java/konkuk/thip/vote/application/service/dto/VoteResult.java (1 hunks)
  • src/main/java/konkuk/thip/vote/domain/VoteItem.java (2 hunks)
  • src/main/java/konkuk/thip/vote/domain/VoteParticipant.java (2 hunks)
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java (2 hunks)
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/vote/application/service/VoteServiceTest.java (1 hunks)
  • src/test/java/konkuk/thip/vote/domain/VoteParticipantTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서 Record와 Vote는 Room에 속하지만 Feed는 Room에 속하지 않는 구조이며, 댓글 작성 시 Record/Vote에 대해서만 사용자가 해당 Room의 참가자인지 검증이 필요하다.
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#75
File: src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java:50-83
Timestamp: 2025-07-14T14:19:38.796Z
Learning: Vote와 VoteItem 엔티티는 자주 함께 사용되므로, N+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 API 등에서도 함께 사용될 가능성이 높습니다.
📚 Learning: vote와 voteitem 엔티티는 자주 함께 사용되므로, n+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 api 등에서...
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#75
File: src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java:50-83
Timestamp: 2025-07-14T14:19:38.796Z
Learning: Vote와 VoteItem 엔티티는 자주 함께 사용되므로, N+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 API 등에서도 함께 사용될 가능성이 높습니다.

Applied to files:

  • src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteParticipantJpaEntity.java
  • src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteItemJpaEntity.java
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java
  • src/test/java/konkuk/thip/vote/domain/VoteParticipantTest.java
  • src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.java
  • src/main/java/konkuk/thip/vote/application/service/dto/VoteResult.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepository.java
  • src/main/java/konkuk/thip/vote/application/service/VoteService.java
  • src/main/java/konkuk/thip/vote/domain/VoteParticipant.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java
  • src/main/java/konkuk/thip/vote/domain/VoteItem.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.java
  • src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.java
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java
  • src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
📚 Learning: thip 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 roomparticipantpolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 ...
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 RoomParticipantPolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 방식을 선호한다.

Applied to files:

  • src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java
  • src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java
📚 Learning: spring data jpa에서 findby{fieldname} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, optional 반환 타입을 사용하는 것이 nu...
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional<Entity> 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.

Applied to files:

  • src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
📚 Learning: thip 프로젝트에서 record와 vote는 room에 속하지만 feed는 room에 속하지 않는 구조이며, 댓글 작성 시 record/vote에 대해서만 사용자가 해당 room...
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서 Record와 Vote는 Room에 속하지만 Feed는 Room에 속하지 않는 구조이며, 댓글 작성 시 Record/Vote에 대해서만 사용자가 해당 Room의 참가자인지 검증이 필요하다.

Applied to files:

  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
  • src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java
📚 Learning: commentcontrollertest는 댓글 생성 api의 검증 로직과 예외 상황만을 테스트하는 단위 테스트이며, 성공 케이스는 별도의 통합 테스트(commentcreateapi...
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#101
File: src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java:118-265
Timestamp: 2025-07-23T17:41:55.507Z
Learning: CommentControllerTest는 댓글 생성 API의 검증 로직과 예외 상황만을 테스트하는 단위 테스트이며, 성공 케이스는 별도의 통합 테스트(CommentCreateAPITest)에서 다룬다.

Applied to files:

  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java
📚 Learning: thip 프로젝트에서는 cqrs port 분리 시 다음 컨벤션을 따름: commandport에는 findbyxxx를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, querypo...
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.

Applied to files:

  • src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java
🧬 Code Graph Analysis (1)
src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.java (1)
src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteCreateRequest.java (1)
  • VoteCreateRequest (12-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (36)
src/main/java/konkuk/thip/user/domain/User.java (1)

47-47: 에러 코드명 개선이 적절합니다.

FOLLOW_COUNT_IS_ZERO에서 FOLLOW_COUNT_CANNOT_BE_NEGATIVE로 변경하여 실제 검증 목적(음수 방지)을 더 명확하게 표현합니다.

src/main/java/konkuk/thip/vote/application/port/in/VoteUseCase.java (1)

6-8: 클린 아키텍처 원칙을 잘 따른 인터페이스입니다.

단일 책임 원칙을 따르며, 명확한 메서드 시그니처를 가지고 있습니다.

src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java (1)

17-17: 매개변수 순서 수정이 올바릅니다.

existByUserIdAndRoomId 호출 시 매개변수 순서를 올바르게 수정했습니다. 이는 사용자 방 참여 검증 로직의 핵심적인 버그 수정입니다.

src/test/java/konkuk/thip/common/util/TestEntityFactory.java (2)

24-26: 필요한 import 추가가 적절합니다.

투표 참가자 관련 엔티티 import가 새로운 팩토리 메서드를 위해 올바르게 추가되었습니다.


298-303: 팩토리 메서드 구현이 일관성 있고 올바릅니다.

createVoteParticipant 메서드가 기존 팩토리 패턴과 일치하게 구현되었으며, 테스트에서 투표 참가자 엔티티 생성을 위한 유틸리티로 적절합니다.

src/main/java/konkuk/thip/vote/domain/VoteItem.java (3)

4-5: 도메인 예외 처리를 위한 적절한 import 추가

도메인 비즈니스 규칙 위반 시 예외 처리를 위한 import가 올바르게 추가되었습니다.


80-82: 투표 수 증가 로직이 올바릅니다.

투표 항목의 카운트를 증가시키는 간단하고 명확한 구현입니다.


84-89: 투표 수 감소 시 유효성 검증이 적절합니다.

음수 카운트를 방지하는 검증 로직이 포함되어 있어 도메인 무결성을 보장합니다. 예외 처리도 적절하게 구현되었습니다.

src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepository.java (1)

7-10: 리포지토리 인터페이스 설계가 적절합니다.

Optional을 사용한 쿼리 메서드 시그니처가 적절하며, 메서드명이 명확하고 Spring Data 패턴을 잘 따르고 있습니다.

src/test/java/konkuk/thip/vote/domain/VoteParticipantTest.java (2)

14-26: 동일한 투표 항목 변경 시 예외 처리 테스트가 적절합니다.

같은 voteItemId로 변경 시 예외가 발생하는 비즈니스 규칙을 올바르게 검증하고 있습니다. 테스트 구조와 assertion이 명확합니다.


28-42: 다른 투표 항목으로 변경 성공 테스트가 올바릅니다.

정상적인 투표 항목 변경 시나리오를 적절히 검증하고 있으며, given-when-then 구조가 명확합니다.

src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java (1)

13-17: JPQL 전환이 잘 되었습니다.

네이티브 SQL에서 JPQL로의 전환이 적절하게 수행되었습니다. 엔티티 필드 참조 사용으로 타입 안전성과 리팩토링 지원이 개선되었습니다.

src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.java (1)

10-12: 적절한 팩토리 메서드입니다.

계층 간 변환을 위한 static 팩토리 메서드가 적절하게 구현되었습니다.

src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)

46-46: 에러 코드 이름 변경이 적절합니다.

FOLLOW_COUNT_IS_ZERO에서 FOLLOW_COUNT_CANNOT_BE_NEGATIVE로의 변경이 더 명확하고 정확한 의미를 전달합니다.


135-141: 투표 관련 에러 코드가 포괄적으로 정의되었습니다.

투표 기능의 주요 에러 시나리오들이 잘 정의되어 있습니다:

  • 방 접근 권한 검증
  • 투표 항목 존재 여부
  • 중복 투표 방지
  • 투표 취소 시 검증
  • 카운트 음수 방지
src/main/java/konkuk/thip/vote/domain/VoteParticipant.java (1)

19-25: 도메인 팩토리 메서드가 잘 구현되었습니다.

ID 없이 인스턴스를 생성하는 팩토리 메서드가 도메인 주도 설계 원칙에 맞게 구현되었습니다.

src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.java (1)

25-42: 구현이 잘 되었습니다!

N+1 문제를 방지하기 위한 fetch join 사용과 ACTIVE 상태 필터링이 적절합니다. 사용자의 투표 참여 정보를 효율적으로 조회할 수 있는 쿼리입니다.

src/test/java/konkuk/thip/vote/application/service/VoteServiceTest.java (1)

35-125: 테스트 구현이 우수합니다!

PR에서 요청한 복잡한 상태 검증 및 업데이트 로직이 모두 적절히 테스트되고 있습니다. 첫 투표, 투표 변경, 중복 투표, 투표 취소 등 모든 주요 시나리오가 커버되어 있으며, 예외 상황도 잘 검증하고 있습니다.

src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.java (1)

10-27: 요청 DTO 구현이 적절합니다!

투표/취소를 Boolean type으로 구분하는 방식이 명확하고, 필수 필드 검증과 Swagger 문서화가 잘 되어 있습니다.

src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java (1)

47-61: 투표 API 엔드포인트 구현이 적절합니다.

RESTful한 경로 설계와 기존 컨트롤러 패턴을 잘 따르고 있습니다. 사용자 인증, 검증, 예외 처리 설명이 모두 적절히 구성되어 있습니다.

src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (2)

53-53: 에러 코드 네이밍 개선이 적절합니다.

FOLLOW_COUNT_IS_ZERO에서 FOLLOW_COUNT_CANNOT_BE_NEGATIVE로 변경하여 의미가 더 명확해졌습니다.


107-115: 투표 관련 에러 코드가 체계적으로 추가되었습니다.

새로운 에러 코드들이 기존 패턴을 잘 따르고 있으며, HTTP 상태 코드와 메시지가 적절합니다. 에러 코드 번호 체계(120000대, 125000대)도 일관성을 유지하고 있습니다.

src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1)

35-45: VoteParticipant 관련 메서드들이 잘 설계되었습니다.

CQRS 패턴을 잘 따르고 있으며, 사용자-투표 및 사용자-투표항목 조합으로 조회하는 메서드들이 비즈니스 요구사항을 적절히 반영하고 있습니다.

src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java (7)

66-75: 테스트 설정이 체계적으로 구성되었습니다.

필요한 모든 엔티티(사용자, 방, 투표 등)를 적절히 설정하고 있으며, TestEntityFactory를 활용한 테스트 데이터 생성이 일관성 있게 처리되었습니다.


77-102: 첫 투표 시나리오 테스트가 완벽합니다.

API 응답 검증과 함께 데이터베이스 상태 변경(VoteParticipant 생성, VoteItem count 증가)을 모두 확인하여 통합 테스트로서 적절한 검증 범위를 제공합니다.


104-132: 투표 변경 시나리오 테스트가 비즈니스 로직을 정확히 검증합니다.

이전 투표 항목의 count 감소와 새로운 투표 항목으로의 변경을 모두 확인하여 복잡한 비즈니스 로직이 올바르게 동작함을 검증하고 있습니다.


134-151: 중복 투표 방지 테스트가 적절합니다.

동일한 항목에 재투표 시 적절한 에러 코드(VOTE_ITEM_ALREADY_VOTED)로 응답하는지 확인하여 비즈니스 규칙을 잘 검증하고 있습니다.


153-173: 투표 취소 시나리오 테스트가 완벽합니다.

VoteParticipant 삭제와 VoteItem count 감소를 모두 확인하여 투표 취소 로직이 정확히 구현되었음을 검증합니다.


175-190: 투표 취소 실패 시나리오 테스트가 적절합니다.

투표 기록이 없는 상태에서 취소 시도 시 적절한 에러 코드(VOTE_ITEM_NOT_VOTED_CANNOT_CANCEL)로 응답하는지 확인합니다.


192-208: 권한 검증 테스트가 보안 요구사항을 잘 반영합니다.

방에 속하지 않은 사용자의 투표 시도를 차단하고 적절한 에러 코드(ROOM_ACCESS_FORBIDDEN)로 응답하는지 확인하여 보안성을 검증합니다.

src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java (6)

78-82: VoteItem 조회 메서드가 깔끔하게 구현되었습니다.

기존 패턴을 잘 따르고 있으며, Optional을 활용한 null 안전성도 적절히 처리되었습니다.


84-94: VoteParticipant 조회 메서드들이 비즈니스 요구사항을 잘 반영합니다.

사용자-투표 조합과 사용자-투표항목 조합으로 조회하는 두 가지 메서드가 투표 비즈니스 로직의 복잡성을 적절히 지원합니다.


96-107: VoteParticipant 업데이트 로직이 안전하게 구현되었습니다.

엔티티 존재 여부 검증과 함께 투표 항목 변경 로직이 적절히 처리되어 있습니다. 에러 처리도 명확합니다.


109-121: VoteParticipant 저장 로직이 관련 엔티티를 충분히 검증합니다.

사용자와 투표 항목의 존재 여부를 모두 확인한 후 저장하여 데이터 무결성을 보장합니다.


123-127: VoteParticipant 삭제 로직이 효율적입니다.

주석에서 명시한 대로 앞선 검증을 통해 존재 여부가 확인된 상태이므로 ID로 직접 삭제하는 것이 효율적입니다.


129-136: VoteItem 업데이트 로직이 적절합니다.

엔티티 존재 여부 검증과 함께 updateFrom 메서드를 활용한 업데이트 패턴이 기존 코드와 일관성을 유지합니다.

Copy link
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

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

LGTM 고생하셨습니다! 코드 깔끔하네요!!

Comment on lines +30 to +33
public VoteParticipantJpaEntity updateVoteItem(VoteItemJpaEntity voteItemJpaEntity) {
this.voteItemJpaEntity = voteItemJpaEntity;
return this;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

굳굳

Comment on lines +80 to +89
public void increaseCount() {
this.count++;
}

public void decreaseCount() {
if(this.count == 0) {
throw new InvalidStateException(ErrorCode.VOTE_ITEM_COUNT_CANNOT_BE_NEGATIVE);
}
this.count--;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

Comment on lines +15 to +17
@Service
@RequiredArgsConstructor
public class VoteService implements VoteUseCase {
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM 로직 가독성 좋네요

Comment on lines -93 to +98
"https://thip-git-develop-thips-projects.vercel.app"
)); // 배포 시 도메인 명시
webUrl
));
Copy link
Collaborator

Choose a reason for hiding this comment

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

굳굳 FE 배포 url을 명시하는 것을 고민했었는데, yml에 숨기는것도 좋은거같습니다

Comment on lines +13 to +16
@Query("SELECT rp FROM RoomParticipantJpaEntity rp " +
"WHERE rp.userJpaEntity.userId = :userId " +
"AND rp.roomJpaEntity.roomId = :roomId " +
"AND rp.status = 'ACTIVE'")
Copy link
Collaborator

Choose a reason for hiding this comment

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

굳굳

Comment on lines +36 to +38
Optional<VoteParticipant> findVoteParticipantByUserIdAndVoteId(Long userId, Long voteId);

Optional<VoteParticipant> findVoteParticipantByUserIdAndVoteItemId(Long userId, Long voteItemId);
Copy link
Collaborator

Choose a reason for hiding this comment

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

굳굳 확실히 Optional을 반환하고, service에서 ifPresentOrElse 로 분기처리하니 가독성이 훨씬 올라가는 것 같습니다

Comment on lines +27 to +34
public void changeVoteItem(Long voteItemId) {
// 같은 항목을 투표하려고 하는 경우 예외처리
if(this.voteItemId.equals(voteItemId)) {
throw new InvalidStateException(ErrorCode.VOTE_ITEM_ALREADY_VOTED);
}
// 투표 항목 변경
this.voteItemId = voteItemId;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

같은 항목을 투표하는 요청이 올 경우 404 error 를 던지는게 맞나? 싶어서 생각 좀 해봤는데, 이 경우 굳이 이후의

유저가 이전에 투표한 기록 삭제 -> 이전에 투표한 아이템의 count-- -> 유저가 새로 투표한 기록(= 사실 같은 투표 아이템) 저장 -> 유저가 새로 투표한 아이템(= 사실 같은 투표 아이템)의 count++

의 과정을 진행할 필요없이 404 error를 던지고, FE 단에서 분기처리를 하는게 훨씬 효율적일 거 같네요!! 좋습니다!!
관련해서 FE 분들에게 언급하면 좋을 것 같습니다!

Comment on lines +136 to +137
@Test
@DisplayName("같은 항목으로 다시 투표 시 예외")
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

Comment on lines +40 to +45
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureMockMvc(addFilters = false)
@Transactional
@DisplayName("[통합] 투표하기 API 통합 테스트")
class VoteApiTest {
Copy link
Collaborator

Choose a reason for hiding this comment

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

통합 테스트 케이스 좋네요!! 가독성 굳굳

Comment on lines +43 to +47
// when
when(voteCommandPort.findVoteParticipantByUserIdAndVoteId(1L, 1L))
.thenReturn(Optional.empty());
when(voteCommandPort.getVoteItemByIdOrThrow(100L)).thenReturn(voteItem);
VoteResult result = voteService.vote(command);
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM 모킹을 활용해 서비스 단위테스트 좋습니다

@buzz0331 buzz0331 merged commit a64004d into develop Aug 2, 2025
2 checks passed
@buzz0331 buzz0331 deleted the feat/#125-vote branch August 2, 2025 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-174] [feat] 투표하기 api 개발

2 participants