Skip to content

Feat/#453 공통퀘스트 공감 API 연동#454

Open
y-eonee wants to merge 8 commits into
developfrom
feat/#453-공감-api

Hidden character warning

The head ref may contain hidden characters: "feat/#453-\uacf5\uac10-api"
Open

Feat/#453 공통퀘스트 공감 API 연동#454
y-eonee wants to merge 8 commits into
developfrom
feat/#453-공감-api

Conversation

@y-eonee

@y-eonee y-eonee commented Jun 10, 2026

Copy link
Copy Markdown
Member

🔗 연결된 이슈

📄 작업 내용

  • 공감 API가 사용되는 CommonQuestVC, MyAnswerVC, HistroyVC에 API를 연동했습니다.
  • 이 API의 리스폰스로 likeCount와 isLiked 값이 오는데요! 서버에서 likeCount만 받아서 뷰에 다시 반영하고, isLiked는 클라에서 toggle로 처리하는 것으로 두었습니다.
구현 내용 HistoryVC CommonQuestVC MyAnswerVC
GIF

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 공통 퀘스트 답변에 좋아요 기능 추가
    • 좋아요 버튼 탭 시 좋아요 여부와 좋아요 수가 화면에 반영되도록 개선
    • 히스토리, 내 답변, 일반 퀘스트 화면 전반에서 좋아요 지원
    • 좋아요 처리 결과를 답변 단위로 업데이트하여 정확한 UI 반영

@y-eonee y-eonee self-assigned this Jun 10, 2026
@y-eonee y-eonee added 나연🐹 feat 새로운 기능 구현 및 API 연결 labels Jun 10, 2026
@y-eonee y-eonee linked an issue Jun 10, 2026 that may be closed by this pull request
1 task
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

워크스루

공통 퀘스트 답변에 좋아요 기능을 추가합니다. 백엔드 API 호출부터 시작하여 도메인 use case를 거쳐 프레젠테이션 계층에서 사용자의 좋아요 탭 이벤트를 처리하고, 결과를 실시간으로 UI에 반영합니다.

변경 사항

공통 퀘스트 좋아요 기능

레이어 / 파일 요약
좋아요 백엔드 통신 레이어
ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift, ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestLikeEntity.swift, ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift, ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift, ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift, ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift, ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift
좋아요 응답 DTO(PostCommonQuestLikeResponseDTO), 도메인 엔티티(CommonQuestLikeEntity), POST 엔드포인트(/{answerID}/likes), 저장소 메서드(postCommonQuestLikes), 도메인 인터페이스 메서드, use case 프로토콜 및 구현(DefaultPostCommonQuestLikeUseCase), DI 등록을 통해 좋아요 요청 및 응답 흐름을 완성합니다.
뷰 계층 프로토콜 및 UI 메서드
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift
CommonQuestLikeCommentProtocol.likeButtonDidTap 메서드에 answerID: Int 파라미터를 추가하여 어떤 답변의 좋아요가 탭되었는지 명시합니다. QuestContentView에 answerID 저장 프로퍼티와 configure(answerID:...) 메서드를 추가하고, updateUI(likeCount:isLiked:) 메서드로 좋아요 카운트와 선택 상태를 갱신합니다. 좋아요 버튼 탭 시 delegate에 answerID와 함께 알립니다.
답변 뷰 바인딩
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift, ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift, ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift
셀 및 히스토리 뷰의 bind/configure 메서드에 answerID 파라미터를 추가하여 QuestContentView에 전달하므로, 각 답변이 자신의 ID를 추적합니다. CommonQuestHistoryView의 questContentView 프로퍼티를 private(set) var로 변경하여 필요 시 delegate 설정 가능하게 합니다.
ViewModel 좋아요 처리 로직
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift, ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift, ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift
세 ViewModel 모두에 PostCommonQuestLikeUseCase 의존성을 주입하고, likeCountSubject를 통해 commonQuestLikeCountPublisher를 외부에 노출합니다. InputlikeButtonDidTap(answerID:) 케이스를 추가하고, 액션 처리에서 postCommonQuestLike(answerID:) 메서드로 비동기 요청을 수행한 후 결과를 발행합니다. indexOfAnswer(answerID:) 헬퍼 메서드로 답변 배열에서 ID로 인덱스를 찾습니다.
ViewController 좋아요 구독 및 처리
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift, ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift, ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift
세 ViewController 모두에서 commonQuestLikeCountPublisher를 구독하고, 성공 시 해당 answerID의 셀을 찾아 questContentView.updateUI(likeCount:isLiked:)로 UI를 갱신합니다(실패 시 로그). CommonQuestLikeCommentProtocol conformance 확장을 추가하여 likeButtonDidTap(answerID:) 메서드를 구현하고, 전달된 answerID를 ViewModel 액션 .likeButtonDidTap(answerID:)으로 라우팅합니다.
DI 등록 및 연결
ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift
CommonQuestViewModel, CommonQuestMyAnswerViewModel, CommonQuestHistoryViewModel 등록 시 postCommonQuestLikeUseCase를 각각 resolve하여 생성자에 주입하므로, 의존성 그래프가 완성되어 좋아요 기능이 활성화됩니다.

