Skip to content

Feat/#444 공통퀘스트 관련 조회 API들 연결#452

Merged
y-eonee merged 25 commits into
developfrom
feat/#444-댓글api
Jun 10, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/#444-\ub313\uae00api"
Merged

Feat/#444 공통퀘스트 관련 조회 API들 연결#452
y-eonee merged 25 commits into
developfrom
feat/#444-댓글api

Conversation

@y-eonee

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

Copy link
Copy Markdown
Member

🔗 연결된 이슈

📄 작업 내용

  • 400번에 이은 444번을 제가 겟했네욤
  • 이전 UI PR에서 까먹고 작업하지 않은.. 수정/삭제/신고/차단 바텀시트를 구현했습니다.
  • 나의 공통퀘스트 조회 v2 api를 연결했습니다.
  • 공통퀘스트 전체 조회 v2 api를 연결했습니다.
  • 공통퀘스트 상세 조회 조회 v2 api를 연결했습니다.
    • 기존에는 CommonQuestVC > historyVC로 넘어갈 때 데이터를 전달해주는 방식으로 @dev-domo 께서 해주고 계셨는데요..!
    • 3-2 스프에서 댓글이 들어오면서 UI가 변경됨에 따라 historyVC에서 writer를 알고 있어야 했습니다. (ex. 공통퀘스트 수정 후 다시 history로 돌아왔을 때 프로필아이콘을 들고있지 않아 뷰에 띄울수 없었어요)
    • 그래서 상세 조회 v2 api를 연결하여 데이터를 이전 뷰컨에서 넘겨받지 않고 서버에서 들고오도록 수정하였습니다.
    • 이에 따라 histroyVC에서 점점 뚱뚱해지던 configure 함수를 대폭 간소화하는 작업을 거쳤습니다.
  • 전체 조회, 상세조회의 dto가 묘하게 조금씩 달라서 entity를 만들 때 조금 고민을 했습니다.. 공통된 부분이 상당히 많거든요
전체 조회 상세 조회

보시면 answer 관련 부분이 공통된 부분이 많은데.. 하나의 entity로 만들고 싶었지만 결정적으로 answerID가 없어서 뭔가 재사용하기에 애매해지는 느낌이 있었어요...! 그래서 일단 전체조회에서 사용하는 것은 CommonQuestAnswerEntity, 상세조회에서 사용하는 것은 CommonQuestDetailEntity로 분리해두었는데 혹시 이 부분에 대해 좋은 의견이 있다면 부탁드립니닷..

  • n시간전 어쩌구... 이 로직을 ServerDateFormatter에서 전역으로 수행할 수 있는 함수를 추가해두었는데요! @dev-domo 님이 먼저 뷰모델로 발빠르게 작업해주셔서 머지되면 따로 리팩토링하는 작업을 할 예정입니다.
  • 바텀 시트 띄우는 코드를 UIViewController extension으로 추가했습니다.
구현 내용 전체 조회 연결 상세조회 연결 / 바텀시트
GIF

*댓글 api는 아직 연결 전이라 스웨거에서 임의로 달았습니다!

💻 주요 코드 설명

CommonQuestHistoryVC

private func bind() {
        viewModel.output.fetchCommonQuestDetailPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] result in
                switch result {
                case .success(let entity):
                    self?.bindData(entity: entity) // view에 데이터 바인딩
                    self?.applySnapshot(commentList: entity.comments) // 댓글은 따로 합니다
                case .failure(let error):
                    ByeBooLogger.debug(error)
                }
            }
            .store(in: &cancellable)
    }
    
    private func bindData(entity: CommonQuestDetailEntity) {
        /// bind.... 
        )
    }
extension CommonQuestHistoryViewController {
    func configure(answerID: Int) {
        self.answerID = answerID
    }
}

configure 함수가 간소화되었습니다.

isMyAnswer, isMyComment

내 답변, 내 댓글인지 구분하기 위하여 관련 entity에 해당 값을 추가했습니다.

func fetchCommonQuestDetail(answerID: Int) async throws -> CommonQuestDetailEntity {
        let userName: String = userDefaultsService.load(key: .userName) ?? ""
        let commonQuestDetail = try await network.request(
            CommonQuestAPI.fetchCommonQuestDetail(asnwerID: answerID),
            decodingType: CommonQuestAnswerDetailResponseDTO.self
        )
        
        return commonQuestDetail.toEntity(userName: userName) // dto > entity 매핑할 때 userName과 writer를 비교합니다. 
    }

UIViewController+

extension UIViewController {
    func presentBottomSheet(_ viewController: UIViewController, height: CGFloat) {
        if let sheet = viewController.sheetPresentationController {
            sheet.detents = [.custom { _ in height }]
            sheet.prefersGrabberVisible = true
            sheet.prefersScrollingExpandsWhenScrolledToEdge = false
            sheet.preferredCornerRadius = 8
        }
        self.present(viewController, animated: true)
    }
}
presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH)

이렇게 사용할 수 있습니다!

Summary by CodeRabbit

  • 새 기능

    • 공통 퀘스트 답변 상세 조회 화면 추가
    • 상대적 시간 표시(예: "방금 전", "2분 전") 추가
  • 개선 사항

    • 답변/댓글의 좋아요·댓글 수 및 좋아요 상태가 실제 데이터와 동기화
    • 작성자 닉네임·프로필 표시 및 내 글/댓글 여부 표시 강화
    • 댓글/답변 메뉴: 내 항목은 수정·삭제, 타인 항목은 차단·신고로 분기
    • 바텀시트 표시 방식 통일 및 화면 전환 흐름 간소화

@y-eonee y-eonee linked an issue Jun 9, 2026 that may be closed by this pull request
1 task
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

상세 조회용 DTO/엔티티 계약을 갱신하고, fetchCommonQuestDetail 엔드포인트·유스케이스·저장소 호출을 연결합니다. History 화면은 ViewModel 주입형 + Combine로 리팩토링되어 상세 응답을 바인딩하고, 댓글 메뉴·하단시트 및 상대시간 포맷을 통합합니다.

Changes

공통 퀘스트 상세 조회 및 데이터 바인딩

Layer / File(s) Summary
DTO 계약 및 엔티티 필드 업데이트
Data/Model/CommonQuestAnswerDetailResponseDTO.swift, Data/Model/CommonQuestAnswersResponseDTO.swift, Data/Model/CommonQuestMyAnswersResponseDTO.swift, Data/Model/CommonQuestAnswerRepliesResponseDTO.swift, Domain/Entity/CommonQuestAnswersEntity.swift, Domain/Entity/CommonQuestCommentEntity.swift, Domain/Entity/CommonQuestDetailEntity.swift, Domain/Entity/CommonQuestMyAnswersEntity.swift
상세/간단 답변 DTO와 댓글 DTO에 메타 필드(작성자 id/닉네임, likeCount, commentCount, isLiked, writtenAt 등)를 추가하고, DTO→엔티티 변환 시 userID/userName 인자를 전달하여 isMyAnswer/isMyComment를 계산하도록 toEntity(...) 시그니처를 갱신했습니다.
상세 조회 API 및 저장소·유스케이스 연결
Data/Network/EndPoint/CommonQuestAPI.swift, Domain/Interface/CommonQuestInterface.swift, Domain/UseCase/FetchCommonQuestDetailUseCase.swift, Data/Repository/CommonQuestRepository.swift, Domain/DomainDependencyAssembler.swift
CommonQuestAPI.fetchCommonQuestDetail(answerID:)를 추가하고, 도메인 인터페이스·유스케이스·Repository에서 상세 호출을 연결해 CommonQuestDetailEntity를 반환하도록 구현 및 DI 등록을 추가했습니다.
히스토리 ViewModel Combine 리팩토링
Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift
ViewModelType 스타일로 Input/Output을 도입하고 FetchCommonQuestDetailUseCase를 주입해 상세를 비동기로 조회·내부 entity에 저장한 뒤 Combine 퍼블리셔로 결과를 방출합니다.
History VC 바인딩 및 스냅샷
Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift
뷰모델 주입형으로 전환하고 viewWillAppear 트리거로 상세 로드, Combine 구독으로 bindData(entity:)·applySnapshot(commentList:)를 호출해 화면을 갱신합니다. 작성 시각은 ServerDateFormatter의 상대 시간 문자열을 사용합니다.

