Skip to content

[feat] 투표 삭제 api 개발#192

Merged
seongjunnoh merged 27 commits intodevelopfrom
feat/#188-vote-delete
Aug 14, 2025
Merged

[feat] 투표 삭제 api 개발#192
seongjunnoh merged 27 commits intodevelopfrom
feat/#188-vote-delete

Conversation

@hd0rable
Copy link
Member

@hd0rable hd0rable commented Aug 11, 2025

#️⃣ 연관된 이슈

closes #188

📝 작업 내용

  • 투표 삭제 api를 개발했습니다.
  • 투표 삭제 api 흐름은 다음과 같습니다.
  • 컨트롤러 에서 커맨드로 변환 -> 투표 삭제 서비스 진입 -> 방 참여자인지 검증, 투표 조회 및 검증, 투표가 해당 방에 대한 투표인지 검증, 투표 작성자인지 검증 -> 투표 삭제 -> 방 아이디 반환 -> 응답
  • 관련 통합,단위 테스트 코드 작성했습니다

📸 스크린샷

💬 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

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

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

Summary by CodeRabbit

  • New Features
    • 투표 삭제 API 추가(모임/투표 경로). 삭제 시 댓글 소프트 삭제 및 좋아요·투표 항목·참여자 등 관련 데이터 정리.
    • 투표 접근 권한 오류 코드(403) 추가로 삭제 권한 검증 강화.
  • Documentation
    • 투표 삭제에 대한 API 문서/응답 설명 추가.
  • Bug Fixes
    • 비활성화된 피드·기록·투표에 대한 업데이트/삭제/저장 차단으로 예외 처리 일관성 강화.
  • Tests
    • 투표 삭제 통합/단위 테스트 추가 및 관련 테스트 검증 확장(연관 엔티티 삭제 검증).
  • Chores
    • 배포 워크플로가 develop 브랜치 푸시에도 실행되도록 확장.

@github-actions
Copy link

github-actions bot commented Aug 11, 2025

Test Results

397 tests   397 ✅  30s ⏱️
119 suites    0 💤
119 files      0 ❌

Results for commit 05f4e53.

♻️ This comment has been updated with latest results.

@coderabbitai
Copy link

coderabbitai bot commented Aug 11, 2025

Walkthrough

투표 삭제 API와 관련 UseCase/Service/컨트롤러/응답/영속 로직이 추가되었고, 도메인 권한 검증(validateDeletable)과 삭제 시 연관 엔티티(댓글 소프트삭제, 좋아요·아이템·참여자 하드삭제) 처리가 구현되었습니다. 에러코드 및 Swagger 응답, 일부 모듈의 ACTIVE 조회 강제화와 CI 트리거가 확장되었습니다.

Changes

Cohort / File(s) Summary
Error/Swagger additions
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java, src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
VOTE_ACCESS_FORBIDDEN(403) 에러코드 추가 및 Swagger에 VOTE_DELETE 응답 설명 항목 추가.
Vote delete: API / DTO / Response
src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java, .../vote/adapter/in/web/response/VoteDeleteResponse.java, .../vote/application/port/in/dto/VoteDeleteCommand.java, .../vote/application/port/in/VoteDeleteUseCase.java
DELETE 엔드포인트와 Request/Response/Command/UseCase 시그니처 추가; 컨트롤러에서 UseCase 호출로 응답 반환.
Vote delete: Service / Domain
src/main/java/konkuk/thip/vote/application/service/VoteDeleteService.java, src/main/java/konkuk/thip/vote/domain/Vote.java
삭제 비즈니스 로직 구현(멤버 검증, validateDeletable, 연관 엔티티 삭제 순서) 및 도메인 권한 검증 메서드 추가.
Vote persistence & repos
.../vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java, .../vote/adapter/out/persistence/repository/VoteJpaRepository.java, .../vote/adapter/out/persistence/repository/VoteItemJpaRepository.java, .../vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java, .../vote/adapter/out/jpa/VoteJpaEntity.java
ACTIVE 상태 기반 조회(findByPostIdAndStatus) 도입, VoteCommandPersistenceAdapter에 delete(Vote) 구현(참여자/아이템 삭제 및 엔티티 softDelete), VoteItem/Participant용 bulk delete 쿼리 추가, 테스트용 updateCommentCount 추가.
Feed / Record ACTIVE lookups
src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java, src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java
update/save/delete에서 findByPostIdAndStatus(..., ACTIVE) 사용으로 ACTIVE 전제 조회 적용.
Tests
src/test/java/konkuk/thip/vote/adapter/in/web/VoteDeleteAPITest.java, src/test/java/konkuk/thip/vote/domain/VoteTest.java, src/test/java/konkuk/thip/common/util/TestEntityFactory.java, src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java, src/test/java/konkuk/thip/record/adapter/in/web/RecordDeleteAPITest.java, src/test/java/konkuk/thip/record/domain/RecordTest.java
투표 삭제 통합 테스트 추가 및 도메인 validateDeletable 관련 단위 테스트 추가. 테스트 유틸 VoteItem 생성기 추가 및 일부 삭제 테스트에 PostLike 검증 보강/표기 수정.
CI/CD
.github/workflows/cd-workflow-prod.yml
push 이벤트 대상 브랜치에 develop 추가.

Sequence Diagram(s)