코드 리뷰 예상 난이도

🎯 3 (보통) | ⏱️ ~25 분

관련 리뷰어

  • juri123123
  • dev-domo

시인성의 노래 🐰

답변에 하트를 주고 ❤️
숫자가 쏘옥 올라가면 🎉
공통 퀘스트가 반짝반짝✨
좋아요 스트림이 흐르고
뷰가 춤을 춘다 💃

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '공통퀘스트 공감 API 연동'은 변경 사항의 주요 목표를 명확하게 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#453-공감-api

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (5)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift (2)

30-30: ⚡ Quick win

사용하지 않는 프로퍼티를 제거하세요.

likeCounts 프로퍼티는 Line 115에서 할당되지만 이후 어디에도 사용되지 않습니다. 좋아요 카운트는 이제 서버 응답에서 직접 updateUI(likeCount:)로 업데이트되므로, 로컬 상태 저장이 불필요합니다.

♻️ 미사용 프로퍼티 제거
 
     private var answerID: Int = 0
-    private var likeCounts: Int = 0
     
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift`
at line 30, Remove the unused property `likeCounts` from QuestContentView and
delete any assignments to it (e.g., the assignment around where it's set from
server response); instead rely solely on `updateUI(likeCount:)` which receives
the server-provided value. Search for and remove all references to the
`likeCounts` property (declaration and assignments) so the class no longer
contains unused local state.

105-127: ⚡ Quick win

불필요한 할당을 제거하세요.

Line 115의 self.likeCounts = likeCount 할당은 likeCounts 프로퍼티가 이후 사용되지 않으므로 불필요합니다.

♻️ 불필요한 할당 제거
     ) {
         self.answerID = answerID
-        self.likeCounts = likeCount
         answerContentTextView.do {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift`
around lines 105 - 127, In the configure(...) method of QuestContentView remove
the unnecessary assignment to self.likeCounts (the line setting self.likeCounts
= likeCount) since likeCounts is not used later; simply delete that statement
from the configure function so likeCount is only applied to likeCountLabel and
the likeButton state remains driven by isLiked.
ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift (1)

357-368: ⚡ Quick win

파라미터 이름과 resolve된 use case 이름이 불일치합니다.

Line 358에서 fetchCommonQuestDetailUseCase를 resolve하고 있지만, Line 365에서 전달할 때 파라미터 이름이 fetchCommonQuestCommentsUseCase입니다. "Detail"과 "Comments"의 불일치가 있어 코드 가독성과 유지보수성을 저하시킵니다. 파라미터 이름을 일관되게 맞춰주세요.

