Skip to content

Feature/#64 좋아요 토글 api 구현#69

Open
pykido wants to merge 11 commits intodevelopfrom
Feature/#64-좋아요_토글_API_구현

Hidden character warning

The head ref may contain hidden characters: "Feature/#64-\uc88b\uc544\uc694_\ud1a0\uae00_API_\uad6c\ud604"
Open

Feature/#64 좋아요 토글 api 구현#69
pykido wants to merge 11 commits intodevelopfrom
Feature/#64-좋아요_토글_API_구현

Conversation

@pykido
Copy link
Contributor

@pykido pykido commented Jan 31, 2026

🔥 연관된 이슈

close: #64

📜 작업 내용

  • 팀 좋아요 토글 API를 구현하였습니다.
  • 투표 토글 API 리뷰에서 받은피드백을 미리 반영하여 개발하였습니다.
  • 투표 토글 API와의 차이점은 좋아요 토글 API의 경우 투표 기간이 아닐 때만 가능하고, 개수 제한이 없다는 것입니다.

💬 리뷰 요구사항

  • 항상 좋은 리뷰 감사드립니다.

✨ 기타

  • 이제 정말 얼마 남지 않았군요! 화이팅입니다~!

@pykido pykido self-assigned this Jan 31, 2026
@pykido pykido added the ⭐️ 기능 구현 새로운 기능을 구현 label Jan 31, 2026
Copilot AI review requested due to automatic review settings January 31, 2026 15:13
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

팀 좋아요(찜) 토글 API를 추가하고, 투표 기간에는 좋아요가 불가능하도록 제약을 적용한 PR입니다. 관련 RestDocs/통합 테스트도 함께 추가되었습니다.

Changes:

  • TeamLike 도메인/예외/레포지토리 및 좋아요 토글 서비스 로직 추가
  • PUT /teams/{teamId}/likes 엔드포인트 및 요청/응답 DTO 추가
  • RestDocs 문서/테스트 및 “투표 기간 중 불가” 예외 타입 정리(공용 convenience로 이동)

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java 좋아요 토글 핵심 비즈니스 로직(투표기간 검증 포함) 추가
src/main/java/com/opus/opus/modules/team/api/TeamController.java 팀 좋아요 토글 API 엔드포인트 추가
src/main/java/com/opus/opus/modules/team/domain/TeamLike.java TeamLike 엔티티 및 유니크 제약(회원+팀) 추가
src/main/java/com/opus/opus/modules/team/domain/dao/TeamLikeRepository.java TeamLike 조회용 JPA 레포지토리 추가
src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java 좋아요 도메인 예외 타입 정의 추가
src/main/java/com/opus/opus/modules/team/exception/TeamLikeException.java 좋아요 도메인 예외 클래스 추가
src/main/java/com/opus/opus/modules/team/application/dto/request/TeamLikeToggleRequest.java 좋아요 토글 요청 DTO 추가
src/main/java/com/opus/opus/modules/team/application/dto/response/TeamLikeToggleResponse.java 좋아요 토글 응답 DTO 추가
src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java “투표 기간 중 작업 불가” 공용 검증 로직 추가
src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java 투표기간 검증을 convenience로 위임하도록 리팩터링
src/main/java/com/opus/opus/modules/contest/exception/ContestExceptionType.java 투표기간 작업 불가 예외 타입 명/메시지 정리
src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java 좋아요 토글 서비스 통합 테스트 추가
src/test/java/com/opus/opus/team/TeamLikeFixture.java TeamLike 테스트 픽스처 추가
src/test/java/com/opus/opus/team/TeamFixture.java contestId 주입 가능한 팀 픽스처로 확장
src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java 팀 좋아요 토글 API RestDocs 테스트 추가
src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java 투표기간 작업 불가 예외 타입 변경 반영
src/test/java/com/opus/opus/restdocs/RestDocsTest.java RestDocs 테스트 컨텍스트에 TeamLikeCommandService mock 추가
src/test/java/com/opus/opus/contest/application/ContestCommandServiceTest.java 투표기간 작업 불가 예외 타입 변경 반영
src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc 팀 좋아요 API 문서 신규 추가
src/main/java/com/opus/opus/docs/asciidoc/opus.adoc API 목록에 팀 좋아요 문서 링크 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Collaborator

@JJimini JJimini left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 전체적으로 좋아요와 투표를 잘 분리하신 것 같아요~!
간단하게 제가 궁금한 부분 남겨두었습니다!

1. 좋아요 API의 위치

  • 현재 좋아요 API는 TeamController 내에 있지만, 서비스 코드는 TeamLikeCommandService에 있습니다
  • TeamController에 작성하신 이유는 좋아요 API는 독자적인 Entity를 가지지만 CURD 기능이 있지 않기 때문에 convention 상 TeamController에 작성하신 것으로 이해됩니다.
  • 하지만 TeamLikeCommandService단독 Service class를 가지고 있는 부분이 애매한 것 같습니다..
  • TeamVote 등 비슷한 API의 사례를 확인하고 동일한 규칙을 적용하면 추후 유지보수가 쉬울 것 같습니다!
  • 언제나 정해진 정답은 없으니 태윤님이 생각하신 알맞은 방법 소개하고 함께 공유하고 이야기해서 정해나가면 좋을 것 같습니다😊