댓글 메뉴 및 하단시트 통합

Layer / File(s) Summary
하단시트 헬퍼 및 컨트롤러 분리
Presentation/Extension/UIViewController+.swift, Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift
공통 presentBottomSheet(_:height:) 헬퍼를 추가하고, BottomSheet의 편집용 설정을 configureWhenEdit로 분리하며 일반 configure의 id 파라미터를 targetID로 통일했습니다.
댓글 셀 및 프로토콜 변경
Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift
메뉴 버튼을 UIImageView→UIButton으로 교체하고 CommentProtocol에 menuButtonDidTap(commentID:isMyComment:)를 추가해 메뉴 터치를 전달하도록 변경했습니다.
아카이브 타입 세분화
Presentation/Enum/CommonQuestArchiveType.swift
아카이브 타입을 myAnswer/otherAnswer/myComment/otherComment로 세분화하고 각 타입별 항목을 재구성했습니다.
히스토리·댓글 UI 연동
Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift, Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift
댓글 셀에 isMyComment 플래그 전달, menuButtonDidTap 구현을 통해 공통 commonBottomSheetUp(...) 경로로 하단시트를 표시하도록 통합했습니다.

목록 바인딩·내비게이션·유틸

Layer / File(s) Summary
답변 셀 바인딩 업데이트
Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift
셀 바인딩에서 작성자 표시를 answer.writer로 바꾸고 좋아요/댓글 표시를 DTO 기반 값(answer.isLiked, answer.likeCount, answer.commentCount)으로 사용하도록 변경했습니다.
목록에서 상세 진입 단순화
Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift, Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift
상세 진입 시 모든 필드를 전달하던 것을 configure(answerID:) 호출로 통일해 상세 화면에서 서버 조회하도록 변경했습니다.
뷰모델 접근자·작성시각 포맷 전환
Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift, Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift
getAnswerID(at:) 추가, getWrittenAt(at:)에서 ServerDateFormatter.relativeTimeString(...) 사용으로 작성시각 포맷 생성 로직을 교체하고, 내 답변 엔티티에 isLiked/likeCount/commentCount를 포함하도록 확장했습니다.
상대 시간 유틸 추가
Data/Model/Util/ServerDateFormatter.swift
ISO 문자열 파싱 후 현재 시각 차이를 기반으로 "방금 전/분 전/시간 전" 등의 상대시간 문자열을 반환하는 relativeTimeString(from:)을 추가했습니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

Poem

상세를 따라 달리는 토끼가 왔네, 🐇
DTO에 이름 싣고 엔티티에 빛을 더했지.
Combine의 망에서 데이터 춤추고,
메뉴 탭에 하단시트가 살짝 내려와 🎶
당근 한 조각 건네며 축하할게요 🥕

🚥 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들 연결'로, 변경사항의 핵심인 CommonQuest 관련 여러 조회 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/#444-댓글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.

@y-eonee y-eonee self-assigned this Jun 9, 2026
@y-eonee y-eonee added 나연🐹 feat 새로운 기능 구현 및 API 연결 labels Jun 9, 2026

@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: 9

Caution

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

⚠️ Outside diff range comments (2)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift (1)

80-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

configureWhenEdit에서 시트 UI 타입이 갱신되지 않습니다.

sheetType만 바꾸고 rootView를 재구성하지 않아, 표시된 메뉴와 실제 액션 분기가 어긋날 수 있습니다.

수정 제안
     func configureWhenEdit(
         sheeetType: CommonQuestArchiveType,
         answerID: Int? = nil,
         answer: String? = nil,
         question: String? = nil,
         writtenAt: String? = nil
     ) {
         self.sheetType = sheeetType
+        rootView = CommonQuestBottomSheetView(sheetType: sheeetType)
         self.answerID = answerID
         self.answer = answer
         self.question = question
         self.writtenAt = writtenAt
     }