sequenceDiagram
  actor Client
  participant Controller as VoteCommandController
  participant Service as VoteDeleteService
  participant Validator as RoomParticipantValidator
  participant VotePort as VoteCommandPort
  participant CommentPort as CommentCommandPort
  participant LikePort as PostLikeCommandPort
  participant Repo as VoteItem/Participant Repos

  Client->>Controller: DELETE /rooms/{roomId}/vote/{voteId} (userId)
  Controller->>Service: deleteVote(command)
  Service->>Validator: validateMember(userId, roomId)
  Service->>VotePort: findById(voteId) [ACTIVE]
  Service->>Service: vote.validateDeletable(userId, roomId)
  Service->>CommentPort: softDeleteAllByPostId(voteId)
  Service->>LikePort: deleteAllByPostId(voteId)
  Service->>Repo: deleteAllByVoteId(voteId)
  Service->>VotePort: delete(vote)
  Service-->>Controller: roomId
  Controller-->>Client: 200 OK + VoteDeleteResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
투표 삭제 API 개발 (#188)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Feed ACTIVE 조회로 변경 (src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java) 투표 삭제 이슈의 직접적 요구사항 아님 — 피드 모듈의 조회 정책 변경.
Record ACTIVE 조회로 변경 (src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java) 투표 삭제 요구와 무관한 기록 모듈의 조회 정책 변경.
CD workflow 브랜치 트리거에 develop 추가 (.github/workflows/cd-workflow-prod.yml) 기능 개발 이슈(#188)와 관련 없는 CI 설정 변경.
Feed/Record 삭제 테스트에 PostLike 검증 추가 (src/test/.../FeedDeleteAPITest.java, src/test/.../RecordDeleteAPITest.java) 투표 삭제 기능 범위 외의 테스트 강화 변경.
VoteJpaEntity 테스트용 updateCommentCount 추가 (src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java) 테스트 헬퍼 메서드 추가로, 투표 삭제 API의 구현 요구와 직접 연관되지 않음.

Possibly related PRs

Suggested labels

🧸 현준

Suggested reviewers

  • buzz0331

Poem

깡총, 깡총! 투표 하나 사라졌네, 휙!
나는 토끼, 권한 쓱쓱 검사했지, 휙휙!
댓글은 조용히 잠들고, 좋아요는 바람에 흩어져,
방 번호 하나 남겨두고 깔끔하게 끝! 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3cefc and 05f4e53.

📒 Files selected for processing (4)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java (3 hunks)
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java
  • src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.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/#188-vote-delete

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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: 3

🧹 Nitpick comments (16)
src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1)

126-127: PostLike 삭제 검증 추가는 적절함 — 범위 한정 검증도 고려

전반 삭제 시맨틱을 더 확실히 보장하는 좋은 보강입니다. 다만 count()는 전역 카운트이므로, 향후 테스트 병렬화/데이터 공유 시 취약할 수 있습니다. 가능하면 대상 feedId 기준으로 존재 여부를 확인하는 쿼리(예: existsByPostId 또는 countByPostId)를 사용하는 방식을 고려해 주세요.

src/test/java/konkuk/thip/record/adapter/in/web/RecordDeleteAPITest.java (1)

106-108: PostLike 삭제 검증 추가 좋음 — 테스트 이름 업데이트 제안

삭제 범위 보장이 강화되었습니다. 현재 DisplayName은 댓글/댓글 좋아요만 언급하고 있어, PostLike 삭제까지 포함하도록 테스트 이름을 업데이트하면 더 명확합니다.

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

17-19: voteId vs postId 명칭 혼동 가능 — 파라미터/쿼리 이름 정교화 제안

쿼리는 postId와 비교하지만 파라미터 이름은 voteId라 혼동 여지가 있습니다. 일관성을 위해 postId로 명명 변경을 권장합니다. 또한 벌크 삭제는 영속성 컨텍스트를 우회하므로 clearAutomatically 설정은 적절합니다(이미 적용됨).

제안 diff:

-    @Modifying(clearAutomatically = true, flushAutomatically = true)
-    @Query("DELETE FROM VoteParticipantJpaEntity vp WHERE vp.voteItemJpaEntity.voteJpaEntity.postId = :voteId")
-    void deleteAllByVoteId(@Param("voteId") Long voteId);
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Query("DELETE FROM VoteParticipantJpaEntity vp WHERE vp.voteItemJpaEntity.voteJpaEntity.postId = :postId")
+    void deleteAllByVoteId(@Param("postId") Long postId);
src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1)

48-48: 포트 메서드 의미 명확화 권장

delete가 soft delete인지 hard delete인지 메서드명 또는 Javadoc으로 명시하면 상위/하위 계층에서의 오해를 줄일 수 있습니다. 예: softDelete(Vote vote) 또는 "Soft delete the vote" 주석.

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

5-7: 반환값 의미를 Javadoc으로 명시해 주세요.

deleteVote가 Long을 반환하는데, roomId를 의미한다는 점을 인터페이스 레벨에서 JavaDoc으로 명확히 해두면 상위/하위 계층 모두에 도움이 됩니다.

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

179-186: 양방향 연관관계 동기화와 테스트 편의성 개선 제안

  • VoteJpaEntity가 VoteItem 컬렉션을 보유하는 양방향 매핑이라면, 테스트 안정성을 위해 부모 컬렉션에도 추가하는 편이 안전합니다. 예: vote.getVoteItemList().add(item)
  • 기본 count=0 고정은 합리적이지만, 케이스에 따라 초기 count가 다른 시나리오가 있을 수 있으므로 오버로드 제공도 고려해 보세요.