2. schema.sqlunique 제약 추가 누락

  • 현재 entity 상에서는 unique 제약을 작성한 것으로 확인되는데, schema.sql에는 추가되지 않은 것 같습니다.
  • 운영 서버에는 schema.sql를 기준으로 테이블이 관리되기 때문에 작성하지 않으면 실제로 적용되지 않는 문제가 있어서 추가 부탁드립니다!

3. final 누락

  • 전체적으로 final이 누락된 것 같습니다! 확인 부탁드려요~!

Comment on lines +72 to +75
|true
|true
|이미 좋아요한 팀입니다.
|400 Bad Request
Copy link
Collaborator

Choose a reason for hiding this comment

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

API의 이름이 좋아요 토글인데 이미 좋아요 한 팀에게 해당 API를 다시 요청할 경우 Bad Request가 뜨는 것이 조금 어색하게 느껴집니다.

만약 좋아요 한 팀에 재요청이 들어갔다면, 좋아요 취소 처리가 되는 것은 어떤가요??
같은 흐름으로 이미 좋아요를 취소한 팀경우도 같은 것 같습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

저도 이 부분을 생각해봤는데 현재 구현에서는isLiked=True 상태인 팀에 다시 isLiked=True를 하면 예외를 던지는게 맞는게 아닐까 생각합니다.

토글만 하려면 Body가 없는게 낫지 않을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JJimini @sjmoon00
두 분 모두 좋은 의견 감사드립니다!

지금 다시 생각해보니, 투표 API와 좋아요 API 모두 '토글'이라는 네이밍이 잘못되었다는 생각이 드네요. 제가 의도한 것은 프론트엔드가 isLiked, isVoted 값을 통해 원하는 최종 상태를 명시적으로 지정하고, 백엔드는 그 상태로 변경 가능한지 검증하는 구조였습니다. 이를 통해 '이미 좋아요한 팀입니다'와 같은 명확한 에러 핸들링이 가능하고, 프론트와 백엔드 간 상태 불일치도 조기에 감지할 수 있어 안정성을 확보하고자 했어요. 하지만 이는 '토글'이 아니라 '상태 변경'에 가까운 방식인 거 같아요!

그래서 단기적으로는 API 명을 '팀 좋아요 상태 변경', '팀 투표 상태 변경'으로 수정하면 어떨까 싶습니다. 이렇게 하면 현재 로직을 유지하면서도 API의 실제 동작과 네이밍이 일치하게 될 것 같아요. 다만, 실제 주요 기업들(GitHub, Twitter, Instagram 등)의 구현 사례를 찾아보니 POST/DELETE로 분리하고 멱등성을 보장하는 형태가 업계 표준이더라고요. 추후 이 방식으로 리팩토링하는 것도 고려해보면 좋을 것 같습니다!

우선 이번 PR에서는 네이밍만 수정하고, 추후에 리팩토링하는 건 어떨까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

좋습니다! 사실 저도 POST/DELETE로 분리하는 게 더 깔끔하다고 생각했어요. 근데 이미 토글로 구현해두셔서 전면 수정해야 하는 작업이라 comment를 남기지는 않았지만요ㅎㅎ

그런데 수정하실 예정이라면 프론트에서도 작업이 바뀔텐데, 지금 이 pr을 머지시키고 리팩토링하면 이중 작업이 되지 않을까 생각도 듭니다.. 원래 코드를 지우고 해야하니까요.. 태윤님이 PR Merge 일정까지 고려해서 저희 전체 회의 전까지 완성할 수 있으시면 지금 머지 후 리팩토링 방식, 아니면 PR Close 하고 새로 설계하는 방식으로 하시면 시간 절약이 될 것 같아요!

추가로 comment를 읽으면서 멱등성에 대해 궁금한 부분이 생겼는데, POST는 멱등성을 보장하지 않는다고 알고 있는데 멱등성을 보장하는 형태가 어떤 식으로 구현이 되었는지 궁금하네요..! 혹시 관련 reference 나중에 ICE BREAKING 때 소개해주셔도 좋을 것 같아요!ㅎㅎ

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JJimini
좋은 의견 감사합니다!

말씀해주신 것처럼 지금 머지 후 이슈를 파서 리팩토링하는게 좋을 거 같아요! 리팩토링은 프론트분과 협의 후에 이슈를 파서 진행하겠습니다.
또한 POST 자체로는 비멱등적이지만, 로직으로 멱등성을 보통 구현한다더군요! 이와 관련해서는 아이스 브레이킹 때 다뤄보겠습니다!

Copy link
Contributor

@sjmoon00 sjmoon00 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 +72 to +75
|true
|true
|이미 좋아요한 팀입니다.
|400 Bad Request
Copy link
Contributor

Choose a reason for hiding this comment

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