🤖 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/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift`
around lines 80 - 92, configureWhenEdit currently updates only the sheetType and
related properties but does not refresh the visible UI, causing the displayed
menu/actions to mismatch the new sheetType; after assigning self.sheetType (and
other properties) update/rebuild the view state by calling the view refresh
method on rootView (e.g., invoke the rootView's configuration/update method or a
rebuildRootView()/configureUI()) so the visible menu and action branching
reflect the new sheetType; locate configureWhenEdit, sheetType, and rootView in
the file and ensure you call the appropriate rootView update routine (or add
one) to reconfigure UI based on the new sheetType and data.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift (1)

30-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

answerID 미설정 상태에서 상세 조회가 호출될 수 있습니다.

Line 30 기본값이 0인데, Line 52에서 검증 없이 viewWillAppear 액션을 호출하고 있습니다. configure(answerID:) 호출이 누락되면 잘못된 ID로 API 요청이 나갑니다. 최소한 answerID > 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/ViewController/CommonQuestHistoryViewController.swift`
around lines 30 - 53, The view controller calls
viewModel.action(.viewWillAppear(answerID: answerID)) in viewWillAppear without
ensuring answerID was set, so default 0 may trigger an invalid API call; add a
guard check in CommonQuestHistoryViewController.viewWillAppear to only call
viewModel.action when answerID > 0 (and otherwise skip the call or assert/log an
error), and ensure the existing configure(answerID:) is used to set answerID
before presenting the view controller.
🤖 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/CommonQuestAnswerDetailResponseDTO.swift`:
- Around line 38-43: The ownership checks use nickname comparison (userName ==
writer) which misclassifies when nicknames change or collide; update the
toEntity(userName: String) -> CommonQuestDetailEntity signature to accept the
current user's identifier (e.g., userID: String/Int) instead of userName, then
replace all comparisons that use writer with writerId (e.g., set
isMyAnswer/isMyComment using writerId == userID). Also update the nested calls
(answer.toEntity(...) and comments.map { $0.toEntity(...) }) to pass the userID
and adjust the corresponding DTO toEntity methods (and their isMy* logic) to use
writerId vs the nickname.

In `@ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift`:
- Around line 35-44: The toEntity method in CommonQuestMyAnswersResponseDTO is
hardcoding isLiked: false and discarding the decoded value; update toEntity
(which constructs a CommonQuestMyAnswerEntity) to pass the DTO's decoded isLiked
property instead of false (e.g., use the instance property isLiked or liked as
appropriate), ensuring the entity reflects the actual server value.

In `@ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift`:
- Around line 147-152: The current fallback userName =
userDefaultsService.load(key: .userName) ?? "" causes ownership checks in
result.toEntity(userName: userName) to silently fail; change this by either (A)
making absence explicit: if userDefaultsService.load(key: .userName) is nil then
throw/return a failure before calling UsersAPI.fetchCommonQuestAnswers /
result.toEntity(userName:), or (B) switch ownership key to a stable identifier:
load userID (e.g., userDefaultsService.load(key: .userID)) and update the
mapping call to use the userID-based toEntity variant (or modify toEntity to
accept userID) so ownership checks rely on userID instead of an empty-string
fallback. Ensure adjustments involve userDefaultsService.load(...) and the
result.toEntity(...) usage.

In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift`:
- Around line 40-44: In CommonQuestArchiveType's case otherComment the menu
items' titles "차단하기" and "신고하기" are incorrectly mapped to actions .edit and
.delete; update the Item action mappings so "차단하기" uses the block action (e.g.,
.block) and "신고하기" uses the report action (e.g., .report) to match behavior in
the code paths that handle blocking/reporting.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift`:
- Around line 270-277: In commonBottomSheetUp, the bottom-sheet is being
configured with writerID even when commentID is provided, causing incorrect
targets for comment actions; update the logic in commonBottomSheetUp (and the
configure call on commonQuestBottomSheet) to pass the correct comment-related
identifier — use commentID (or the comment author's ID if your protocol expects
an author id) as the targetID when the function is invoked with a comment
context instead of writerID, and if necessary adjust the configure signature or
add an explicit commenterID parameter so sheeetType: (.myComment/.otherComment)
is paired with the proper target identifier.
- Around line 247-289: bindData currently formats writtenAt for
rootView.configure but never assigns it to the view controller's property, so
commonBottomSheetUp's call to configureWhenEdit(... writtenAt: writtenAt)
receives an empty value; fix by setting self.writtenAt =
ServerDateFormatter.shared.relativeTimeString(from: answer.writtenAt) ?? "" (or
compute once into a local let formattedWrittenAt and assign it to
self.writtenAt) inside bindData before calling rootView.configure so the
property used in commonBottomSheetUp (and configureWhenEdit) contains the
correct value.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift`:
- Around line 99-105: menuButtonDidTap currently passes the comment's ID as the
targetID for all actions which breaks identifying user-targeted actions
(block/report); update the call site in menuButtonDidTap to provide both the
commentID and the writerID separately to the bottom sheet by using
ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() and
calling commonQuestBottomSheet.configure with distinct parameters (e.g.,
commentID: commentID, writerID: writerID) and adjust the sheeetType selection as
before; also update the CommonQuestBottomSheetViewController.configure signature
to accept both commentID and writerID (and update usages) so user-target actions
use writerID while comment-specific actions use commentID.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift`:
- Around line 55-59: In the catch block inside CommonQuestHistoryViewModel
(where you currently guard-cast error to ByeBooError and return), stop ignoring
non-BryBooError values: always send a failure through fetchCommentListSubject by
mapping any non-ByeBooError to a domain error (e.g., a .unknown or .unexpected
case) and call fetchCommentListSubject.send(.failure(mappedError)); keep the
existing behavior when the error is already a ByeBooError by sending it
directly.

In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift`:
- Around line 190-194: The DI failure path in ViewControllerFactory currently
calls DIErrorHandle() which re-enters makeLoginViewController() and can cause
recursive stack overflow when resolving CommonQuestHistoryViewModel (via
DIContainer.shared.resolve) or LoginViewModel fails; replace the call to
DIErrorHandle() in the CommonQuestHistoryViewController creation branch with a
non-reentrant immediate failure path: log the error synchronously (include the
failed type, e.g., CommonQuestHistoryViewModel), then call fatalError() (or
throw) without invoking makeLoginViewController() or any code that may resolve
other view models; alternatively implement a separate
DIErrorHandleNonReentrant() used here that only logs and exits to prevent
recursion.

---

Outside diff comments:
In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift`:
- Around line 80-92: configureWhenEdit currently updates only the sheetType and
related properties but does not refresh the visible UI, causing the displayed
menu/actions to mismatch the new sheetType; after assigning self.sheetType (and
other properties) update/rebuild the view state by calling the view refresh
method on rootView (e.g., invoke the rootView's configuration/update method or a
rebuildRootView()/configureUI()) so the visible menu and action branching
reflect the new sheetType; locate configureWhenEdit, sheetType, and rootView in
the file and ensure you call the appropriate rootView update routine (or add
one) to reconfigure UI based on the new sheetType and data.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift`:
- Around line 30-53: The view controller calls
viewModel.action(.viewWillAppear(answerID: answerID)) in viewWillAppear without
ensuring answerID was set, so default 0 may trigger an invalid API call; add a
guard check in CommonQuestHistoryViewController.viewWillAppear to only call
viewModel.action when answerID > 0 (and otherwise skip the call or assert/log an
error), and ensure the existing configure(answerID:) is used to set answerID
before presenting the view controller.
🪄 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: 813cacf8-7556-4e75-91d5-b96fea33320d

📥 Commits

Reviewing files that changed from the base of the PR and between e45cdc7 and 1d119ea.

📒 Files selected for processing (32)
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/UsersAPI.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestDetailEntity.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UIViewController+.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.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/CommonQuestReplyViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.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
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift

Comment on lines +38 to +43
func toEntity(userName: String) -> CommonQuestDetailEntity {
.init(
question: question,
answer: answer.toEntity(userName: userName),
comments: comments.map { $0.toEntity(userName: userName) }
)

@coderabbitai coderabbitai Bot Jun 9, 2026

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 | 🏗️ Heavy lift

소유권 판별을 닉네임 기준으로 두면 오분류가 발생합니다.

Line [50], Line [66]의 userName == writer 비교는 닉네임 중복/변경 시 isMyAnswer/isMyComment가 잘못 계산될 수 있습니다. 상세 화면의 수정/삭제/신고/차단 분기가 이 값에 의존하므로 writerId와 현재 사용자 userID 비교로 바꾸는 게 안전합니다.

🔧 제안 수정 (이 파일 기준)
-extension CommonQuestAnswerDetailResponseDTO {
-    func toEntity(userName: String) -> CommonQuestDetailEntity {
+extension CommonQuestAnswerDetailResponseDTO {
+    func toEntity(userID: Int) -> CommonQuestDetailEntity {
         .init(
             question: question,
-            answer: answer.toEntity(userName: userName),
-            comments: comments.map { $0.toEntity(userName: userName) }
+            answer: answer.toEntity(userID: userID),
+            comments: comments.map { $0.toEntity(userID: userID) }
         )
     }
 }
 
 extension CommonQuestAnswerSimpleResponseDTO {
-    func toEntity(userName: String) -> CommonQuestAnswerDetailEntity {
+    func toEntity(userID: Int) -> CommonQuestAnswerDetailEntity {
         .init(
-            isMyAnswer: userName == writer ? true : false,
+            isMyAnswer: userID == writerId,
             content: content,
             writerID: writerId,
             writer: writer,
@@
 extension CommonQuestCommentResponseDTO {
-    func toEntity(userName: String) -> CommonQuestCommentEntity {
+    func toEntity(userID: Int) -> CommonQuestCommentEntity {
         return .init(
-            isMyComment: userName == writer ? true : false,
+            isMyComment: userID == writerId,
             commentID: commentId,
             replyCount: replyCount,
             writerID: writerId,

Also applies to: 48-50, 64-67

🤖 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/CommonQuestAnswerDetailResponseDTO.swift`
around lines 38 - 43, The ownership checks use nickname comparison (userName ==
writer) which misclassifies when nicknames change or collide; update the
toEntity(userName: String) -> CommonQuestDetailEntity signature to accept the
current user's identifier (e.g., userID: String/Int) instead of userName, then
replace all comparisons that use writer with writerId (e.g., set
isMyAnswer/isMyComment using writerId == userID). Also update the nested calls
(answer.toEntity(...) and comments.map { $0.toEntity(...) }) to pass the userID
and adjust the corresponding DTO toEntity methods (and their isMy* logic) to use
writerId vs the nickname.

✅ Addressed in commit 1c1c7f7

Comment thread ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift Outdated
Comment thread ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift Outdated
Comment on lines +99 to +105
func menuButtonDidTap(commentID: Int, isMyComment: Bool) {
let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController()

commonQuestBottomSheet.configure(
sheeetType: isMyComment ? .myComment : .otherComment ,
targetID: commentID
)

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 | 🏗️ Heavy lift

댓글 메뉴에서 commentIDtargetID로 전달하면 차단/신고 대상 식별이 깨집니다.

현재 흐름은 사용자 대상 액션(차단)에도 댓글 ID를 전달합니다. 댓글 메뉴는 최소 writerIDcommentID를 분리 전달하도록 호출 계약을 분리하는 게 안전합니다.

🤖 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/CommonQuestReplyViewController.swift`
around lines 99 - 105, menuButtonDidTap currently passes the comment's ID as the
targetID for all actions which breaks identifying user-targeted actions
(block/report); update the call site in menuButtonDidTap to provide both the
commentID and the writerID separately to the bottom sheet by using
ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() and
calling commonQuestBottomSheet.configure with distinct parameters (e.g.,
commentID: commentID, writerID: writerID) and adjust the sheeetType selection as
before; also update the CommonQuestBottomSheetViewController.configure signature
to accept both commentID and writerID (and update usages) so user-target actions
use writerID while comment-specific actions use commentID.

Comment on lines +55 to +59
} catch {
guard let error = error as? ByeBooError else {
return
}
fetchCommentListSubject.send(.failure(error))

@coderabbitai coderabbitai Bot Jun 9, 2026

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

알 수 없는 에러를 무시하지 말고 실패 이벤트를 항상 방출하세요.

현재는 ByeBooError 캐스팅에 실패하면 조용히 return해서 화면이 로딩 상태로 남을 수 있습니다. 모든 에러를 도메인 에러로 매핑해 .failure를 보장해 주세요.

🤖 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/CommonQuestHistoryViewModel.swift`
around lines 55 - 59, In the catch block inside CommonQuestHistoryViewModel
(where you currently guard-cast error to ByeBooError and return), stop ignoring
non-BryBooError values: always send a failure through fetchCommentListSubject by
mapping any non-ByeBooError to a domain error (e.g., a .unknown or .unexpected
case) and call fetchCommentListSubject.send(.failure(mappedError)); keep the
existing behavior when the error is already a ByeBooError by sending it
directly.

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.

@y-eonee 이 부분 catch let error as ByeBooError로 싹 리팩토링 해보는 것도 좋은 방법 인 것 같아요

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

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.

반영했습니다 !!!!

Comment on lines +190 to +194
guard let viewModel = DIContainer.shared.resolve(type: CommonQuestHistoryViewModel.self) else {
DIErrorHandle()
fatalError()
}
return CommonQuestHistoryViewController(viewModel: viewModel)

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

DI 실패 처리 경로가 재귀 진입으로 비정상 종료를 유발할 수 있습니다.

Line 190의 DIErrorHandle() 호출은 내부 Line 241의 makeLoginViewController()를 다시 타면서 같은 실패 패턴으로 재귀 진입할 수 있습니다. LoginViewModel 해석도 실패하면 fatalError() 전에 스택 오버플로우가 발생할 수 있습니다. 이 경로에서는 재진입 가능한 핸들러 호출 대신 즉시 로깅 후 종료로 분리하는 게 안전합니다.

수정 예시
 func makeCommonQuestHistoryViewController() -> CommonQuestHistoryViewController {
     guard let viewModel = DIContainer.shared.resolve(type: CommonQuestHistoryViewModel.self) else {
-        DIErrorHandle()
-        fatalError()
+        ByeBooLogger.error(ByeBooError.DIFailedError)
+        fatalError("Failed to resolve CommonQuestHistoryViewModel")
     }
     return CommonQuestHistoryViewController(viewModel: viewModel)
 }
🤖 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/ViewControllerFactory.swift` around lines
190 - 194, The DI failure path in ViewControllerFactory currently calls
DIErrorHandle() which re-enters makeLoginViewController() and can cause
recursive stack overflow when resolving CommonQuestHistoryViewModel (via
DIContainer.shared.resolve) or LoginViewModel fails; replace the call to
DIErrorHandle() in the CommonQuestHistoryViewController creation branch with a
non-reentrant immediate failure path: log the error synchronously (include the
failed type, e.g., CommonQuestHistoryViewModel), then call fatalError() (or
throw) without invoking makeLoginViewController() or any code that may resolve
other view models; alternatively implement a separate
DIErrorHandleNonReentrant() used here that only logs and exits to prevent
recursion.

@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.

고생하셧습니다 ! ! ! !

Comment on lines +125 to +128
guard index >= 0 && index < answers.count else {
return nil
}
return answers[index].answerID

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.

조아요

Comment on lines +55 to +59
} catch {
guard let error = error as? ByeBooError else {
return
}
fetchCommentListSubject.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.

@y-eonee 이 부분 catch let error as ByeBooError로 싹 리팩토링 해보는 것도 좋은 방법 인 것 같아요


configureDataSource()
applySnapshot()
// applySnapshot()

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.

요기 주석 !

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.

어머나

Comment on lines +30 to +41
private var answerID: Int = 0
private var writerID: Int = 0

private var isMyAnswer: Bool = false
private var nickname: String = ""
private var profileIcon: UIImage?
private var answer: String = ""
private var question: String = ""
private var writtenAt: String = ""
private var isLiked: Bool = false
private var likeCount: Int = 0
private var commentCount: Int = 0

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.

요거 entity 하나로 묶어서 해도 되나요 ??

아니면 그냥 viewomodel.entity로 접근하게 하면 안되는 이유가 있을까요 ?!

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.

오! 수정해보겠습니다

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 (8)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift (1)

67-98: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Typed catch가 non-ByeBooError를 처리하지 못해 UI가 응답 없이 멈출 수 있습니다.

blockUser, reportCommonQuest, deleteCommonQuest 모두 catch(let error as ByeBooError) 패턴을 사용합니다. UseCase가 ByeBooError가 아닌 에러를 throw하면 해당 subject에 failure가 전송되지 않아 ViewController가 결과를 받지 못합니다.

세 메서드 모두에 fallback catch 블록을 추가하거나, ByeBooError.unknownError로 래핑하여 전송하세요.

🐛 Fallback catch 추가 예시 (blockUser)
     private func blockUser(userID: Int) {
         Task {
             do {
                 try await blockUserUseCase.execute(userID: userID)
                 blockUserSubject.send(.success(()))
             } catch(let error as ByeBooError) {
                 blockUserSubject.send(.failure(error))
+            } catch {
+                blockUserSubject.send(.failure(.unknownError))
             }
         }
     }
🤖 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/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift`
around lines 67 - 98, The typed catch blocks in blockUser(userID:),
reportCommonQuest(answerID:) and deleteCommonQuest(answerID:) only handle
ByeBooError, so non-ByeBooError throws never send a failure and the UI can hang;
update each Task to add a fallback catch (or a final catch) that maps any other
Error to a ByeBooError (e.g. ByeBooError.unknownError or wrap with context) and
send the corresponding subject (.failure(mappedError)) so the ViewController
always receives a result from blockUserSubject, reportQuestSubject and
deleteQuestSubject.
ByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swift (1)

55-63: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Typed catch가 AFError를 처리하지 못해 요청이 무한 대기할 수 있습니다.

TokenService.reissue()는 네트워크 실패 시 AFError를 throw합니다 (TokenService.swift Line 79-82 참조). catch(let error as ByeBooError)AFError를 캐치하지 못하므로, completion 핸들러가 호출되지 않아 요청이 무한 대기 상태에 빠집니다.

🐛 모든 에러 타입을 처리하도록 수정
             Task {
                 do {
                     try await self.tokenService.reissue()
                     ByeBooLogger.debug("401 Error -> 토큰 재발급 성공")
                     completion(.retry)
                 } catch(let error as ByeBooError) {
                     completion(.doNotRetryWithError(error))
+                } catch {
+                    completion(.doNotRetryWithError(error))
                 }
             }
🤖 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/Network/Service/NetworkInterceptor.swift` around
lines 55 - 63, The current typed catch in NetworkInterceptor's Task only handles
ByeBooError, so AFError thrown by tokenService.reissue() can bypass it and leave
completion uncalled; update the error handling in the Task around
tokenService.reissue() to include a general catch (or an explicit catch for
AFError) that forwards the caught error to completion (e.g.,
completion(.doNotRetryWithError(error))) after logging, ensuring both
ByeBooError and AFError (and any other thrown errors) are handled and completion
is always invoked.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift (1)

62-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

로그인 실패 시 non-ByeBooError가 처리되지 않습니다.

socialLoginUseCase.execute()ByeBooError가 아닌 에러를 throw하면 로깅도 되지 않고 socialLoginAuthSubject에 failure도 전송되지 않습니다. 로그인은 핵심 플로우이므로 모든 에러를 처리해야 합니다.

🐛 Fallback catch 추가
             } catch(let error as ByeBooError) {
                 ByeBooLogger.error(error)
                 socialLoginAuthSubject.send(.failure(error))
+            } catch {
+                ByeBooLogger.error(error)
+                socialLoginAuthSubject.send(.failure(.unknownError))
             }
🤖 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/Login/ViewModel/LoginViewModel.swift`
around lines 62 - 74, The current socialLogin(platform:) Task only catches
ByeBooError so non-ByeBooError throws are dropped; add a fallback catch block
after the existing catch(let error as ByeBooError) to handle all other errors
from socialLoginUseCase.execute(platform:), log them via ByeBooLogger.error
(including the error) and send a failure on socialLoginAuthSubject
(wrapping/propagating as an appropriate ByeBooError or a generic failure case)
so every thrown error is logged and the subject is notified.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift (1)

88-123: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

fetchDialoguefetchJourney의 typed catch가 non-ByeBooError를 놓칩니다.

fetchStatus()catch { if let error = error as? ByeBooError ... } 패턴으로 모든 에러를 처리하지만, fetchDialogue()fetchJourney()catch(let error as ByeBooError) 패턴을 사용하여 일관성이 없고 non-ByeBooError가 누락됩니다.

fetchStatus와 동일한 패턴을 적용하거나, fallback catch를 추가하세요.

🐛 fetchDialogue 수정 예시
             } catch(let error as ByeBooError) {
                 characterResultSubject.send(.failure(error))
+            } catch {
+                characterResultSubject.send(.failure(.unknownError))
             }
🤖 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/Home/ViewModel/HomeViewModel.swift`
around lines 88 - 123, fetchDialogue() and fetchJourney() currently use typed
catches that ignore non-ByeBooError errors; update their error handling to match
fetchStatus() by using an untyped catch and then conditionally casting the
caught error to ByeBooError before sending failure to characterResultSubject /
journeyResultSubject, and provide a sensible fallback action (e.g., send a
default state or call isHelperShown(...)) for non-ByeBooError cases so all
errors are handled consistently with fetchStatus().
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swift (1)

66-93: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

fetchBlocedUserListdeleteBlockedUser에서 non-ByeBooError가 처리되지 않습니다.

두 메서드 모두 catch(let error as ByeBooError) 패턴을 사용하여 ByeBooError가 아닌 에러 발생 시 subject에 failure가 전송되지 않습니다.

🐛 Fallback catch 추가 예시 (fetchBlocedUserList)
             } catch(let error as ByeBooError) {
                 ByeBooLogger.error(error)
                 getBlockedUsersListSubject.send(.failure(error))
+            } catch {
+                ByeBooLogger.error(error)
+                getBlockedUsersListSubject.send(.failure(.unknownError))
             }
🤖 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/MyPage/ViewModel/BlockedUserListViewModel.swift`
around lines 66 - 93, Both fetchBlocedUserList and deleteBlockedUser only catch
ByeBooError, so non-ByeBooError exceptions are ignored; add a fallback catch
block in each Task after the existing catch(let error as ByeBooError) to handle
any other Error, log it with ByeBooLogger.error, and send a failure to the
corresponding subject (getBlockedUsersListSubject for fetchBlocedUserList and
deleteBlockUserSubject for deleteBlockedUser), converting or wrapping the
generic Error into an appropriate ByeBooError case if needed before sending.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swift (1)

54-65: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Non-ByeBooError 발생 시 userInformationSubject에 failure가 전송되지 않습니다.

기존 강제 캐스팅(as!)보다 안전해졌지만, sendUserUseCase.execute()ByeBooError가 아닌 에러를 throw하면 UI에 실패가 전달되지 않습니다.

🐛 Fallback catch 추가
             } catch(let error as ByeBooError) {
                 userInformationSubject.send(.failure(error))
+            } catch {
+                userInformationSubject.send(.failure(.unknownError))
             }
🤖 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/Information/ViewModel/InformationViewModel.swift`
around lines 54 - 65, The catch currently only handles ByeBooError in the Task
inside InformationViewModel (around sendUserUseCase.execute), so non-ByeBooError
throws never notify the UI; update the Task to add a fallback catch that
captures any Error (in addition to the existing catch(let error as ByeBooError))
and call userInformationSubject.send(.failure(...)) with an appropriate fallback
ByeBooError case or wrapper so all errors (from sendUserUseCase.execute)
propagate to the UI; ensure you reference sendUserUseCase.execute and
userInformationSubject.send when making the change.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swift (1)

57-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Non-ByeBooError 발생 시 실패 알림이 누락됩니다.

catch(let error as ByeBooError)ByeBooError 타입만 캐치합니다. UseCase에서 다른 타입의 에러가 발생하면 journeyResultSubject에 failure가 전송되지 않아 UI가 에러 상태를 인지하지 못합니다.

🐛 Fallback catch 추가
             } catch(let error as ByeBooError) {
                 journeyResultSubject.send(.failure(error))
+            } catch {
+                journeyResultSubject.send(.failure(.unknownError))
             }
🤖 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/Home/ViewModel/CardJourneyViewModel.swift`
around lines 57 - 66, The current fetchJourney() only catches ByeBooError (catch
let error as ByeBooError) so non-ByeBooError exceptions never send a failure to
journeyResultSubject; add a fallback catch block after the typed catch in
fetchJourney() to handle any other Error from fetchUserJourneyUseCase.execute()
and send a failure to journeyResultSubject (e.g., map to a generic ByeBooError
case or wrap the unknown error) while preserving/logging the original error for
diagnostics; ensure you reference fetchJourney(), journeyResultSubject and
fetchUserJourneyUseCase.execute() when applying the fix.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift (1)

51-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

자동 로그인 시 non-ByeBooError가 처리되지 않습니다.

autoLoginUseCase.execute()ByeBooError가 아닌 에러를 throw하면 autoLoginSubject에 failure가 전송되지 않아 스플래시 화면에서 멈출 수 있습니다.

🐛 Fallback catch 추가
             } catch(let error as ByeBooError) {
                 autoLoginSubject.send(.failure((.noData)))
                 ByeBooLogger.debug(ByeBooError.networkConnect)
                 ByeBooLogger.error(error)
+            } catch {
+                autoLoginSubject.send(.failure(.noData))
+                ByeBooLogger.error(error)
             }
🤖 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/Login/ViewModel/SplashViewModel.swift`
around lines 51 - 69, autoLogin() currently only catches ByeBooError types, so
if autoLoginUseCase.execute() throws any other error the autoLoginSubject never
receives a failure; add a fallback catch after the existing catch(let error as
ByeBooError) to handle all other errors, send
autoLoginSubject.send(.failure(.noData)) (or appropriate failure case), and log
the unexpected error via ByeBooLogger.error with context; keep references to
autoLoginUseCase.execute(), autoLoginSubject and ByeBooLogger when updating the
error handling.
🧹 Nitpick comments (6)
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swift (2)

91-94: 💤 Low value

불필요한 타입 캐스트 제거

error는 이미 ByeBooError 타입으로 바인딩되어 있으므로, error as ByeBooError 캐스트가 불필요합니다.

♻️ 제안 수정
             } catch(let error as ByeBooError) {
-                ByeBooLogger.error(error as ByeBooError)
+                ByeBooLogger.error(error)
                 startResultSubject.send(.failure((error)))
             }
🤖 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/QuestStartViewModel.swift`
around lines 91 - 94, In QuestStartViewModel's catch block for ByeBooError,
remove the redundant type cast "as ByeBooError" because the error is already
bound as ByeBooError; update the ByeBooLogger.error call to pass the error
directly (ByeBooLogger.error(error)) and likewise send the error directly to
startResultSubject.send(.failure(error)) to eliminate the unnecessary casts and
simplify the code.

74-111: ⚡ Quick win

동일 파일 내 에러 처리 패턴 불일치

startJourney()postNewJourneys()catch(let error as ByeBooError) 패턴을 사용하지만, fetchJourney()는 기존 catch { } 패턴에 unknownError 폴백을 사용합니다.

의도적인 차이인지 확인이 필요합니다. 일관성을 위해 동일한 패턴을 적용하는 것이 좋습니다.

🤖 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/QuestStartViewModel.swift`
around lines 74 - 111, The error-handling patterns are inconsistent:
startJourney() and postNewJourneys() use catch(let error as ByeBooError) while
fetchJourney() uses a generic catch with an unknownError fallback; make
fetchJourney() consistent by adding the same ByeBooError-specific catch
(catch(let error as ByeBooError) { journeyResultSubject.send(.failure(error)) })
and optionally keep a trailing generic catch to map non-ByeBooError errors to
ByeBooError.unknownError so its behavior matches startJourney() and
postNewJourneys() (refer to startJourney(), postNewJourneys(), and
fetchJourney()).
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swift (1)

54-57: 💤 Low value

불필요한 타입 캐스트 제거

error는 이미 ByeBooError 타입으로 바인딩되어 있으므로, error as ByeBooError 캐스트가 불필요합니다.

♻️ 제안 수정
             } catch(let error as ByeBooError) {
-                ByeBooLogger.error(error as ByeBooError)
+                ByeBooLogger.error(error)
                 questTipSubject.send(.failure(error))
             }
🤖 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/QuestTipViewModel.swift`
around lines 54 - 57, In the catch block that binds the error as `ByeBooError`
(catch let error as ByeBooError) remove the unnecessary cast when logging and
sending the failure: call `ByeBooLogger.error(error)` instead of
`ByeBooLogger.error(error as ByeBooError)` and continue to call
`questTipSubject.send(.failure(error))`; update the catch binding and the usages
to use the already-typed `error` directly.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swift (1)

50-52: 💤 Low value

일관성을 위해 에러 로깅 추가 권장

다른 ViewModel들(LookBackJourneyViewModel, NewJourneyViewModel 등)은 catch 블록에서 ByeBooLogger.error(error)를 호출하지만, 이 파일에서는 로깅 없이 failure만 전송합니다. 디버깅 편의를 위해 로깅 추가를 권장합니다.

♻️ 제안 수정
             } catch(let error as ByeBooError) {
+                ByeBooLogger.error(error)
                 questsSubject.send(.failure(error))
             }
🤖 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/Offboarding/ViewModel/CompletedQuestsViewModel.swift`
around lines 50 - 52, The catch block in CompletedQuestsViewModel currently
sends failure via questsSubject.send(.failure(error)) but doesn't log the error;
add a call to ByeBooLogger.error(error) inside the catch(let error as
ByeBooError) before or alongside questsSubject.send(.failure(error)) so errors
are consistently logged like in LookBackJourneyViewModel/NewJourneyViewModel;
update the catch in CompletedQuestsViewModel to call ByeBooLogger.error(error)
then send the failure.
ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift (1)

47-47: 💤 Low value

삼항 연산자 단순화 제안

userID == writerId ? true : false는 불리언 비교 결과 자체가 Bool이므로 삼항 연산자 없이 userID == writerId만으로 충분합니다.

♻️ 제안 수정
-            isMyAnswer: userID == writerId ? true : false,
+            isMyAnswer: userID == writerId,
🤖 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/CommonQuestAnswersResponseDTO.swift` at line
47, Replace the redundant ternary expression used to set isMyAnswer in
CommonQuestAnswersResponseDTO: instead of using "userID == writerId ? true :
false", assign the boolean comparison directly as "userID == writerId" so
isMyAnswer uses the comparison result; update the initializer/property
assignment where isMyAnswer is set (refer to isMyAnswer, userID and writerId in
CommonQuestAnswersResponseDTO).
ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift (1)

25-31: ⚡ Quick win

userID 파라미터가 사용되지 않습니다

Line 25의 toEntity(userID: Int) 시그니처에서 userID를 받지만, Line 29의 answers.map { $0.toEntity() } 호출에서는 전달하지 않아 파라미터가 실제로 사용되지 않습니다. "내 답변" 엔드포인트는 소유권이 암묵적이므로 userID가 불필요할 수 있지만, 파라미터를 받기만 하고 사용하지 않으면 혼란을 줄 수 있습니다.

♻️ 제안: 파라미터 제거 또는 활용

Option 1: 파라미터가 불필요하다면 제거

-    func toEntity(userID: Int) -> CommonQuestMyAnswersEntity {
+    func toEntity() -> CommonQuestMyAnswersEntity {

Option 2: 일관성을 위해 하위 매핑에 전달 (향후 확장성 고려)

-            answers: answers.map { $0.toEntity() }
+            answers: answers.map { $0.toEntity(userID: userID) }

그리고 Line 35의 시그니처도 업데이트

-    func toEntity() -> CommonQuestMyAnswerEntity {
+    func toEntity(userID: Int) -> CommonQuestMyAnswerEntity {
🤖 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/CommonQuestMyAnswersResponseDTO.swift`
around lines 25 - 31, The toEntity(userID: Int) method on
CommonQuestMyAnswersResponseDTO accepts userID but doesn't use it; either remove
the userID parameter from CommonQuestMyAnswersResponseDTO.toEntity (and from any
callers) if ownership is implicit, or propagate it into each answer conversion
by changing answers.map { $0.toEntity() } to answers.map { $0.toEntity(userID:
userID) } and updating the child DTO's toEntity signature (and any other
affected signatures, e.g., the answer DTO's toEntity method) to accept and use
userID for consistency and future extensibility.
🤖 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/Repository/CommonQuestRepository.swift`:
- Line 41: Several repositories (CommonQuestRepository and UsersRepository) use
userDefaultsService.load(key: .userID) ?? 0 which silently substitutes 0 and
breaks ownership checks (userID == writerId); update all call sites (e.g.,
CommonQuestRepository where userID is read at declaration and again at line ~69,
and UsersRepository read at ~147) to handle missing userID explicitly: change
read logic to fail fast by returning/throwing an error (or propagating
Result/Optional) when load(key: .userID) is nil, and/or emit a warning log via
your logger before returning; ensure ownership checks use the validated/non-nil
userID and adjust method signatures to propagate the error/optional if needed so
callers can handle unauthenticated state instead of treating 0 as a valid user
ID.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift`:
- Around line 134-136: The typed catch (catch let error as ByeBooError) can let
non-ByeBooError exceptions escape the Task, so add a trailing catch-all block to
each async Task in getQuestInfo, postQuestType, editQuestType, saveCommonQuest
and updateCommonQuest that catches any Error and forwards an appropriate failure
to the corresponding subject (e.g., questInfoResultSubject.send(.failure(...))),
ensuring subscribers always receive an error; also remove the redundant "as
ByeBooError" casts in those catch bindings and use a plain catch or
pattern-match ByeBooError then a final catch to handle all other errors.

---

Outside diff comments:
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swift`:
- Around line 55-63: The current typed catch in NetworkInterceptor's Task only
handles ByeBooError, so AFError thrown by tokenService.reissue() can bypass it
and leave completion uncalled; update the error handling in the Task around
tokenService.reissue() to include a general catch (or an explicit catch for
AFError) that forwards the caught error to completion (e.g.,
completion(.doNotRetryWithError(error))) after logging, ensuring both
ByeBooError and AFError (and any other thrown errors) are handled and completion
is always invoked.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift`:
- Around line 67-98: The typed catch blocks in blockUser(userID:),
reportCommonQuest(answerID:) and deleteCommonQuest(answerID:) only handle
ByeBooError, so non-ByeBooError throws never send a failure and the UI can hang;
update each Task to add a fallback catch (or a final catch) that maps any other
Error to a ByeBooError (e.g. ByeBooError.unknownError or wrap with context) and
send the corresponding subject (.failure(mappedError)) so the ViewController
always receives a result from blockUserSubject, reportQuestSubject and
deleteQuestSubject.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swift`:
- Around line 57-66: The current fetchJourney() only catches ByeBooError (catch
let error as ByeBooError) so non-ByeBooError exceptions never send a failure to
journeyResultSubject; add a fallback catch block after the typed catch in
fetchJourney() to handle any other Error from fetchUserJourneyUseCase.execute()
and send a failure to journeyResultSubject (e.g., map to a generic ByeBooError
case or wrap the unknown error) while preserving/logging the original error for
diagnostics; ensure you reference fetchJourney(), journeyResultSubject and
fetchUserJourneyUseCase.execute() when applying the fix.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift`:
- Around line 88-123: fetchDialogue() and fetchJourney() currently use typed
catches that ignore non-ByeBooError errors; update their error handling to match
fetchStatus() by using an untyped catch and then conditionally casting the
caught error to ByeBooError before sending failure to characterResultSubject /
journeyResultSubject, and provide a sensible fallback action (e.g., send a
default state or call isHelperShown(...)) for non-ByeBooError cases so all
errors are handled consistently with fetchStatus().

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swift`:
- Around line 54-65: The catch currently only handles ByeBooError in the Task
inside InformationViewModel (around sendUserUseCase.execute), so non-ByeBooError
throws never notify the UI; update the Task to add a fallback catch that
captures any Error (in addition to the existing catch(let error as ByeBooError))
and call userInformationSubject.send(.failure(...)) with an appropriate fallback
ByeBooError case or wrapper so all errors (from sendUserUseCase.execute)
propagate to the UI; ensure you reference sendUserUseCase.execute and
userInformationSubject.send when making the change.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift`:
- Around line 62-74: The current socialLogin(platform:) Task only catches
ByeBooError so non-ByeBooError throws are dropped; add a fallback catch block
after the existing catch(let error as ByeBooError) to handle all other errors
from socialLoginUseCase.execute(platform:), log them via ByeBooLogger.error
(including the error) and send a failure on socialLoginAuthSubject
(wrapping/propagating as an appropriate ByeBooError or a generic failure case)
so every thrown error is logged and the subject is notified.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift`:
- Around line 51-69: autoLogin() currently only catches ByeBooError types, so if
autoLoginUseCase.execute() throws any other error the autoLoginSubject never
receives a failure; add a fallback catch after the existing catch(let error as
ByeBooError) to handle all other errors, send
autoLoginSubject.send(.failure(.noData)) (or appropriate failure case), and log
the unexpected error via ByeBooLogger.error with context; keep references to
autoLoginUseCase.execute(), autoLoginSubject and ByeBooLogger when updating the
error handling.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swift`:
- Around line 66-93: Both fetchBlocedUserList and deleteBlockedUser only catch
ByeBooError, so non-ByeBooError exceptions are ignored; add a fallback catch
block in each Task after the existing catch(let error as ByeBooError) to handle
any other Error, log it with ByeBooLogger.error, and send a failure to the
corresponding subject (getBlockedUsersListSubject for fetchBlocedUserList and
deleteBlockUserSubject for deleteBlockedUser), converting or wrapping the
generic Error into an appropriate ByeBooError case if needed before sending.

---

Nitpick comments:
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift`:
- Line 47: Replace the redundant ternary expression used to set isMyAnswer in
CommonQuestAnswersResponseDTO: instead of using "userID == writerId ? true :
false", assign the boolean comparison directly as "userID == writerId" so
isMyAnswer uses the comparison result; update the initializer/property
assignment where isMyAnswer is set (refer to isMyAnswer, userID and writerId in
CommonQuestAnswersResponseDTO).

In `@ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift`:
- Around line 25-31: The toEntity(userID: Int) method on
CommonQuestMyAnswersResponseDTO accepts userID but doesn't use it; either remove
the userID parameter from CommonQuestMyAnswersResponseDTO.toEntity (and from any
callers) if ownership is implicit, or propagate it into each answer conversion
by changing answers.map { $0.toEntity() } to answers.map { $0.toEntity(userID:
userID) } and updating the child DTO's toEntity signature (and any other
affected signatures, e.g., the answer DTO's toEntity method) to accept and use
userID for consistency and future extensibility.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swift`:
- Around line 50-52: The catch block in CompletedQuestsViewModel currently sends
failure via questsSubject.send(.failure(error)) but doesn't log the error; add a
call to ByeBooLogger.error(error) inside the catch(let error as ByeBooError)
before or alongside questsSubject.send(.failure(error)) so errors are
consistently logged like in LookBackJourneyViewModel/NewJourneyViewModel; update
the catch in CompletedQuestsViewModel to call ByeBooLogger.error(error) then
send the failure.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swift`:
- Around line 91-94: In QuestStartViewModel's catch block for ByeBooError,
remove the redundant type cast "as ByeBooError" because the error is already
bound as ByeBooError; update the ByeBooLogger.error call to pass the error
directly (ByeBooLogger.error(error)) and likewise send the error directly to
startResultSubject.send(.failure(error)) to eliminate the unnecessary casts and
simplify the code.
- Around line 74-111: The error-handling patterns are inconsistent:
startJourney() and postNewJourneys() use catch(let error as ByeBooError) while
fetchJourney() uses a generic catch with an unknownError fallback; make
fetchJourney() consistent by adding the same ByeBooError-specific catch
(catch(let error as ByeBooError) { journeyResultSubject.send(.failure(error)) })
and optionally keep a trailing generic catch to map non-ByeBooError errors to
ByeBooError.unknownError so its behavior matches startJourney() and
postNewJourneys() (refer to startJourney(), postNewJourneys(), and
fetchJourney()).

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swift`:
- Around line 54-57: In the catch block that binds the error as `ByeBooError`
(catch let error as ByeBooError) remove the unnecessary cast when logging and
sending the failure: call `ByeBooLogger.error(error)` instead of
`ByeBooLogger.error(error as ByeBooError)` and continue to call
`questTipSubject.send(.failure(error))`; update the catch binding and the usages
to use the already-typed `error` directly.
🪄 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: 6712bf60-bee9-4e8c-b6d2-28b7cd2390ea

📥 Commits

Reviewing files that changed from the base of the PR and between b998d5e and 33d19a8.

📒 Files selected for processing (28)
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/LookBackJourneyViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/MyPageViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/NewJourneyViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ArchiveQuestViewModel.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/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift
🚧 Files skipped from review as they are similar to previous changes (3)
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift

cursor: Int?
) async throws -> CommonQuestAnswersEntity {
let userName: String = userDefaultsService.load(key: .userName) ?? ""
let userID: Int = userDefaultsService.load(key: .userID) ?? 0

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

여러 저장소에서 userID 폴백 0 패턴이 반복됩니다

CommonQuestRepository(Lines 41, 69)와 UsersRepository(Line 147) 모두 userDefaultsService.load(key: .userID) ?? 0 폴백을 사용합니다. userID가 없을 때 0으로 대체하면 모든 소유권 판별(userID == writerId)이 false가 되어, 사용자가 자신의 컨텐츠를 수정/삭제할 수 없게 됩니다.

공통 해결 방안: userID 누락 시 명시적 에러 처리 또는 최소한 경고 로그를 남기도록 모든 위치에서 일관되게 수정하세요.

🤖 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/Repository/CommonQuestRepository.swift` at line
41, Several repositories (CommonQuestRepository and UsersRepository) use
userDefaultsService.load(key: .userID) ?? 0 which silently substitutes 0 and
breaks ownership checks (userID == writerId); update all call sites (e.g.,
CommonQuestRepository where userID is read at declaration and again at line ~69,
and UsersRepository read at ~147) to handle missing userID explicitly: change
read logic to fail fast by returning/throwing an error (or propagating
Result/Optional) when load(key: .userID) is nil, and/or emit a warning log via
your logger before returning; ensure ownership checks use the validated/non-nil
userID and adjust method signatures to propagate the error/optional if needed so
callers can handle unauthenticated state instead of treating 0 as a valid user
ID.

Comment on lines +134 to 136
} catch(let error as ByeBooError) {
questInfoResultSubject.send(.failure(error))
}

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

타입 지정 catch로 인한 예외 누락 위험

catch(let error as ByeBooError) 패턴은 Swift의 패턴 매칭 catch로, ByeBooError가 아닌 예외가 발생하면 이 catch 블록이 실행되지 않고 Task 외부로 전파됩니다. 현재 Task { ... } 블록에는 상위 catch-all 핸들러가 없으므로:

  • UseCase에서 시스템 오류, 라이브러리 디코딩 실패 등 ByeBooError가 아닌 예외가 발생하면 catch되지 않음
  • Subject가 .failure(...)를 받지 못해 구독자(ViewController)가 오류 피드백을 표시하지 못함
  • 사용자는 응답 없는 상태를 경험하게 됨
🛡️ 권장 수정: 각 catch 블록 뒤에 catch-all 추가

예시 (getQuestInfo):

 } catch(let error as ByeBooError) {
     questInfoResultSubject.send(.failure(error))
+} catch {
+    questInfoResultSubject.send(.failure(.unknownError))
+    ByeBooLogger.error("Unexpected error: \(error)")
 }

동일한 패턴을 postQuestType, editQuestType, saveCommonQuest, updateCommonQuest에도 적용하세요.

추가 정리: 146, 158, 170, 192번 줄의 error as ByeBooError 캐스팅은 불필요합니다. error는 이미 catch 절에서 ByeBooError 타입으로 바인딩되었습니다.

-didSuccessPostSubject.send(.failure(error as ByeBooError))
+didSuccessPostSubject.send(.failure(error))

Also applies to: 145-148, 157-160, 169-172, 191-194

🤖 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/WriteQuestTypeViewModel.swift`
around lines 134 - 136, The typed catch (catch let error as ByeBooError) can let
non-ByeBooError exceptions escape the Task, so add a trailing catch-all block to
each async Task in getQuestInfo, postQuestType, editQuestType, saveCommonQuest
and updateCommonQuest that catches any Error and forwards an appropriate failure
to the corresponding subject (e.g., questInfoResultSubject.send(.failure(...))),
ensuring subscribers always receive an error; also remove the redundant "as
ByeBooError" casts in those catch bindings and use a plain catch or
pattern-match ByeBooError then a final catch to handle all other errors.

@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.

확인 완-!

import UIKit

extension UIViewController {
func presentBottomSheet(_ viewController: UIViewController, height: CGFloat) {

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.

좋네요 굿굿!

@y-eonee y-eonee merged commit 8847d2e into develop Jun 10, 2026
2 checks passed
@y-eonee y-eonee deleted the feat/#444-댓글api branch June 10, 2026 14:13
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