아래는 컬렉션 동기화(존재 시)의 예시입니다.

 public static VoteItemJpaEntity createVoteItem(String itemName, VoteJpaEntity vote) {
-    return VoteItemJpaEntity.builder()
+    VoteItemJpaEntity item = VoteItemJpaEntity.builder()
             .itemName(itemName)
             .count(0)
             .voteJpaEntity(vote)
             .build();
+    // 양방향 매핑일 경우에만
+    if (vote.getVoteItemList() != null) {
+        vote.getVoteItemList().add(item);
+    }
+    return item;
 }

양방향 컬렉션 필드가 실제 존재하는지 확인이 필요하면 알려주세요. 원하시면 해당 방향으로 유틸을 리팩터링해 드릴 수 있습니다.

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

3-10: 커맨드 필드에 Bean Validation 추가 제안 (@NotNull)

입력 유효성 보장을 위해 각 컴포넌트에 @NotNull을 부여하는 것을 권장합니다. 컨트롤러에서 @validated와 함께 사용하면 바인딩 단계에서 조기 검증 가능합니다.

 package konkuk.thip.vote.application.port.in.dto;
 
+import jakarta.validation.constraints.NotNull;
+
 public record VoteDeleteCommand(
-        Long roomId,
-
-        Long voteId,
-
-        Long userId
+        @NotNull Long roomId,
+        @NotNull Long voteId,
+        @NotNull Long userId
 ) {
 }

컨트롤러 메서드/클래스에 @validated 적용 여부도 함께 확인 부탁드립니다.

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

156-160: VOTE_DELETE 오류 코드 범위 재확인 요청

현재 정의는 ROOM_ACCESS_FORBIDDEN, VOTE_NOT_FOUND, VOTE_ACCESS_FORBIDDEN입니다. 삭제 플로우에서 방 자체가 존재하지 않는 경우를 별도로 노출한다면 ROOM_NOT_FOUND 포함 여부를 검토해 주세요. 동일한 패턴의 RECORD_DELETE에는 ROOM_NOT_FOUND가 포함되어 있지 않아 일관성을 우선한다면 지금 상태도 합리적입니다. 다만 실제 서비스 정책과 정합성만 재확인해 주세요.

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

3-7: 간결하고 명확합니다.

응답 모델로 충분히 적절합니다. 필요하다면 Swagger 문서화를 위해 @Schema(description = "...") 추가를 고려해도 좋습니다.

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

15-17: 트랜잭션 경계 보장 및 삭제 건수 반환 고려

  • @Modifying(clearAutomatically = true, flushAutomatically = true)는 적절합니다. 다만 서비스/어댑터 상위에서 @transactional(READ_ONLY=false)로 묶여야 부분 성공을 방지할 수 있습니다. 삭제 유스케이스 메서드에 @transactional 추가 여부를 확인해 주세요.
  • 운영 관점에서 삭제 건수를 받아 로깅/검증에 활용하려면 반환형을 int로 변경하는 것도 고려해 볼 만합니다.
  • 또한 파라미터명이 voteId이지만 실제로는 vi.voteJpaEntity.postId를 기준으로 삭제하므로, 메서드명/파라미터명을 deleteAllByVotePostId(Long votePostId) 등으로 더 구체화하면 혼동을 줄일 수 있습니다.

예시:

-@Query("DELETE FROM VoteItemJpaEntity vi WHERE vi.voteJpaEntity.postId = :voteId")
-void deleteAllByVoteId(@Param("voteId") Long voteId);
+@Query("DELETE FROM VoteItemJpaEntity vi WHERE vi.voteJpaEntity.postId = :votePostId")
+int deleteAllByVoteId(@Param("votePostId") Long votePostId);

원하시면 VoteParticipantJpaRepository 등 연관 리포지토리도 동일 패턴으로 정리해 드리겠습니다.

src/main/java/konkuk/thip/vote/application/service/VoteDeleteService.java (2)

34-35: 코드 스타일: 인자 사이 공백

가독성을 위해 공백 추가를 권장합니다.

-        vote.validateDeletable(command.userId(),command.roomId());
+        vote.validateDeletable(command.userId(), command.roomId());

37-43: 삭제 책임의 계층화 명확화(서비스 vs 어댑터)

현 구조는 서비스에서 댓글/게시글 좋아요 삭제, 어댑터에서 투표항목/투표참여 삭제를 나눠 처리합니다. 팀 합의된 경계라면 OK지만, 유지보수성 측면에서 “한 지점에서 일관되게 연쇄 삭제”가 이뤄지도록 응집시키는 것도 고려할 만합니다(예: 어댑터에서 부모 삭제 시 자식/연관 모두 정리).

원칙 합의가 없다면 Javadoc/클래스-level 주석으로 삭제 책임의 분리를 문서화해두는 것도 좋습니다.

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

182-190: 가독성: 메서드 인자 공백

테스트 호출부의 인자 사이 공백을 통일해 주세요.

-                () -> vote.validateDeletable(OTHER_USER_ID,ROOM_ID));
+                () -> vote.validateDeletable(OTHER_USER_ID, ROOM_ID));
...
-        assertDoesNotThrow(() -> vote.validateDeletable(CREATOR_ID,ROOM_ID));
+        assertDoesNotThrow(() -> vote.validateDeletable(CREATOR_ID, ROOM_ID));