저도 이 부분을 생각해봤는데 현재 구현에서는isLiked=True 상태인 팀에 다시 isLiked=True를 하면 예외를 던지는게 맞는게 아닐까 생각합니다.

토글만 하려면 Body가 없는게 낫지 않을까요?

@pykido
Copy link
Contributor Author

pykido commented Feb 8, 2026

@JJimini
좋은 피드백 감사합니다!
말씀해주신 것처럼 TeamLike가 독립적인 Service를 가지는 반면 Controller는 TeamController에 있는 부분이 일관성 측면에서 고민될 수 있을 거 같아요!

이에 대해 제가 현재 구조로 결정한 이유를 공유해드립니다!

  1. controller 위치 선정
  • 회의 때 CRUD를 포함하지 않는 작은 기능은 별도의 Controller를 만들지 않고 큰 도메인의 Controller에 속한다고 논의했기에, 좋아요 기능은 TeamController에 두었습니다.
  • URL 구조도 /teams/{teamId}/likes로 팀의 하위 리소스 형태라 TeamController에 위치하는 게 자연스럽다고 판단했어요.
  1. Service 분리 결정
  • 좋아요 관련 서비스 로직도 TeamCommandService에 두는 것을 고려했지만, 현재 TeamCommandService는 이미 팀 생성/수정/이미지 관리 등 많은 책임을 가지고 있고 코드 줄 수도 많아 향후 유지보수 및 관리가 힘들어 보였습니다. 테스트 코드 또한 합쳐진다면 더욱 관리하기 힘들 거 같아요!
  • 또한 좋아요 로직에는 투표 기간 검증, 중복 체크, 상태 토글 등 고유한 비즈니스 로직이 있어 별도 Service로 분리하는 게 유지보수에 더 유리하다고 생각했어요-!

따라서 이번 좋아요, 투표 기능처럼 CRUD를 포함하지 않는 작은 기능은 큰 도메인의 Controller에 속하되, 서비스 로직은 분리하는 것으로 통일하는 것이 어떨까요~?

@JJimini
Copy link
Collaborator

JJimini commented Feb 9, 2026

@JJimini 좋은 피드백 감사합니다! 말씀해주신 것처럼 TeamLike가 독립적인 Service를 가지는 반면 Controller는 TeamController에 있는 부분이 일관성 측면에서 고민될 수 있을 거 같아요!

이에 대해 제가 현재 구조로 결정한 이유를 공유해드립니다!

  1. controller 위치 선정
  • 회의 때 CRUD를 포함하지 않는 작은 기능은 별도의 Controller를 만들지 않고 큰 도메인의 Controller에 속한다고 논의했기에, 좋아요 기능은 TeamController에 두었습니다.
  • URL 구조도 /teams/{teamId}/likes로 팀의 하위 리소스 형태라 TeamController에 위치하는 게 자연스럽다고 판단했어요.
  1. Service 분리 결정
  • 좋아요 관련 서비스 로직도 TeamCommandService에 두는 것을 고려했지만, 현재 TeamCommandService는 이미 팀 생성/수정/이미지 관리 등 많은 책임을 가지고 있고 코드 줄 수도 많아 향후 유지보수 및 관리가 힘들어 보였습니다. 테스트 코드 또한 합쳐진다면 더욱 관리하기 힘들 거 같아요!
  • 또한 좋아요 로직에는 투표 기간 검증, 중복 체크, 상태 토글 등 고유한 비즈니스 로직이 있어 별도 Service로 분리하는 게 유지보수에 더 유리하다고 생각했어요-!

따라서 이번 좋아요, 투표 기능처럼 CRUD를 포함하지 않는 작은 기능은 큰 도메인의 Controller에 속하되, 서비스 로직은 분리하는 것으로 통일하는 것이 어떨까요~?

태윤님의 의견 잘 읽었습니다!
저희가 Controller 하나에 Service 2개를 가지는 구조이다보니, 1~2개의 API를 위해 class가 불필요하게 많아지는 것 같아서 해당 convention을 정했었는데요
지금 작성된 코드를 보니 TeamCommandServiceContestCommandService가 너무 많은 책임을 가지고 있는 것 같기도 합니다.

그런데 추후 다른 사람들이 이 코드를 유지보수하기 위해 확인했을 때, Controller와 Service가 매치되지 않거나 Service 내에서도 Query와 Command가 맞지 않으면 헷갈릴 수도 있을 것 같아요

또한 코드의 길이에 차이가 있을 뿐 ControllerService가 동일하게 책임을 많이 가지고 있는데(API 개수), Controller는 통합 & Service는 분리하는 것도 애매한 느낌이 들고요

따라서 제 의견은 아래와 같습니다!

  1. Controller와 Service를 통일하거나(코드가 길어지더라도)
  2. 다른 분들도 Service는 분리가 맞다고 의견을 주신다면, 추후 유지보수 인원이 헷갈리지 않도록 Service의 메서드 위에 주석을 달아두거나

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⭐️ 기능 구현 새로운 기능을 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 좋아요 토글 API 구현

3 participants