Feat/#444 공통퀘스트 관련 조회 API들 연결#452
Hidden character warning
Conversation
기존 데이터를 넘겨받는 방식이 아닌 서버에서 데이터를 받아옴
Walkthrough상세 조회용 DTO/엔티티 계약을 갱신하고, fetchCommonQuestDetail 엔드포인트·유스케이스·저장소 호출을 연결합니다. History 화면은 ViewModel 주입형 + Combine로 리팩토링되어 상세 응답을 바인딩하고, 댓글 메뉴·하단시트 및 상대시간 포맷을 통합합니다. Changes공통 퀘스트 상세 조회 및 데이터 바인딩
댓글 메뉴 및 하단시트 통합
목록 바인딩·내비게이션·유틸
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60분 Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (32)
ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swiftByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swiftByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/UsersAPI.swiftByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swiftByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swiftByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestDetailEntity.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swiftByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UIViewController+.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift
| func toEntity(userName: String) -> CommonQuestDetailEntity { | ||
| .init( | ||
| question: question, | ||
| answer: answer.toEntity(userName: userName), | ||
| comments: comments.map { $0.toEntity(userName: userName) } | ||
| ) |
There was a problem hiding this comment.
소유권 판별을 닉네임 기준으로 두면 오분류가 발생합니다.
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
| func menuButtonDidTap(commentID: Int, isMyComment: Bool) { | ||
| let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() | ||
|
|
||
| commonQuestBottomSheet.configure( | ||
| sheeetType: isMyComment ? .myComment : .otherComment , | ||
| targetID: commentID | ||
| ) |
There was a problem hiding this comment.
댓글 메뉴에서 commentID를 targetID로 전달하면 차단/신고 대상 식별이 깨집니다.
현재 흐름은 사용자 대상 액션(차단)에도 댓글 ID를 전달합니다. 댓글 메뉴는 최소 writerID와 commentID를 분리 전달하도록 호출 계약을 분리하는 게 안전합니다.
🤖 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.
| } catch { | ||
| guard let error = error as? ByeBooError else { | ||
| return | ||
| } | ||
| fetchCommentListSubject.send(.failure(error)) |
There was a problem hiding this comment.
알 수 없는 에러를 무시하지 말고 실패 이벤트를 항상 방출하세요.
현재는 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.
There was a problem hiding this comment.
@y-eonee 이 부분 catch let error as ByeBooError로 싹 리팩토링 해보는 것도 좋은 방법 인 것 같아요
There was a problem hiding this comment.
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!
| guard let viewModel = DIContainer.shared.resolve(type: CommonQuestHistoryViewModel.self) else { | ||
| DIErrorHandle() | ||
| fatalError() | ||
| } | ||
| return CommonQuestHistoryViewController(viewModel: viewModel) |
There was a problem hiding this comment.
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.
| guard index >= 0 && index < answers.count else { | ||
| return nil | ||
| } | ||
| return answers[index].answerID |
| } catch { | ||
| guard let error = error as? ByeBooError else { | ||
| return | ||
| } | ||
| fetchCommentListSubject.send(.failure(error)) |
There was a problem hiding this comment.
@y-eonee 이 부분 catch let error as ByeBooError로 싹 리팩토링 해보는 것도 좋은 방법 인 것 같아요
|
|
||
| configureDataSource() | ||
| applySnapshot() | ||
| // applySnapshot() |
| 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 |
There was a problem hiding this comment.
요거 entity 하나로 묶어서 해도 되나요 ??
아니면 그냥 viewomodel.entity로 접근하게 하면 안되는 이유가 있을까요 ?!
There was a problem hiding this comment.
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 winTyped 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 winTyped 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
fetchDialogue와fetchJourney의 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
fetchBlocedUserList와deleteBlockedUser에서 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 winNon-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 winNon-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
📒 Files selected for processing (28)
ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swiftByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swiftByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swiftByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swiftByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/LookBackJourneyViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/MyPageViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/NewJourneyViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ArchiveQuestViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swiftByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swiftByeBoo-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 |
There was a problem hiding this comment.
여러 저장소에서 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.
| } catch(let error as ByeBooError) { | ||
| questInfoResultSubject.send(.failure(error)) | ||
| } |
There was a problem hiding this comment.
타입 지정 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.
| import UIKit | ||
|
|
||
| extension UIViewController { | ||
| func presentBottomSheet(_ viewController: UIViewController, height: CGFloat) { |
🔗 연결된 이슈
📄 작업 내용
보시면 answer 관련 부분이 공통된 부분이 많은데.. 하나의 entity로 만들고 싶었지만 결정적으로 answerID가 없어서 뭔가 재사용하기에 애매해지는 느낌이 있었어요...! 그래서 일단 전체조회에서 사용하는 것은 CommonQuestAnswerEntity, 상세조회에서 사용하는 것은 CommonQuestDetailEntity로 분리해두었는데 혹시 이 부분에 대해 좋은 의견이 있다면 부탁드립니닷..
*댓글 api는 아직 연결 전이라 스웨거에서 임의로 달았습니다!
💻 주요 코드 설명
CommonQuestHistoryVCconfigure 함수가 간소화되었습니다.
isMyAnswer, isMyComment
내 답변, 내 댓글인지 구분하기 위하여 관련 entity에 해당 값을 추가했습니다.
UIViewController+
이렇게 사용할 수 있습니다!
Summary by CodeRabbit
새 기능
개선 사항