Also applies to: 202-207


192-200: 표기 수정: 띄어쓰기

DisplayName 문구에서 “일치하지않은” → “일치하지 않은”으로 수정하면 자연스럽습니다.

-    @DisplayName("validateDeletable: 전달된 roomId가 투표의 roomId와 일치하지않은 경우 투표를 삭제하려고 하면 InvalidStateException이 발생한다.")
+    @DisplayName("validateDeletable: 전달된 roomId가 투표의 roomId와 일치하지 않은 경우 투표를 삭제하려고 하면 InvalidStateException이 발생한다.")
src/test/java/konkuk/thip/vote/adapter/in/web/VoteDeleteAPITest.java (1)

118-136: 검증 보강(선택): 응답 페이로드 확인

상태 코드만 검증 중입니다. BaseResponse에 roomId가 정상 반환되는지도 함께 검증하면 회귀에 강합니다.

원하시면 MockMvc로 JSON path 검증 코드(예: $.data.roomId)가 포함된 예제를 드리겠습니다.

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

22-23: Swagger 예외 기술 상수 혼용: RECORD_DELETE → VOTE_DELETE 제안

삭제 API가 “투표”이므로 Swagger 예외 기술 상수는 VOTE_DELETE가 더 적합해 보입니다. 문서 일관성을 위해 교체를 제안합니다.

-import static konkuk.thip.common.swagger.SwaggerResponseDescription.RECORD_DELETE;
+import static konkuk.thip.common.swagger.SwaggerResponseDescription.VOTE_DELETE;
...
-    @ExceptionDescription(RECORD_DELETE)
+    @ExceptionDescription(VOTE_DELETE)

Also applies to: 69-71

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd6891 and b4b564d.

📒 Files selected for processing (21)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java (3 hunks)
  • src/main/java/konkuk/thip/vote/adapter/in/web/response/DummyResponse.java (0 hunks)
  • src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteDeleteResponse.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.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/VoteItemJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java (2 hunks)
  • src/main/java/konkuk/thip/vote/application/port/in/VoteDeleteUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/vote/application/port/in/dto/VoteDeleteCommand.java (1 hunks)
  • src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/vote/application/service/VoteDeleteService.java (1 hunks)
  • src/main/java/konkuk/thip/vote/domain/Vote.java (1 hunks)
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1 hunks)
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1 hunks)
  • src/test/java/konkuk/thip/record/adapter/in/web/RecordDeleteAPITest.java (1 hunks)
  • src/test/java/konkuk/thip/record/domain/RecordTest.java (1 hunks)
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteDeleteAPITest.java (1 hunks)
  • src/test/java/konkuk/thip/vote/domain/VoteTest.java (5 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/konkuk/thip/vote/adapter/in/web/response/DummyResponse.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-26T06:09:00.850Z
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/test/java/konkuk/thip/record/domain/RecordTest.java
📚 Learning: 2025-07-14T14:19:38.796Z
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/test/java/konkuk/thip/common/util/TestEntityFactory.java
🧬 Code Graph Analysis (3)
src/test/java/konkuk/thip/vote/adapter/in/web/VoteDeleteAPITest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (35-369)
src/test/java/konkuk/thip/record/domain/RecordTest.java (2)
src/test/java/konkuk/thip/vote/domain/VoteTest.java (1)
  • DisplayName (12-209)
src/test/java/konkuk/thip/feed/domain/FeedTest.java (1)
  • DisplayName (18-367)
src/test/java/konkuk/thip/vote/domain/VoteTest.java (1)
src/test/java/konkuk/thip/record/domain/RecordTest.java (1)
  • DisplayName (12-209)
🔇 Additional comments (9)
src/test/java/konkuk/thip/record/domain/RecordTest.java (1)

203-203: 표기 정합성 수정 LGTM

"피드" → "기록"으로 용어를 맞춘 점 좋습니다. 도메인 규칙(Record/Vote는 Room 소속)과도 일치합니다.

src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1)

121-121: 직접 카운트 검증으로 간결화 👍

임시 변수 없이 count()로 바로 검증해 가독성이 좋아졌습니다.

src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java (1)

52-55: 테스트 지원용 setter 추가 타당

updateLikeCount와 대칭적으로 updateCommentCount(@VisibleForTesting) 추가는 테스트 셋업 편의에 유용하며, 운영 코드 영향이 없습니다.

src/test/java/konkuk/thip/record/adapter/in/web/RecordDeleteAPITest.java (1)

104-104: 간결한 카운트 검증 LGTM

불필요한 로컬 변수 제거로 테스트가 더 명확해졌습니다.

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

9-10: 상태 기준 조회 추가 LGTM

postId+status 기반 조회는 소프트 삭제 시나리오에 필수적이며 Optional 반환으로 안정적입니다.

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

102-103: 에러 코드 추가 LGTM

VOTE_ACCESS_FORBIDDEN(403, 110003) 추가가 도메인 검증/Swagger 설명과 일관됩니다. FEED/RECORD의 접근 금지 코드와도 패턴이 맞습니다.

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