♻️ 파라미터 이름 일관성 개선
         return CommonQuestHistoryViewModel(
-            fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase,
+            fetchCommonQuestDetailUseCase: fetchCommonQuestDetailUseCase,
             postCommonQuestLikeUseCase: postCommonQuestLikeUseCase
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift`
around lines 357 - 368, The resolved variable name fetchCommonQuestDetailUseCase
does not match the parameter name passed into CommonQuestHistoryViewModel
(fetchCommonQuestCommentsUseCase); update the registration to use consistent
names: either resolve and name the variable fetchCommonQuestCommentsUseCase or
change the parameter passed to fetchCommonQuestDetailUseCase so the identifier
matches the intended use case. Edit the DIContainer.shared.register block and
adjust the resolve call and the constructor argument for
CommonQuestHistoryViewModel accordingly (symbols: DIContainer.shared.register,
CommonQuestHistoryViewModel, fetchCommonQuestDetailUseCase,
fetchCommonQuestCommentsUseCase).
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift (1)

26-26: ⚡ Quick win

let으로 변경하여 불변성을 유지하세요.

questContentViewvar로 선언할 필요가 없습니다. ViewController가 delegate를 설정하기 위해 접근만 하면 되므로 private(set) let으로 충분합니다. 뷰 객체 자체를 재할당할 이유가 없습니다.

♻️ 불변 프로퍼티로 변경
-    private(set) var questContentView = QuestContentView()
+    private(set) let questContentView = QuestContentView()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift`
at line 26, questContentView은 재할당이 필요 없으니 불변으로 선언하세요: 현재 선언된 private(set) var
questContentView = QuestContentView()를 private(set) let questContentView =
QuestContentView()로 변경하면 됩니다; QuestContentView 인스턴스의 프로퍼티(예: delegate) 변경은 여전히
가능하니 재할당만 막도록 선언을 바꾸세요.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift (1)

295-295: 💤 Low value

프로덕션 코드에서 디버그 로그를 제거하는 것을 고려하세요.

likeButtonDidTap 메서드에 디버그 로그가 남아있습니다. 이 로그가 개발 중 디버깅 목적이었다면 제거하거나, 필요시 더 의미 있는 로그 레벨로 변경하는 것을 권장합니다.

♻️ 제안하는 수정
 func likeButtonDidTap(answerID: Int) {
-    ByeBooLogger.debug("answerID: \(answerID)")
     viewModel.action(.likeButtonDidTap(answerID: answerID))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift`
at line 295, likeButtonDidTap 메서드에 남아 있는 개발용 디버그 로그
ByeBooLogger.debug("answerID: \(answerID)")를 제거하거나 프로덕션에 적합한 로그 레벨로 변경하세요; 구체적으로
likeButtonDidTap 내부의 ByeBooLogger.debug 호출을 삭제하거나 info/trace 등 더 적절한 레벨로 바꾸고
메시지를 더 의미 있게(예: "Like button tapped for answerID: {id}") 수정해 배포 시 불필요한 디버그 출력이
남지 않도록 하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift`:
- Line 2: Header comment filename casing doesn't match the actual file/type
name; update the top-of-file header in PostCommonQuestLikeResponseDTO.swift so
the commented filename reads "PostCommonQuestLikeResponseDTO.swift" to match the
Swift file/type naming convention (e.g., the struct/class
PostCommonQuestLikeResponseDTO).

In `@ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift`:
- Around line 78-84: postCommonQuestLikes currently ignores
PostCommonQuestLikeResponseDTO.isLiked and returns only likeCount, causing UI to
diverge from server state; change CommonQuestRepository.postCommonQuestLikes to
return both likeCount and isLiked (e.g. a tuple or DTO) by decoding
PostCommonQuestLikeResponseDTO and propagating its isLiked, update the
PostCommonQuestLikeUseCase return type and the ViewModel subjects/bindings to
accept and publish the isLiked value, and modify
QuestContentView.likeButtonDidTap to avoid unconditionally toggling the button
(instead update the UI only after success or roll back to server isLiked on
failure) so the local state remains consistent with the server response.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`:
- Line 13: The cancellables property is declared immutable but must be mutable
to use Combine’s `.store(in: &cancellables)`; change the declaration of
`cancellables` from `let cancellables = Set<AnyCancellable>()` to a mutable
`var` in both `CommonQuestViewModel` and `CommonQuestMyAnswerViewModel` so
Combine subscriptions can be stored (refer to the `cancellables` property and
usage of `.store(in: &cancellables)` in those classes).

---

Nitpick comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift`:
- Line 30: Remove the unused property `likeCounts` from QuestContentView and
delete any assignments to it (e.g., the assignment around where it's set from
server response); instead rely solely on `updateUI(likeCount:)` which receives
the server-provided value. Search for and remove all references to the
`likeCounts` property (declaration and assignments) so the class no longer
contains unused local state.
- Around line 105-127: In the configure(...) method of QuestContentView remove
the unnecessary assignment to self.likeCounts (the line setting self.likeCounts
= likeCount) since likeCounts is not used later; simply delete that statement
from the configure function so likeCount is only applied to likeCountLabel and
the likeButton state remains driven by isLiked.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift`:
- Line 26: questContentView은 재할당이 필요 없으니 불변으로 선언하세요: 현재 선언된 private(set) var
questContentView = QuestContentView()를 private(set) let questContentView =
QuestContentView()로 변경하면 됩니다; QuestContentView 인스턴스의 프로퍼티(예: delegate) 변경은 여전히
가능하니 재할당만 막도록 선언을 바꾸세요.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift`:
- Line 295: likeButtonDidTap 메서드에 남아 있는 개발용 디버그 로그 ByeBooLogger.debug("answerID:
\(answerID)")를 제거하거나 프로덕션에 적합한 로그 레벨로 변경하세요; 구체적으로 likeButtonDidTap 내부의
ByeBooLogger.debug 호출을 삭제하거나 info/trace 등 더 적절한 레벨로 바꾸고 메시지를 더 의미 있게(예: "Like
button tapped for answerID: {id}") 수정해 배포 시 불필요한 디버그 출력이 남지 않도록 하세요.

In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift`:
- Around line 357-368: The resolved variable name fetchCommonQuestDetailUseCase
does not match the parameter name passed into CommonQuestHistoryViewModel
(fetchCommonQuestCommentsUseCase); update the registration to use consistent
names: either resolve and name the variable fetchCommonQuestCommentsUseCase or
change the parameter passed to fetchCommonQuestDetailUseCase so the identifier
matches the intended use case. Edit the DIContainer.shared.register block and
adjust the resolve call and the constructor argument for
CommonQuestHistoryViewModel accordingly (symbols: DIContainer.shared.register,
CommonQuestHistoryViewModel, fetchCommonQuestDetailUseCase,
fetchCommonQuestCommentsUseCase).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26a2d730-550e-42c8-bdef-88a522c8c041

📥 Commits

Reviewing files that changed from the base of the PR and between 8847d2e and 3bc3258.

📒 Files selected for processing (17)
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift

@@ -0,0 +1,13 @@
//
// postCommonQuestLikeResponseDTO.swift

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

파일명 표기 불일치

헤더 주석의 파일명이 실제 파일명과 대소문자가 다릅니다 (postCommonQuestLikeResponseDTO.swiftPostCommonQuestLikeResponseDTO.swift). Swift 컨벤션에 따라 타입명과 일치하는 파일명을 사용하므로, 헤더 주석도 PostCommonQuestLikeResponseDTO.swift로 수정해야 합니다.

수정 제안
 //
-//  postCommonQuestLikeResponseDTO.swift
+//  PostCommonQuestLikeResponseDTO.swift
 //  ByeBoo-iOS
📝 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
// postCommonQuestLikeResponseDTO.swift
//
// PostCommonQuestLikeResponseDTO.swift
// ByeBoo-iOS
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift` at
line 2, Header comment filename casing doesn't match the actual file/type name;
update the top-of-file header in PostCommonQuestLikeResponseDTO.swift so the
commented filename reads "PostCommonQuestLikeResponseDTO.swift" to match the
Swift file/type naming convention (e.g., the struct/class
PostCommonQuestLikeResponseDTO).

Comment thread ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift Outdated
@@ -12,7 +12,10 @@ final class CommonQuestViewModel {

private let cancellables = Set<AnyCancellable>()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

cancellables 프로퍼티는 두 ViewModel 모두에서 mutable하게 선언되어야 합니다.

CommonQuestViewModelCommonQuestMyAnswerViewModel 모두 cancellableslet으로 선언했지만, Combine 구독을 저장하려면 var로 선언해야 합니다. 이는 일반적인 Combine 사용 패턴에서 .store(in: &cancellables)를 사용하기 위해 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`
at line 13, The cancellables property is declared immutable but must be mutable
to use Combine’s `.store(in: &cancellables)`; change the declaration of
`cancellables` from `let cancellables = Set<AnyCancellable>()` to a mutable
`var` in both `CommonQuestViewModel` and `CommonQuestMyAnswerViewModel` so
Combine subscriptions can be stored (refer to the `cancellables` property and
usage of `.store(in: &cancellables)` in those classes).

@juri123123 juri123123 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

isLike를 전체 조회에서 관리하는 것 보다는 각 post마다 관리하는게 더 유지보수하기 좋을 것 같아요 !

Comment on lines +70 to +79
private func postCommonQuestLike(answerID: Int) {
Task {
do {
let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID)
likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount)))
} catch (let error as ByeBooError) {
likeCountSubject.send(.failure(error))
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요기 task 취소할 수 있도록 변수로 관리해줘도 좋을 듯 싶습니다 !

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영하겠습니다 !!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift (1)

29-30: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

미사용 프로퍼티 likeCounts 제거 필요

Line 30의 likeCounts 프로퍼티는 Line 115에서 설정되지만 이후 어디에서도 읽히지 않습니다. 좋아요 수는 likeCountLabel에 직접 설정되므로 이 프로퍼티는 불필요합니다.

♻️ 제안하는 수정
 weak var delegate: CommonQuestLikeCommentProtocol?
 
 private var answerID: Int = 0
-private var likeCounts: Int = 0

그리고 Line 115도 제거:

 ) {
     self.answerID = answerID
-    self.likeCounts = likeCount
     answerContentTextView.do {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift`
around lines 29 - 30, Remove the unused property `likeCounts` from the
QuestContentView class. Delete the private variable declaration `likeCounts: Int
= 0` at line 30, and also remove the line that assigns a value to `likeCounts`
at line 115. Since the like count is set directly to the `likeCountLabel` and
the `likeCounts` property is never read elsewhere in the code, maintaining this
intermediate variable is unnecessary and creates code clutter.
🧹 Nitpick comments (1)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift (1)

71-88: ⚖️ Poor tradeoff

세 ViewModel에서 좋아요 처리 로직 중복.

postCommonQuestLike(answerID:) 메서드가 세 ViewModel에서 거의 동일하게 구현되어 있습니다. 현재는 private 메서드이므로 허용 가능하지만, 향후 유지보수를 위해 공통 프로토콜 extension이나 base class로 리팩토링을 고려해볼 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`
around lines 71 - 88, The `postCommonQuestLike(answerID:)` method is duplicated
across three ViewModels: CommonQuestViewModel.swift (71-88),
CommonQuestMyAnswerViewModel.swift (72-89), and
CommonQuestHistoryViewModel.swift (86-103). Extract this shared logic into a
common location by creating a protocol with a default implementation in an
extension (or using a base class if other shared functionality exists). Define
the method in the protocol extension to handle the Task cancellation, error
handling, and Subject updates using the shared pattern. Then remove the
duplicated `postCommonQuestLike(answerID:)` implementations from all three
ViewModel files and have them conform to the protocol instead, ensuring all
three ViewModels call the shared implementation which will reduce code
duplication and improve future maintainability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`:
- Around line 79-82: The error handling in the catch blocks across three
ViewModel files is silently ignoring non-ByeBooError exceptions, causing the UI
to hang indefinitely. In CommonQuestViewModel.swift (lines 79-82),
CommonQuestMyAnswerViewModel.swift (lines 80-83), and
CommonQuestHistoryViewModel.swift (lines 94-97), replace the early return with
logic that logs the unknown error, wraps it as a ByeBooError, and sends the
failure to the subject. For any error that cannot be cast to ByeBooError,
explicitly handle it by creating a ByeBooError wrapper and calling the
appropriate subject.send method with the wrapped error to ensure the UI receives
proper error notification instead of hanging indefinitely.
- Around line 71-88: A race condition exists in the postCommonQuestLike method
across three ViewModel files where likeTasks[answerID] = nil is executed
unconditionally after the do-catch block, allowing cancelled Tasks to remove
entries from the dictionary before new Tasks can cancel them. In
CommonQuestViewModel.swift at lines 71-88, move the likeTasks[answerID] = nil
statement (currently at line 86) inside the guard !Task.isCancelled block so
that only non-cancelled Tasks clean up the dictionary. Apply the identical fix
to CommonQuestMyAnswerViewModel.swift at lines 72-89 (move line 87's
likeTasks[answerID] = nil into the cancellation guard), and to
CommonQuestHistoryViewModel.swift at lines 86-103 (move line 101's
likeTasks[answerID] = nil into the cancellation guard).

---

Outside diff comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift`:
- Around line 29-30: Remove the unused property `likeCounts` from the
QuestContentView class. Delete the private variable declaration `likeCounts: Int
= 0` at line 30, and also remove the line that assigns a value to `likeCounts`
at line 115. Since the like count is set directly to the `likeCountLabel` and
the `likeCounts` property is never read elsewhere in the code, maintaining this
intermediate variable is unnecessary and creates code clutter.

---

Nitpick comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`:
- Around line 71-88: The `postCommonQuestLike(answerID:)` method is duplicated
across three ViewModels: CommonQuestViewModel.swift (71-88),
CommonQuestMyAnswerViewModel.swift (72-89), and
CommonQuestHistoryViewModel.swift (86-103). Extract this shared logic into a
common location by creating a protocol with a default implementation in an
extension (or using a base class if other shared functionality exists). Define
the method in the protocol extension to handle the Task cancellation, error
handling, and Subject updates using the shared pattern. Then remove the
duplicated `postCommonQuestLike(answerID:)` implementations from all three
ViewModel files and have them conform to the protocol instead, ensuring all
three ViewModels call the shared implementation which will reduce code
duplication and improve future maintainability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b101713-18e0-48d8-b8ba-cdb96628a276

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc3258 and 4e431cd.

📒 Files selected for processing (12)
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestLikeEntity.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift
🚧 Files skipped from review as they are similar to previous changes (5)
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift

Comment on lines +71 to +88
private func postCommonQuestLike(answerID: Int) {
likeTasks[answerID]?.cancel()

likeTasks[answerID] = Task {
do {
let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID)
guard !Task.isCancelled else { return }
likeCountSubject.send(.success((answerID: answerID, entity)))
} catch {
guard let error = error as? ByeBooError else {
return
}
guard !Task.isCancelled else { return }
likeCountSubject.send(.failure(error))
}
likeTasks[answerID] = nil
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

세 ViewModel 모두에서 Task 정리 경쟁 조건 발생.

세 파일 모두 postCommonQuestLike(answerID:) 메서드에서 likeTasks[answerID] = nil이 do-catch 블록 외부에 위치하여, Task가 취소된 후에도 무조건 실행됩니다. 사용자가 좋아요 버튼을 빠르게 연타하면 이전 Task가 새 Task를 딕셔너리에서 제거하여 취소가 불가능해집니다.

  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L71-L88: L86의 likeTasks[answerID] = nilif !Task.isCancelled 블록 안으로 이동하여 취소된 Task가 정리하지 않도록 수정
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L72-L89: L87의 likeTasks[answerID] = nil을 동일하게 취소 확인 블록 안으로 이동
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L86-L103: L101의 likeTasks[answerID] = nil을 동일하게 취소 확인 블록 안으로 이동
📍 Affects 3 files
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L71-L88 (this comment)
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L72-L89
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L86-L103
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`
around lines 71 - 88, A race condition exists in the postCommonQuestLike method
across three ViewModel files where likeTasks[answerID] = nil is executed
unconditionally after the do-catch block, allowing cancelled Tasks to remove
entries from the dictionary before new Tasks can cancel them. In
CommonQuestViewModel.swift at lines 71-88, move the likeTasks[answerID] = nil
statement (currently at line 86) inside the guard !Task.isCancelled block so
that only non-cancelled Tasks clean up the dictionary. Apply the identical fix
to CommonQuestMyAnswerViewModel.swift at lines 72-89 (move line 87's
likeTasks[answerID] = nil into the cancellation guard), and to
CommonQuestHistoryViewModel.swift at lines 86-103 (move line 101's
likeTasks[answerID] = nil into the cancellation guard).

Comment on lines +79 to +82
} catch {
guard let error = error as? ByeBooError else {
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

세 ViewModel 모두에서 ByeBooError가 아닌 에러 무시.

세 파일 모두 ByeBooError로 캐스팅할 수 없는 에러를 조용히 무시하여 subject에 실패를 전송하지 않습니다. 이로 인해 UI가 무한정 응답을 기다리게 됩니다.

  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L79-L82: 알 수 없는 에러를 로깅하고 ByeBooError로 래핑하여 실패 전송
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L80-L83: 동일하게 에러 로깅 및 래핑 추가
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L94-L97: 동일하게 에러 로깅 및 래핑 추가
📍 Affects 3 files
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift#L79-L82 (this comment)
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift#L80-L83
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift#L94-L97
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift`
around lines 79 - 82, The error handling in the catch blocks across three
ViewModel files is silently ignoring non-ByeBooError exceptions, causing the UI
to hang indefinitely. In CommonQuestViewModel.swift (lines 79-82),
CommonQuestMyAnswerViewModel.swift (lines 80-83), and
CommonQuestHistoryViewModel.swift (lines 94-97), replace the early return with
logic that logs the unknown error, wraps it as a ByeBooError, and sends the
failure to the subject. For any error that cannot be cast to ByeBooError,
explicitly handle it by creating a ByeBooError wrapper and calling the
appropriate subject.send method with the wrapped error to ensure the UI receives
proper error notification instead of hanging indefinitely.

@dev-domo dev-domo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

리뷰가 늦어져 죄송합니다! 코멘트 확인 부탁드릴게요~

}

private func postCommonQuestLike(answerID: Int) {
likeTasks[answerID]?.cancel()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

저도 헷갈리는 부분이라 체크 한 번 부탁드려요! 현재 코드에서 cancel 메서드는 호출하고 있지만 실제로 취소 에러를 던지는 부분은 없는 것 같은데, 그럼 취소를 해도 API 호출 작업이 끝까지 실행되지 않을까요?

@@ -12,7 +12,10 @@ final class CommonQuestViewModel {

private let cancellables = Set<AnyCancellable>()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이 부분은 코드래빗 리뷰 반영해야 할 것 같아요!
private var cancellables

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

Labels

feat 새로운 기능 구현 및 API 연결 나연🐹

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 게시글 공감 API 연동

3 participants