139-150: Vote 삭제 트랜잭션 처리 및 댓글 고아 레코드 방지 보완 필요

  • Vote 삭제 흐름(PostLike → VoteParticipant → VoteItem → softDelete)은 원자적 처리가 필요하므로, 서비스 레이어 메서드에 @Transactional을 추가하세요.
  • 현재 VoteDeleteService에서는 PostLike만 삭제하고 댓글(Comment)은 누락되어 있어, Vote 관련 댓글이 고아 레코드로 남을 수 있습니다. commentCommandPort.deleteAllByPostId(command.voteId()); 호출을 추가해 주세요.

대상 파일:

  • src/main/java/konkuk/thip/vote/application/service/VoteDeleteService.java
+ import org.springframework.transaction.annotation.Transactional;
  ...
  @Override
+ @Transactional
  public void delete(DeleteVoteCommand command) {
      postLikeCommandPort.deleteAllByPostId(command.voteId());
+     commentCommandPort.deleteAllByPostId(command.voteId());
      // 3-3. 투표 삭제
      voteCommandPort.delete(vote);
  }

Likely an incorrect or invalid review comment.

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

94-103: Vote.java 필드의 null 허용 여부를 확인해 주세요. JPA 매핑(@Column(nullable = false)) 혹은 생성 로직에서 creatorId/roomId가 절대 null이 아님을 보장한다면 현재 equals 사용도 안전할 수 있습니다. 그렇지 않다면 제안하신 대로 Objects.equals로 변경해 NPE를 방지하는 편이 좋습니다.

추가 확인 포인트:

  • src/main/java/konkuk/thip/vote/domain/Vote.java 에서 @Column(nullable = …) 혹은 @NotNull 등의 어노테이션 확인
  • 빌더 혹은 생성자 로직에서 해당 필드를 항상 주입하는지 확인
src/test/java/konkuk/thip/vote/domain/VoteTest.java (1)

37-37: 매직 넘버 제거: ROOM_ID 상수 재사용 LGTM

테스트 픽스처에서 roomId에 상수를 사용해 일관·가독성 모두 좋아졌습니다.

Also applies to: 50-50

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: 1

🧹 Nitpick comments (4)
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java (2)

53-56: ACTIVE 상태 필터로 삭제 대상 조회 일관성 확보 — 멱등성 정책 확인 권장

삭제/수정은 활성 레코드만 허용한다는 규칙과 맞습니다. 다만 이미 삭제(INACTIVE)된 경우 404 대신 멱등 동작(no-op)을 원한다면 아래처럼 Optional 비어있을 때 조용히 return하도록 바꿀 수 있습니다. 제품/도메인 정책이 404라면 그대로 두셔도 됩니다.

-RecordJpaEntity recordJpaEntity = recordJpaRepository.findByPostIdAndStatus(record.getId(),ACTIVE).orElseThrow(
-        () -> new EntityNotFoundException(RECORD_NOT_FOUND)
-);
+Optional<RecordJpaEntity> recordOpt = recordJpaRepository.findByPostIdAndStatus(record.getId(), ACTIVE);
+if (recordOpt.isEmpty()) return; // 이미 삭제되었거나 비활성인 경우 멱등 처리
+RecordJpaEntity recordJpaEntity = recordOpt.get();

63-66: 업데이트 시 ACTIVE 필터 적용은 합리적입니다 — 로딩 패턴 헬퍼 메서드 추출 제안

여러 어댑터에서 동일한 로딩 패턴(findByPostIdAndStatus(..., ACTIVE) → orElseThrow)이 반복됩니다. 가독성과 변경 용이성을 위해 private 로더(예: loadActive(id))로 캡슐화하는 것을 고려해 주세요.

예시(파일 하단에 추가):

private RecordJpaEntity loadActive(Long id) {
    return recordJpaRepository.findByPostIdAndStatus(id, ACTIVE)
        .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND));
}
src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java (2)

118-119: 보관(스크랩) 저장 시 ACTIVE만 허용 — 중복 저장 방지(유니크키 + 예외 처리) 권장

동시성 상황에서 exists-then-insert는 경쟁 조건이 있습니다. DB 레벨 유니크 제약(saved_feed.user_id, saved_feed.feed_id) + DataIntegrityViolationException 처리로 이중 저장을 방지하는 구성이 안전합니다. exists 체크는 보조적으로만 쓰는 것을 권장합니다.


134-135: 멱등성 정책 검토 및 트랜잭션 경계 확인

  • FeedDeleteService.deleteFeed(및 VoteDeleteService 등)에 이미 @Transactional이 적용되어 있어, FeedCommandPersistenceAdapter 호출 시 트랜잭션 내에서 실행됩니다.
  • 현재 FeedCommandPersistenceAdapter.java(134–135행)에서는 엔티티가 없으면 EntityNotFoundException을 던져 404를 반환하도록 되어 있습니다.
  • API 설계상 삭제 요청의 멱등성을 유지하고 싶다면, 아래와 같이 존재하지 않는 경우 no-op 처리로 변경할 수 있습니다.
--- a/src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java
+++ b/src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java
@@ -134,2 +134,5 @@
-       FeedJpaEntity feedJpaEntity = feedJpaRepository.findByPostIdAndStatus(feed.getId(), ACTIVE)
-           .orElseThrow(() -> new EntityNotFoundException(FEED_NOT_FOUND));
+       Optional<FeedJpaEntity> feedOpt = feedJpaRepository.findByPostIdAndStatus(feed.getId(), ACTIVE);
+       if (feedOpt.isEmpty()) return; // 이미 삭제되었거나 비활성인 경우 멱등 처리
+       FeedJpaEntity feedJpaEntity = feedOpt.get();

삭제 시 404 응답 유지 vs. no-op 처리 중 원하는 정책을 결정해주세요.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b4b564d and e6f95a9.

📒 Files selected for processing (4)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java (3 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java (4 hunks)
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteDeleteAPITest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteDeleteAPITest.java

Comment on lines +78 to 79
FeedJpaEntity feedJpaEntity = feedJpaRepository.findByPostIdAndStatus(feed.getId(),ACTIVE)
.orElseThrow(() -> new EntityNotFoundException(FEED_NOT_FOUND));
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

ACTIVE 필터로 업데이트 대상 조회 — OK. (post_id, status) 복합 인덱스 존재 여부 확인 권장

해당 조회가 핫패스일 가능성이 높습니다. DB에 (post_id, status) 복합 인덱스가 없으면 성능 저하가 발생할 수 있습니다. 엔티티(@Index) 또는 스키마 마이그레이션에 인덱스 정의가 있는지 확인 부탁드립니다.


🏁 Script executed:

#!/bin/bash
# 엔티티에 @Index로 복합 인덱스 선언 여부 점검
rg -n "@Index\\(" -g "src/**.java"

# Feed/Record JPA 엔티티에서 postId/status 컬럼 매핑 확인
rg -n "class .*FeedJpaEntity|class .*RecordJpaEntity" -A 50 -g "src/**.java" | rg -n "postId|status"

# Repository 시그니처 확인(참고)
rg -n "findByPostIdAndStatus\\(" -g "src/**.java"

Length of output: 636


🏁 Script executed:

#!/bin/bash
# 1) @Index 어노테이션으로 복합 인덱스 선언 여부 확인
rg -n '@Index' -g 'src/**/*.java'

# 2) @Table(indexes = …) 형태로 인덱스 설정 확인
rg -n '@Table' -g 'src/**/*.java' | rg -n 'indexes'

# 3) FeedJpaEntity에서 postId/status 컬럼 매핑 확인
rg -n 'class .*FeedJpaEntity' -A 50 -g 'src/**/*.java' | rg -n 'postId\|status'

# 4) Repository 메서드 시그니처(findByPostIdAndStatus) 확인
rg -n 'findByPostIdAndStatus' -g 'src/**/*.java'

Length of output: 3366


(post_id, status) 복합 인덱스 부재 확인 — 추가가 필요합니다
JPA 엔티티(@Index/@table(indexes)) 상에 (post_id, status) 복합 인덱스가 정의되어 있지 않습니다. 해당 조회는 핫패스이므로, DB 성능 저하를 방지하기 위해 스키마 마이그레이션 또는 JPA 어노테이션을 통해 인덱스를 추가해 주세요.

수정 대상 예시:

  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedJpaEntity.java
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteJpaEntity.java
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordJpaEntity.java
@Entity
@Table(name = "feed",
       indexes = @Index(
           name = "idx_feed_post_id_status",
           columnList = "post_id, status"
       )
)
public class FeedJpaEntity { … }
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java
around lines 78-79, the repository query uses (post_id, status) but the JPA
entities lack a composite index; add a composite index for (post_id, status) to
the FeedJpaEntity (and similarly to VoteJpaEntity and RecordJpaEntity) by
annotating the entity with a @Table(indexes = @Index(name =
"idx_<entity>_post_id_status", columnList = "post_id, status")) or create a DB
migration that adds the same composite index to the underlying tables; ensure
the column names in columnList match the physical column names, rebuild, and
apply migrations to prevent hot-path query slowdown.

buzz0331
buzz0331 previously approved these changes Aug 13, 2025
Copy link
Contributor

@buzz0331 buzz0331 left a comment

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 +44
// 3. 투표 삭제
// 3-1. 투표 게시글 댓글 삭제
commentCommandPort.softDeleteAllByPostId(command.voteId());
// 3-2. 투표 게시글 좋아요 삭제
postLikeCommandPort.deleteAllByPostId(command.voteId());
// 3-3. 투표 삭제
voteCommandPort.delete(vote);

return command.roomId();
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 애그리거트 단위로 삭제를 진행하니까 확실히 재사용성이 높네요!! LGTM

Comment on lines +94 to +109
private void validateCreator(Long userId) {
if (!this.creatorId.equals(userId)) {
throw new InvalidStateException(VOTE_ACCESS_FORBIDDEN, new IllegalArgumentException("투표 작성자만 투표를 수정/삭제할 수 있습니다."));
}
}

public void validateDeletable(Long userId,Long roomId) {
validateRoomId(roomId);
validateCreator(userId);
}

private void validateRoomId(Long roomId) {
if (!this.roomId.equals(roomId)) {
throw new InvalidStateException(VOTE_ACCESS_FORBIDDEN, new IllegalArgumentException("투표가 해당 방에 속하지 않습니다."));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻

# Conflicts:
#	.github/workflows/cd-workflow-dev.yml
#	.github/workflows/cd-workflow-prod.yml
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: 0

🔭 Outside diff range comments (2)
.github/workflows/cd-workflow-prod.yml (2)

55-56: docker login 보안 개선: --password-stdin 사용 권장

CLI의 -p 옵션은 경고가 발생하며, 프로세스 목록 노출 가능성 등 보안 우려가 있습니다. --password-stdin으로 교체하세요.

-          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
+          echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

60-61: 서드파티 GitHub Action SHA pinning 적용 필요

파일: .github/workflows/cd-workflow-prod.yml
라인: 60–61

- uses: appleboy/ssh-action@master
+ uses: appleboy/ssh-action@2ead5e32344bcde3572c761ce2df1aae4d9a3240

2ead5e3…는 v1.2.2(2025-03-09)의 커밋 SHA입니다.
• GitHub Security Hardening Guide 등 공식 보안 가이드에서는 태그(pin by tag)가 아닌 풀 커밋 SHA pinning을 권장합니다.

🧹 Nitpick comments (1)
.github/workflows/cd-workflow-prod.yml (1)

7-7: 프로덕션 CD 워크플로우가 develop 브랜치 푸시에도 트리거됩니다 — 확인 필요

.github/workflows/cd-workflow-prod.ymlon.push.branches 설정에서 develop이 포함되어 있어, develop 브랜치에 커밋 푸시 시에도 프로덕션 배포가 실행됩니다. 일반적으로 develop는 스테이징, main은 프로덕션용으로 구분하므로 의도된 동작인지 확인 부탁드립니다.

  • 의도되지 않은 경우
    • on.push.branches에서 develop 제거
       on:
         push:
           branches:
      -      - 'develop'
           ​-      - 'main'
  • 의도된 경우에도 권장 사항
    • Required reviewers 등 환경 보호 규칙 설정
    • if: github.ref == 'refs/heads/main' 조건 추가로 main 전용 배포 보장
    • develop용 스테이징 워크플로우 별도 분리
    • concurrency로 중복 배포 취소 제어

위 사항 확인 후 대응 방안을 알려주세요.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 901f29d and 7e3cefc.

📒 Files selected for processing (4)
  • .github/workflows/cd-workflow-prod.yml (1 hunks)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/test/java/konkuk/thip/record/domain/RecordTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/test/java/konkuk/thip/record/domain/RecordTest.java
🔇 Additional comments (2)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)

102-103: 신규 에러코드 추가 적절합니다

  • VOTE 도메인 내 코드 범위(110xxx)와 순번 일관성 양호
  • 메시지/HTTP 상태(403)도 다른 도메인 접근권한 에러와 어휘·톤 일관

테스트 및 Swagger 반영과도 잘 맞습니다.

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

162-166: VOTE_DELETE 응답 스펙 추가 적절합니다

  • RECORD_DELETE, FEED_DELETE와의 구성(ROOM_ACCESS_FORBIDDEN, *_NOT_FOUND, *_ACCESS_FORBIDDEN) 일관성 유지
  • 공통 API 에러 세트 합산 로직과도 호환

컨트롤러의 삭제 엔드포인트가 실제로 이 enum을 참조하도록만 유지되면 문서화도 문제없겠습니다.

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: 1

🔭 Outside diff range comments (3)
.github/workflows/cd-workflow-prod.yml (3)

53-58: Docker 단계: develop에서도 프로덕션 이미지가 푸시될 수 있습니다 — main 한정 및 password-stdin 사용으로 보안/안전 강화

현재 설정은 develop 브랜치에서도 동일 태그(latest)에 푸시될 수 있습니다. 또한 -p 인자 사용은 로그/프로세스 목록 노출 위험이 있으므로 --password-stdin을 권장합니다. main에서만 프로덕션 이미지 푸시, 태그에 SHA를 포함해 불변 태깅을 병행하세요.

-      - name: 🐳 Docker build & push
-        run: |
-          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
-          docker build --build-arg PORT=${{env.APP_PORT}} -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} .
-          docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}
+      - name: 🐳 Docker build & push (main only)
+        if: ${{ github.ref == 'refs/heads/main' }}
+        run: |
+          echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
+          docker build --build-arg PORT=${{env.APP_PORT}} \
+            -f Dockerfile \
+            -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}:${{ github.sha }} .
+          docker tag ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}:${{ github.sha }} \
+            ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}:latest
+          docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}:${{ github.sha }}
+          docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}:latest

59-78: 배포 단계는 main 전용으로 가드해야 합니다

develop에서 배포 스텝이 실행되지 않도록 조건을 추가하세요.

       - name: 🚀 deploy to server
-        uses: appleboy/ssh-action@master
+        if: ${{ github.ref == 'refs/heads/main' }}
+        uses: appleboy/ssh-action@master
         with:
           host: ${{ secrets.EC2_HOST_PROD }}
           username: ${{ secrets.EC2_USERNAME }}
           key: ${{ secrets.EC2_KEY }}
           port: ${{ secrets.EC2_PORT }}
           envs: GITHUB_SHA
           script: |

18-21: 프로덕션 배포 보호: GitHub Environments(approvals) 적용 권장

프로덕션 배포에 환경 보호(승인 게이트)를 두면 오배포를 더 줄일 수 있습니다. build 잡에 environment를 지정하세요.

 jobs:
   build:
     runs-on: ubuntu-latest
+    environment: production
🧹 Nitpick comments (3)
.github/workflows/cd-workflow-prod.yml (3)

9-17: 동시 배포 충돌 방지: concurrency 그룹 추가 제안

여러 커밋이 연달아 푸시될 때 이전 배포를 취소하고 최신만 유지하도록 설정하면 안정적입니다.

 permissions:
   contents: read

 env:
   RESOURCE_PATH: src/main/resources
   COMPOSE_PATH: /home/ec2-user/compose
   APP_PORT: 8000
+
+concurrency:
+  group: prod-${{ github.ref }}
+  cancel-in-progress: true

60-61: 서드파티 액션 버전 고정(pinning) 권장

appleboy/ssh-action@master는 서플라이 체인 리스크가 큽니다. 릴리스 태그나 커밋 SHA로 핀닝해주세요. 동일하게 actions/*도 가능한 한 커밋 SHA로 고정하는 것이 모범사례입니다. 정확한 버전/커밋은 조직 기준에 맞춰 선정 부탁드립니다.


50-51: 프로덕션 빌드에서 테스트를 스킵하고 있습니다 — 상위 파이프라인에서 테스트 보장이 되는지 확인 필요

-x test로 테스트가 실행되지 않습니다. PR/merge 시점에 별도 CI가 테스트를 충분히 커버한다면 괜찮지만, 그렇지 않다면 최소 스모크 테스트라도 CD 전에 실행하는 것을 권장합니다. 현재 리포지토리에 해당 CI 워크플로우가 존재하는지 확인 부탁드립니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 901f29d and 7e3cefc.

📒 Files selected for processing (4)
  • .github/workflows/cd-workflow-prod.yml (1 hunks)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/test/java/konkuk/thip/record/domain/RecordTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/test/java/konkuk/thip/record/domain/RecordTest.java
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java

push:
branches:
- 'main'
- 'develop'
Copy link

@coderabbitai coderabbitai bot Aug 13, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

develop 브랜치 푸시 시 프로덕션 CD가 실행됩니다 — 의도된가요? 매우 위험합니다

이 워크플로우는 PROD 시크릿/호스트(EC2_HOST_PROD, APPLICATION_YML_PROD)를 사용해 실제 배포까지 수행합니다. develop에 푸시만 해도 프로덕션 배포가 일어나는 구조라, 오배포 리스크가 큽니다. 최소한 main 전용으로 제한하거나, 배포/푸시 단계에 조건을 걸어 main에서만 실행되도록 가드해주세요.

Option A: develop 트리거 제거(가장 안전)

   push:
     branches:
       - 'main'
-      - 'develop'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 'develop'
push:
branches:
- 'main'
🤖 Prompt for AI Agents
.github/workflows/cd-workflow-prod.yml lines 7-7: the workflow is triggered on
pushes to the 'develop' branch which causes full production CD to run — change
the trigger to prevent accidental prod deploys by removing 'develop' and
limiting the workflow to 'main' (or add a job-level guard). Replace the push
branches list so it only includes 'main' (or add an if: condition on the
deployment job such as github.ref == 'refs/heads/main') and verify secrets/hosts
are only used when that condition is true; alternatively remove the develop
trigger entirely to adopt Option A.

Copy link
Collaborator

Choose a reason for hiding this comment

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

의도 된거야

Copy link

Choose a reason for hiding this comment

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

@seongjunnoh 알겠습니다! develop 브랜치에서 프로덕션 배포가 의도된 워크플로우라면 그대로 진행하시면 됩니다. 팀의 배포 전략에 따른 결정이군요.


✏️ Learnings added
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#192
File: .github/workflows/cd-workflow-prod.yml:7-7
Timestamp: 2025-08-13T07:52:24.145Z
Learning: seongjunnoh는 develop 브랜치에서 프로덕션 CD가 트리거되는 것을 의도적으로 설정하여 사용하고 있으며, 이는 팀의 배포 전략에 따른 결정이다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.258Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.

seongjunnoh
seongjunnoh previously approved these changes Aug 13, 2025
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.

확인했습니다!! 굳굳

push:
branches:
- 'main'
- 'develop'
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 +37 to +40
// 3-1. 투표 게시글 댓글 삭제
commentCommandPort.softDeleteAllByPostId(command.voteId());
// 3-2. 투표 게시글 좋아요 삭제
postLikeCommandPort.deleteAllByPostId(command.voteId());
Copy link
Collaborator

Choose a reason for hiding this comment

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

오 이미 댓글, 댓글 좋아요, 게시글 좋아요 의 hard/soft delete 메서드를 구현해두셨군요!! 방 삭제 구현할떄 잘 쓰겠습니다!! LGTM

voteParticipantJpaRepository.deleteAllByVoteId(voteJpaEntity.getPostId());
voteItemJpaRepository.deleteAllByVoteId(voteJpaEntity.getPostId());

voteJpaEntity.softDelete();
Copy link
Collaborator

Choose a reason for hiding this comment

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

아 저번에 jpa entity 내부에 status 의 상태를 변경하는 (= soft delete) 전용 메서드 뚫어놓고, 이를 변경 감지 기능을 활용해서 soft delete 처리한다는 부분이 이 코드였군요! 확인했습니다

Comment on lines +109 to +110
@Test
@DisplayName("투표를 삭제하면 [soft delete]되고, 연관된 댓글, 댓글 좋아요, 투표 항목, 투표 참여 관계도 모두 삭제된다")
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

@seongjunnoh seongjunnoh merged commit 82c4a6b into develop Aug 14, 2025
4 checks passed
@seongjunnoh seongjunnoh deleted the feat/#188-vote-delete branch August 14, 2025 15:46
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-241] [feat] 투표 삭제 api 개발

3 participants