Skip to content

Comments

Hotfix/#132 챌린지 api 연동#133

Merged
sum130 merged 3 commits intodevelopfrom
hotfix/#132-chellengeAPIFix
Jan 21, 2026
Merged

Hotfix/#132 챌린지 api 연동#133
sum130 merged 3 commits intodevelopfrom
hotfix/#132-chellengeAPIFix

Conversation

@sum130
Copy link
Contributor

@sum130 sum130 commented Jan 21, 2026

🔗 연결된 이슈

📄 작업 내용

  • api 연결했습니다!
  • 홈케어 루틴 목록 조회
  • AI 챌린지 추천
  • 챌린지 생성
구현 내용

💻 주요 코드 설명

  • 챌린지생성뷰 = 챌린지루틴선택 + 로딩 + 챌린지미션선택 으로 합쳤습니다! 그래서 뮤보델도 하나로 되었고!!
    CreateChallengeViewModel
enum challengeViewState: StepNavigatable {
    case routine
    case loding
    case mission
    
    var title: String {
        switch self {
        case .routine:
            return "루틴 챌린지 선택"
        case .loding:
            return ""
        case .mission:
            return "TO-DO 미션 선택"
        }
    }
  • 홈케어 루틴 목록 조회
  • AI 챌린지 추천
  • 챌린지 생성
@MainActor
    func fetchRoutines() async throws {
        routines = try await fetchRoutineUseCase.excute()
        CherrishLogger.debug("루틴 \(routines)")
    }


    @MainActor
    func postChallengeRecommend() async throws {
        guard let selectedRoutine else { return  }
        isLoading = true
        missions = try await postChallengeRecommendUseCase.excute(id: selectedRoutine.id)
        CherrishLogger.debug("뷰모델 미션 \(missions)")
    }

    @MainActor
    func makeChallenge() async throws {
        guard let selectedRoutine else { return }
        
        let selectedMissionList = missonsSelectedState
            .filter(\.value)
            .map(\.key.title)
        CherrishLogger.debug("선택한 미션들: \(selectedMissionList)")

        try await createChallengeUseCase.execute(id: selectedRoutine.id, routines: selectedMissionList)
    }

그리고!!
PresentationDependencyAssembler.swift

  • DI 컨테이너에 등록해주고 사용했습니당
 guard let fetchChallengeHomecareRoutines = DIContainer.shared.resolve(type: FetchChllengeHomecareRoutinesUseCase.self) else {
            return
        }
        guard let postChallengeRecommendUseCase = DIContainer.shared.resolve(type: PostChallengeRecommendUseCase.self) else {
            return
        }
        
        guard let createChallengeUseCase = DIContainer.shared.resolve(type: CreateChallengeUseCase.self) else {
            return
        }

        DIContainer.shared.register(type: CreateChallengeViewModel.self) {
            return CreateChallengeViewModel(fetchRoutineUseCase: fetchChallengeHomecareRoutines, postChallengeRecommendUseCase:  postChallengeRecommendUseCase, createChallengeUseCase: createChallengeUseCase)
        }

📚 참고자료

먼저 연결하신 api들 많이 참고했슴미다..

👀 기타 더 이야기해볼 점

어렵네요.. 엉엉슨ㅠ
images

도와주신분들 넘넘 감사합니다ㅠㅠㅠ 열심히 공부할게용..
이슈 만든것도..........ㅠㅠㅠㅠ 죄송하고 감사합니다...ㅠ

@sum130 sum130 linked an issue Jan 21, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

챌린지 기능을 완성하기 위해 API 엔드포인트, 데이터 전송 객체, 저장소 구현, 도메인 유스 케이스, 뷰 모델, UI 뷰를 추가하고 기존 뷰들을 재구성했습니다.

Changes

Cohort / File(s) 변경 요약
데이터 계층 - API 정의
Data/Network/EndPoint/ChallengeAPI.swift, Data/Network/EndPoint/ChallengeDemoAPI.swift
챌린지 관련 API 엔드포인트 두 개 추가: ChallengeAPI는 루틴 조회 및 AI 추천 기능 제공, ChallengeDemoAPI는 챌린지 생성 기능 제공
데이터 계층 - 데이터 전송 객체
Data/Model/ChallengeRoutineRequestDTO.swift, Data/Model/RecommendMissionsRequestDTO.swift, Data/Model/RecommendMissionsResponseDTO.swift, Data/Model/MakeChallengeRequestDTO.swift
챌린지 관련 DTO 네 개 추가: 루틴 요청/응답, 미션 추천 요청/응답, 챌린지 생성 요청을 위한 데이터 모델
데이터 계층 - 유틸리티 및 저장소
Data/Model/Extension/Encodable+.swift, Data/Repository/ChallengeRepository.swift, Data/DataDependencyAssembler.swift
Encodable 확장 메서드 추가, DefaultChallengeRepository 구현, DI 컨테이너에 챌린지 저장소 등록
도메인 계층 - 인터페이스 및 엔티티
Domain/Interface/ChallengeInterface.swift, Domain/Model/RoutineEntity.swift, Domain/Model/ChallengeMissionEntity.swift, Domain/Model/ChallengeEntity.swift
챌린지 리포지토리 프로토콜 및 도메인 엔티티 세 개 추가
도메인 계층 - 유스 케이스
Domain/UseCase/FetchChllengeHomecareRoutinesUseCase.swift, Domain/UseCase/PostChallengeRecommendUseCase.swift, Domain/UseCase/CreateChallengeUseCase.swift, Domain/DomainDependencyAssembler.swift
챌린지 관련 유스 케이스 세 개 추가, DI 컨테이너 등록
프레젠테이션 계층 - 새 뷰 및 뷰 모델
Presentation/Feature/ChallengeView/View/ChallengeSelectRoutineView.swift, Presentation/Feature/ChallengeView/View/ChallengeSelectMissionView.swift, Presentation/Feature/ChallengeView/View/ChallengeLoadingView.swift, Presentation/Feature/ChallengeView/View/CreateChallengeView.swift, Presentation/Feature/ChallengeView/ViewModel/CreateChallengeViewModel.swift
챌린지 생성 플로우 위한 새로운 뷰 네 개 및 뷰 모델 추가
프레젠테이션 계층 - 좌표 변경 및 팩토리
Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinator.swift, Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift, Presentation/Feature/ChallengeView/StartChallengeView.swift, Presentation/ViewFactory.swift, Presentation/PresentationDependencyAssembler.swift
라우트 변경 (selectRoutine, loading, selectMission 제거 → createChallenge 추가), 뷰 팩토리 메서드 업데이트, 뷰 모델 이름 변경 (StartChallengeView → ChallengeStartChallengeView), DI 등록
프레젠테이션 계층 - 삭제 및 마이너 수정
Presentation/Feature/ChallengeView/ChallengeView.swift, Presentation/Feature/ChallengeView/SelectRoutineViewModel.swift, Presentation/Feature/ChallengeView/SelectMissionView.swift, Presentation/Feature/ChallengeView/SelectRoutineView.swift, Presentation/Feature/ChallengeView/LoadingView.swift, Presentation/Feature/ChallengeView/ChallengeProgressView.swift
기존 뷰/뷰 모델 다섯 개 제거, ChallengeProgressView 패딩 조정

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant ChallengeView
    participant CreateChallengeViewModel
    participant FetchRoutinesUseCase
    participant Repository as ChallengeRepository
    participant API as NetworkService
    participant Server

    User->>ChallengeView: 챌린지 시작
    ChallengeView->>CreateChallengeViewModel: fetchRoutines()
    CreateChallengeViewModel->>FetchRoutinesUseCase: excute()
    FetchRoutinesUseCase->>Repository: fetchHomecareRoutines()
    Repository->>API: request(ChallengeAPI.fetchRoutines)
    API->>Server: GET /api/challenges/homecare-routines
    Server-->>API: [ChallengeRoutineRequestDTO]
    API-->>Repository: [ChallengeRoutineRequestDTO]
    Repository-->>FetchRoutinesUseCase: [RoutineEntity]
    FetchRoutinesUseCase-->>CreateChallengeViewModel: [RoutineEntity]
    CreateChallengeViewModel-->>ChallengeView: routines updated

    User->>ChallengeView: 루틴 선택
    ChallengeView->>CreateChallengeViewModel: selectRoutine(id:)
    CreateChallengeViewModel->>CreateChallengeViewModel: postChallengeRecommend()
    CreateChallengeViewModel->>Repository: aiRecommendations(id:)
    Repository->>API: request(ChallengeAPI.aiRecommendations)
    API->>Server: POST /api/challenges/ai-recommendations
    Server-->>API: RecommendMissionsResponseDTO
    API-->>Repository: [ChallengeMissionEntity]
    Repository-->>CreateChallengeViewModel: [ChallengeMissionEntity]
    CreateChallengeViewModel-->>ChallengeView: missions updated, loading state

    User->>ChallengeView: 미션 선택 후 추가
    ChallengeView->>CreateChallengeViewModel: makeChallenge()
    CreateChallengeViewModel->>Repository: createChallenge(missionIds:, routineNames:)
    Repository->>API: request(ChallengeDemoAPI.createChallenge)
    API->>Server: POST /api/demo/challenges
    Server-->>API: success
    API-->>Repository: Void
    Repository-->>CreateChallengeViewModel: Void
    CreateChallengeViewModel-->>ChallengeView: navigation to challengeProgress
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • soseoyo12
  • wotjs020708
  • y-eonee
🚥 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
Title check ✅ Passed PR 제목은 연결된 이슈 #132와 직접 관련이 있으며, 챌린지 API 연동이라는 주요 변경사항을 명확하게 요약합니다.
Description check ✅ Passed PR 설명은 API 연동, 홈케어 루틴 조회, AI 챌린지 추천, 챌린지 생성 구현에 대해 상세히 설명하며 변경사항과 관련이 있습니다.
Linked Issues check ✅ Passed PR의 모든 변경사항이 이슈 #132의 목표인 챌린지 API 복구를 달성합니다. 홈케어 루틴 조회, AI 추천, 챌린지 생성 기능이 완전히 구현되었습니다.
Out of Scope Changes check ✅ Passed ChallengeProgressView의 패딩 감소, StartChallengeView 이름 변경, 기존 뷰 삭제 등의 변경사항이 포함되어 있으나, 모두 챌린지 기능 리팩토링과 관련된 범위 내 변경입니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hotfix/#132-chellengeAPIFix

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 changed the title hotfix: #132 챌린지 api 연동 Hotfix/#132 챌린지 api 연동 Jan 21, 2026
@y-eonee y-eonee requested review from a team, soseoyo12, wotjs020708 and y-eonee and removed request for a team January 21, 2026 11:18
@soseoyo12 soseoyo12 added 수민🐶 Hotfix 충돌해결 등 devlop에 바로 반영해야하는 경우 사용 labels Jan 21, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 24

Caution

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

⚠️ Outside diff range comments (3)
Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift (2)

40-42: 중복된 homeRepository 선언

homeRepository가 Line 24-26에서 이미 선언되어 있는데, Line 40-42에서 다시 guard로 선언하고 있습니다. 이 중복 코드를 제거해주세요.

-        guard let homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else {
-            return
-        }
-        
         DIContainer.shared.register(type: FetchProcedureDowntimeUseCase.self) {

28-30: 사용되지 않는 treatmentCategoryRepository 변수

Line 28-30에서 treatmentCategoryRepository를 resolve하지만 실제로 사용되지 않습니다. Line 60-62에서 treatmentRepository가 별도로 resolve되어 사용됩니다. 불필요한 코드를 제거하거나 의도를 명확히 해주세요.

-        guard let treatmentCategoryRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) else {
-            return
-        }
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift (1)

23-31: 탭바 숨김 복구 경로 보장 필요
.createChallenge에서 탭바를 숨기지만, 스와이프 백/경로 초기화 등으로 뷰가 사라질 때 복구가 보장되지 않으면 탭바가 계속 숨겨질 수 있습니다. onDisappear에서 복구하거나 .root에서 항상 표시로 되돌리는 방어 처리를 권장합니다.

🛠️ 수정 제안
 case .createChallenge:
     ViewFactory.shared.makeCreateChallengeView()
         .onAppear {
             tabBarCoordinator.isTabbarHidden = true
         }
+        .onDisappear {
+            tabBarCoordinator.isTabbarHidden = false
+        }
🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift`:
- Around line 44-47: Rename the concrete repository class ChallengeRepository to
DefaultChallengeRepository and update all references (including its type in DI
registration DIContainer.shared.register(type: ChallengeInterface.self) where
the factory returns DefaultChallengeRepository) so the constructor signature and
injected dependencies (networkService, userDefaultService) remain unchanged;
also rename the source file to match the new class name and update any
imports/usages across the codebase that reference ChallengeRepository to avoid
build errors.

In `@Cherrish-iOS/Cherrish-iOS/Data/Model/RecommendMissionsRequestDTO.swift`:
- Around line 10-12: The DTO RecommendMissionsRequestDTO currently conforms to
Decodable but is used as a request body and must be serializable; change its
protocol conformance from Decodable to Encodable (or Codable if you need both
directions) so it can be encoded when sending the request, update any related
uses that call JSONEncoder.encode(...) to accept this type, and run a quick
build to ensure no other callsites expect Decodable-only behavior.

In `@Cherrish-iOS/Cherrish-iOS/Data/Model/RecommendMisssionsResponseDTO.swift`:
- Around line 2-12: The DTO name and filename contain typos: rename the struct
RecommendMisssionsResponseDTO to RecommendMissionsResponseDTO and update the
source file and header comment accordingly (change
"RecommentMisssionsResponseDTO.swift" or current header to
"RecommendMissionsResponseDTO.swift"); then update all references/usages across
the codebase (decoding calls, tests, imports, and any Codable mapping) to the
new symbol to keep naming consistent and searchable.

In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/ChallengeAPI.swift`:
- Around line 48-55: The parameterEncoding computed property currently returns
JSONEncoding.default for both .fetchRoutines and .aiRecommendations; change it
so .fetchRoutines uses URLEncoding.default (or the server‑specified encoding)
while .aiRecommendations keeps JSONEncoding.default to avoid sending unnecessary
JSON Content-Type on GET requests; update the switch in the parameterEncoding
var (cases .fetchRoutines and .aiRecommendations) accordingly and verify with
the server contract.

In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/ChallengeDemoAPI.swift`:
- Around line 54-59: The current bodyParameters computed property uses `try?
dto.toDictionary()` which swallows encoding errors and returns nil; change it to
explicitly handle encoding failures for the `.createChallenge` case by using a
`do`/`catch` around `dto.toDictionary()` and log the caught error (or assert /
capture to your telemetry) so you don't silently send requests without a body;
alternatively, if the EndPoint protocol can be changed, make `bodyParameters`
throwing and propagate the error instead of returning nil so callers can handle
it. Ensure you update the `.createChallenge(_, let dto)` branch to reference
`dto.toDictionary()` inside the new error-handling path.

In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/ChallengeRepository.swift`:
- Around line 34-44: The createChallenge function declares an unused response
variable and silently falls back to userID == 1; remove the unused local (or
replace with _ =) so the compiler warning is gone, and change the userID
retrieval via userDefaultService.load(key: .userID) to fail explicitly when nil
(e.g., throw an appropriate error) instead of defaulting to 1 so that
createChallenge detects missing authentication; update references in
createChallenge and any calling code to handle the thrown error accordingly.

In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift`:
- Around line 84-86: The DI registration uses a misspelled use-case type
FetchChllengeHomecareRoutinesUseCase; rename it to
FetchChallengeHomecareRoutinesUseCase everywhere (the type declaration and all
references) and update the DIContainer registration call that currently
references FetchChllengeHomecareRoutinesUseCase to use the corrected
FetchChallengeHomecareRoutinesUseCase, ensuring the returned instance still
constructs DefaultFetchChallengeHomecareRoutinesUseCase(repository:
challengeRepository) so all symbols match.

In `@Cherrish-iOS/Cherrish-iOS/Domain/Interface/ChallengeInterface.swift`:
- Around line 10-14: Rename the misleading parameter in the
ChallengeInterface.createChallenge signature: change the parameter currently
named missionIds: Int to a name that matches its actual meaning (e.g.,
homecareRoutineId: Int or missionId: Int) so the parameter name aligns with its
single-Int type and the repository implementation; update the method declaration
in ChallengeInterface (createChallenge) and adjust all implementations/call
sites to use the new parameter name to avoid mismatches.

In
`@Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchChllengeHomecareRoutinesUseCase.swift`:
- Line 2: Rename the misspelled file and symbols: change the filename
FetchChllengeHomecareRoutinesUseCase.swift to
FetchChallengeHomecareRoutinesUseCase.swift, rename the protocol
FetchChllengeHomecareRoutinesUseCase to FetchChallengeHomecareRoutinesUseCase,
and rename the method excute() to execute(); update all references and imports
across the project (including PostChallengeRecommendUseCase.swift and
CreateChallengeViewModel.swift) so protocol names, method calls, and file
imports match the corrected identifiers to keep consistency and build integrity.
- Around line 14-24: The struct name
DefaultFetchChallengeHomecareRoutinesUseCase exceeds SwiftLint length; rename
DefaultFetchChallengeHomecareRoutinesUseCase to a shorter name (e.g.,
HomecareRoutinesUseCase or FetchRoutinesUseCase) and update all references and
initializers to match, ensuring it still conforms to
FetchChllengeHomecareRoutinesUseCase and keeps the repository:
ChallengeInterface property and excute() async throws -> [RoutineEntity]
implementation intact; search/replace usages where the type is instantiated or
referenced so the compiler and lint pass succeed.

In
`@Cherrish-iOS/Cherrish-iOS/Domain/UseCase/PostChallengeRecommendUseCase.swift`:
- Around line 10-24: The protocol and struct contain typos that should be
renamed: change the method name excute to execute and fix the struct/type name
DefaultSubmitChallengRecommendUseCase (and any other 'Challeng' occurrences) to
use 'Challenge' consistently; update the protocol PostChallengeRecommendUseCase,
its implementation DefaultSubmitChallengeRecommendUseCase, and the function
signature func execute(id: Int) async throws -> [ChallengeMissionEntity], then
update all call sites and conforming types to use the new names to keep the API
consistent and compilable.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinator.swift`:
- Around line 21-22: Remove the unused property makeChallengeViewModel from the
ChallengeCoordinator class: locate the declaration var makeChallengeViewModel:
CreateChallengeViewModel? and delete it, ensuring no other references remain;
rely on the existing ViewFactory and DIContainer usage for creating
CreateChallengeViewModel instances instead of adding or initializing this
property.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/StartChallengeView.swift`:
- Line 10: The struct name ChallengeStartChallengeView redundantly repeats
"Challenge"; rename the declaration to a clearer identifier such as
ChallengeStartView or StartChallengeView (update the struct name
ChallengeStartChallengeView -> ChallengeStartView), then update all
references/usages (initializers, SwiftUI previews, any view builders or imports)
to the new name to ensure compilation; also rename the file/class symbol if you
keep file names aligned to avoid confusion.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeLoadingView.swift`:
- Around line 1-6: The file header comment at the top currently says
"LoadingView.swift" but the actual file/class is ChallengeLoadingView; update
the header comment to match the real filename by replacing "LoadingView.swift"
with "ChallengeLoadingView.swift" (keep other header lines like author and date
intact) so the header and file name are consistent.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeSelectMissionView.swift`:
- Around line 1-6: 파일 헤더 주석의 파일명이 실제 struct 이름과 불일치합니다; 현재 주석에는
"SelectMissionView.swift"로 되어 있으나 실제 타입은 ChallengeSelectMissionView입니다. 파일 상단
주석의 파일명(또는 struct 이름 중 하나)을 일관되게 수정하여 "ChallengeSelectMissionView.swift"로 맞추거나
struct 이름을 주석에 반영되도록 변경하세요; 참조 대상: struct ChallengeSelectMissionView 및 파일 헤더
코멘트.
- Around line 67-75: The catch block in ChallengeSelectMissionView currently
only logs errors (CherrishLogger.error(error)) when viewModel.makeChallenge()
fails, leaving the user without feedback; update the ViewModel (e.g., add a
`@Published` var challengeError: Error? or alertMessage: String?) and set that
state in the catch instead of only logging, then update
ChallengeSelectMissionView to observe that state and present an Alert or Toast
(using the .alert modifier) with a user-friendly message and optional retry
action; keep CherrishLogger.error(error) for diagnostics but ensure the
ViewModel error state is the source for UI presentation so the user sees
feedback when makeChallenge() fails.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeSelectRoutineView.swift`:
- Around line 34-39: The tap handler is duplicating selection state changes
(setting viewModel.selectedRoutine and viewModel.nextButtonState) which risks
inconsistency; replace the inline state mutations in the onTapGesture attached
to routineChip(routine) (and the other similar occurrence) with a single call to
viewModel.selectRoutine(id:) so all selection and next-button logic lives only
inside selectRoutine(id:), removing the direct assignments to
viewModel.selectedRoutine and viewModel.nextButtonState from the view.
- Around line 59-72: The code advances the view state and sets isLoading but
never restores state on failure, which can trap the user in the loading view; in
the catch block ensure you set viewModel.isLoading = false and roll back the
view state (e.g., call the inverse navigation method like viewModel.previous()
or explicitly reset viewModel.viewState to the prior value) and provide a retry
path (e.g., expose a retry action that calls viewModel.postChallengeRecommend()
again) so failures restore UI and allow retry; update the catch in the Task that
wraps viewModel.postChallengeRecommend() accordingly.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/CreateChallengeView.swift`:
- Around line 10-14: The CreateChallengeView currently declares the injected
viewModel as `@StateObject` which can cause ownership issues; change it to
`@ObservedObject` if the viewModel is created/owned externally, or provide an
explicit initializer that sets the StateObject via StateObject(wrappedValue:) to
establish ownership (e.g., add init(viewModel: CreateChallengeViewModel) {
_viewModel = StateObject(wrappedValue: viewModel) }) so the
CreateChallengeViewModel instance is handled deterministically; update
references to viewModel accordingly.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ViewModel/CreateChallengeViewModel.swift`:
- Line 51: Rename the published property missonsSelectedState to
missionsSelectedState and update all usages and references accordingly
(including bindings, observers, initializations, and any tests) so the compiler
and UI bindings continue to work; ensure the `@Published` declaration in
CreateChallengeViewModel and any places referencing missonsSelectedState (e.g.,
property observers, view bindings, functions) are updated to the new identifier
missionsSelectedState to keep behavior identical.
- Around line 99-102: selectMission currently calls
missonsSelectedState[mission]?.toggle() which does nothing if the mission key is
missing; initialize missonsSelectedState whenever missions are loaded (or when
postChallengeRecommend() sets missions) by creating entries for each mission
(e.g., set missonsSelectedState = Dictionary(uniqueKeysWithValues: missions.map
{ ($0, false) })), or alternatively in selectMission ensure a default exists
before toggling (e.g., if missonsSelectedState[mission] == nil {
missonsSelectedState[mission] = false }; then toggle). Update the code paths
that set missions (such as postChallengeRecommend and wherever missions is
assigned) to populate missonsSelectedState so selectMission will always toggle
an existing Bool.
- Around line 10-40: Rename the enum challengeViewState to ChallengeViewState
and fix the misspelled case .loding to .loading; update all switch arms and
references (including title, isRightButton and anywhere StepNavigatable is used)
to use the new enum name and corrected case so the code compiles and behavior is
preserved (keep the existing title mapping for .loading if intended).
- Around line 91-97: postChallengeRecommend currently sets isLoading = true but
never resets it; update the function (postChallengeRecommend in
CreateChallengeViewModel) to ensure isLoading is set back to false on both
success and failure (use a defer block or set it in both the success path after
missions assignment and in the catch block around
postChallengeRecommendUseCase.excute) and preserve awaiting/throwing behavior;
also keep existing logging (CherrishLogger.debug) and ensure any thrown errors
are rethrown or handled as before.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift`:
- Around line 83-96: The new DI resolution guards for
FetchChllengeHomecareRoutinesUseCase, PostChallengeRecommendUseCase, and
CreateChallengeUseCase return early without logging; update those guard failure
branches to call CherrishLogger.error(CherrishError.DIFailedError) before
returning so failures match the existing pattern used for other DI resolves,
ensuring the CreateChallengeViewModel registration still uses the resolved
fetchRoutineUseCase, postChallengeRecommendUseCase, and createChallengeUseCase
variables.

Comment on lines 44 to 47
DIContainer.shared.register(type: ChallengeInterface.self) {
return ChallengeRepository(
networkService: self.networkService, userDefaultService: self.userDefaultService)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Repository 네이밍 컨벤션 불일치

다른 Repository들은 DefaultCalendarRepository, DefaultHomeRepository, DefaultOnboardingRepositoryDefault 접두사를 사용하고 있지만, ChallengeRepository는 이 컨벤션을 따르지 않습니다.

일관성을 위해 DefaultChallengeRepository로 이름을 변경하는 것을 권장합니다.

 DIContainer.shared.register(type: ChallengeInterface.self) {
-    return ChallengeRepository(
+    return DefaultChallengeRepository(
         networkService: self.networkService, userDefaultService: self.userDefaultService)
 }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift` around lines 44
- 47, Rename the concrete repository class ChallengeRepository to
DefaultChallengeRepository and update all references (including its type in DI
registration DIContainer.shared.register(type: ChallengeInterface.self) where
the factory returns DefaultChallengeRepository) so the constructor signature and
injected dependencies (networkService, userDefaultService) remain unchanged;
also rename the source file to match the new class name and update any
imports/usages across the codebase that reference ChallengeRepository to avoid
build errors.

Copy link
Contributor

Choose a reason for hiding this comment

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

요고 네이민 수정 부탁드려용

Comment on lines 2 to 12
// RecommentMisssionsResponseDTO.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/20/26.
//

import Foundation

struct RecommendMisssionsResponseDTO: Decodable {
let routines: [String]
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

타입/파일명 오탈자 정리 권장
RecommendMisssionsResponseDTO 및 헤더 파일명 표기가 오탈자라 검색성과 일관성이 떨어집니다. RecommendMissionsResponseDTO로 정리하고 관련 참조도 함께 수정하는 리팩터링을 권장합니다.

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Model/RecommendMisssionsResponseDTO.swift`
around lines 2 - 12, The DTO name and filename contain typos: rename the struct
RecommendMisssionsResponseDTO to RecommendMissionsResponseDTO and update the
source file and header comment accordingly (change
"RecommentMisssionsResponseDTO.swift" or current header to
"RecommendMissionsResponseDTO.swift"); then update all references/usages across
the codebase (decoding calls, tests, imports, and any Codable mapping) to the
new symbol to keep naming consistent and searchable.

Copy link
Contributor

Choose a reason for hiding this comment

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

오타 수정부탁드립니둥

Comment on lines +54 to +59
var bodyParameters: Parameters? {
switch self {
case .createChallenge(_, let dto):
return try? dto.toDictionary()
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인코딩 실패 시 nil을 반환하여 디버깅이 어려울 수 있습니다.

try?를 사용하면 toDictionary() 인코딩 실패 시 nil이 반환되어 body 없이 API 요청이 전송될 수 있습니다. 서버에서 400 에러가 발생해도 원인 파악이 어렵습니다.

🔧 수정 제안

옵션 1: 에러 로깅 추가

 var bodyParameters: Parameters? {
     switch self {
     case .createChallenge(_, let dto):
-        return try? dto.toDictionary()
+        do {
+            return try dto.toDictionary()
+        } catch {
+            CherrishLogger.error(error)
+            return nil
+        }
     }
 }

옵션 2: EndPoint 프로토콜이 throwing을 지원한다면 에러를 전파하는 것이 더 좋습니다.

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/ChallengeDemoAPI.swift`
around lines 54 - 59, The current bodyParameters computed property uses `try?
dto.toDictionary()` which swallows encoding errors and returns nil; change it to
explicitly handle encoding failures for the `.createChallenge` case by using a
`do`/`catch` around `dto.toDictionary()` and log the caught error (or assert /
capture to your telemetry) so you don't silently send requests without a body;
alternatively, if the EndPoint protocol can be changed, make `bodyParameters`
throwing and propagate the error instead of returning nil so callers can handle
it. Ensure you update the `.createChallenge(_, let dto)` branch to reference
`dto.toDictionary()` inside the new error-handling path.

Copy link
Contributor

Choose a reason for hiding this comment

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

@y-eonee 요고 확인 부탁드려요

Comment on lines +91 to +97
@MainActor
func postChallengeRecommend() async throws {
guard let selectedRoutine else { return }
isLoading = true
missions = try await postChallengeRecommendUseCase.excute(id: selectedRoutine.id)
CherrishLogger.debug("뷰모델 미션 \(missions)")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

isLoadingtrue로 설정된 후 다시 false로 복원되지 않음

postChallengeRecommend()에서 isLoading = true를 설정하지만, 성공 또는 실패 시 false로 되돌리지 않습니다. 이로 인해 UI가 영구적인 로딩 상태에 빠질 수 있습니다.

🐛 제안하는 수정
 `@MainActor`
 func postChallengeRecommend() async throws {
     guard let selectedRoutine else { return  }
     isLoading = true
-    missions = try await postChallengeRecommendUseCase.excute(id: selectedRoutine.id)
-    CherrishLogger.debug("뷰모델 미션 \(missions)")
+    defer { isLoading = false }
+    do {
+        missions = try await postChallengeRecommendUseCase.excute(id: selectedRoutine.id)
+        CherrishLogger.debug("뷰모델 미션 \(missions)")
+        // Initialize mission selection state
+        missonsSelectedState = Dictionary(uniqueKeysWithValues: missions.map { ($0, false) })
+    } catch {
+        CherrishLogger.debug("미션 추천 실패: \(error)")
+        throw error
+    }
 }
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ViewModel/CreateChallengeViewModel.swift`
around lines 91 - 97, postChallengeRecommend currently sets isLoading = true but
never resets it; update the function (postChallengeRecommend in
CreateChallengeViewModel) to ensure isLoading is set back to false on both
success and failure (use a defer block or set it in both the success path after
missions assignment and in the catch block around
postChallengeRecommendUseCase.excute) and preserve awaiting/throwing behavior;
also keep existing logging (CherrishLogger.debug) and ensure any thrown errors
are rethrown or handled as before.

Comment on lines 99 to 102
func selectMission(mission: ChallengeMissionEntity) {
missonsSelectedState[mission]?.toggle()
CherrishLogger.debug("미션 체크 상태 \(missonsSelectedState)")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

selectMission이 동작하지 않음 - 딕셔너리에 키가 없으면 toggle이 실행되지 않습니다

missonsSelectedState[mission]?.toggle()은 해당 키가 딕셔너리에 없으면 nil을 반환하고 toggle()이 호출되지 않습니다. missions를 받아올 때 missonsSelectedState를 초기화해야 합니다.

🐛 제안하는 수정
 func selectMission(mission: ChallengeMissionEntity) {
-    missonsSelectedState[mission]?.toggle()
+    if missonsSelectedState[mission] != nil {
+        missonsSelectedState[mission]?.toggle()
+    } else {
+        missonsSelectedState[mission] = true
+    }
     CherrishLogger.debug("미션 체크 상태 \(missonsSelectedState)")
 }

또는 postChallengeRecommend()에서 missions 설정 시 초기화:

missions = try await postChallengeRecommendUseCase.excute(id: selectedRoutine.id)
missonsSelectedState = Dictionary(uniqueKeysWithValues: missions.map { ($0, false) })
📝 Committable suggestion

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

Suggested change
func selectMission(mission: ChallengeMissionEntity) {
missonsSelectedState[mission]?.toggle()
CherrishLogger.debug("미션 체크 상태 \(missonsSelectedState)")
}
func selectMission(mission: ChallengeMissionEntity) {
if missonsSelectedState[mission] != nil {
missonsSelectedState[mission]?.toggle()
} else {
missonsSelectedState[mission] = true
}
CherrishLogger.debug("미션 체크 상태 \(missonsSelectedState)")
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ViewModel/CreateChallengeViewModel.swift`
around lines 99 - 102, selectMission currently calls
missonsSelectedState[mission]?.toggle() which does nothing if the mission key is
missing; initialize missonsSelectedState whenever missions are loaded (or when
postChallengeRecommend() sets missions) by creating entries for each mission
(e.g., set missonsSelectedState = Dictionary(uniqueKeysWithValues: missions.map
{ ($0, false) })), or alternatively in selectMission ensure a default exists
before toggling (e.g., if missonsSelectedState[mission] == nil {
missonsSelectedState[mission] = false }; then toggle). Update the code paths
that set missions (such as postChallengeRecommend and wherever missions is
assigned) to populate missonsSelectedState so selectMission will always toggle
an existing Bool.

Comment on lines +83 to +96
guard let fetchChallengeHomecareRoutines = DIContainer.shared.resolve(type: FetchChllengeHomecareRoutinesUseCase.self) else {
return
}
guard let postChallengeRecommendUseCase = DIContainer.shared.resolve(type: PostChallengeRecommendUseCase.self) else {
return
}

guard let createChallengeUseCase = DIContainer.shared.resolve(type: CreateChallengeUseCase.self) else {
return
}

DIContainer.shared.register(type: CreateChallengeViewModel.self) {
return CreateChallengeViewModel(fetchRoutineUseCase: fetchChallengeHomecareRoutines, postChallengeRecommendUseCase: postChallengeRecommendUseCase, createChallengeUseCase: createChallengeUseCase)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

에러 로깅 일관성 부재

다른 DI resolution 실패 시 CherrishLogger.error(CherrishError.DIFailedError)를 호출하는 곳이 있지만(Line 21, 33, 54), 새로 추가된 guard 문들(Line 83-92)에서는 에러 로깅 없이 바로 return하고 있습니다.

일관성을 위해 에러 로깅을 추가하거나, 기존 코드의 로깅도 통일해주세요:

 guard let fetchChallengeHomecareRoutines = DIContainer.shared.resolve(type: FetchChllengeHomecareRoutinesUseCase.self) else {
+    CherrishLogger.error(CherrishError.DIFailedError)
     return
 }
 guard let postChallengeRecommendUseCase = DIContainer.shared.resolve(type: PostChallengeRecommendUseCase.self) else {
+    CherrishLogger.error(CherrishError.DIFailedError)
     return
 }

 guard let createChallengeUseCase = DIContainer.shared.resolve(type: CreateChallengeUseCase.self) else {
+    CherrishLogger.error(CherrishError.DIFailedError)
     return
 }
📝 Committable suggestion

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

Suggested change
guard let fetchChallengeHomecareRoutines = DIContainer.shared.resolve(type: FetchChllengeHomecareRoutinesUseCase.self) else {
return
}
guard let postChallengeRecommendUseCase = DIContainer.shared.resolve(type: PostChallengeRecommendUseCase.self) else {
return
}
guard let createChallengeUseCase = DIContainer.shared.resolve(type: CreateChallengeUseCase.self) else {
return
}
DIContainer.shared.register(type: CreateChallengeViewModel.self) {
return CreateChallengeViewModel(fetchRoutineUseCase: fetchChallengeHomecareRoutines, postChallengeRecommendUseCase: postChallengeRecommendUseCase, createChallengeUseCase: createChallengeUseCase)
}
guard let fetchChallengeHomecareRoutines = DIContainer.shared.resolve(type: FetchChllengeHomecareRoutinesUseCase.self) else {
CherrishLogger.error(CherrishError.DIFailedError)
return
}
guard let postChallengeRecommendUseCase = DIContainer.shared.resolve(type: PostChallengeRecommendUseCase.self) else {
CherrishLogger.error(CherrishError.DIFailedError)
return
}
guard let createChallengeUseCase = DIContainer.shared.resolve(type: CreateChallengeUseCase.self) else {
CherrishLogger.error(CherrishError.DIFailedError)
return
}
DIContainer.shared.register(type: CreateChallengeViewModel.self) {
return CreateChallengeViewModel(fetchRoutineUseCase: fetchChallengeHomecareRoutines, postChallengeRecommendUseCase: postChallengeRecommendUseCase, createChallengeUseCase: createChallengeUseCase)
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift`
around lines 83 - 96, The new DI resolution guards for
FetchChllengeHomecareRoutinesUseCase, PostChallengeRecommendUseCase, and
CreateChallengeUseCase return early without logging; update those guard failure
branches to call CherrishLogger.error(CherrishError.DIFailedError) before
returning so failures match the existing pattern used for other DI resolves,
ensuring the CreateChallengeViewModel registration still uses the resolved
fetchRoutineUseCase, postChallengeRecommendUseCase, and createChallengeUseCase
variables.

Copy link
Contributor

@y-eonee y-eonee left a comment

Choose a reason for hiding this comment

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

IMG_6653
다사다난햇다 .... 고생했어여 ㅠㅠ


import Alamofire

struct ChallengeRepository: ChallengeInterface {
Copy link
Contributor

Choose a reason for hiding this comment

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

DefaultChallengeRepository로 네이밍 수정해주세요!


func aiRecommendations(id: Int) async throws -> [ChallengeMissionEntity] {

let response = try await networkService.request(ChallengeAPI.aiRecommendations(homecareRoutineId: id), decodingType: RecommendMisssionsResponseDTO.self)
Copy link
Contributor

Choose a reason for hiding this comment

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

개행해주세요!

Comment on lines +84 to +86
DIContainer.shared.register(type: FetchChllengeHomecareRoutinesUseCase.self) {
return DefaultFetchChallengeHomecareRoutinesUseCase(repository: challengeRepository)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

확인해주세요!

@@ -0,0 +1,24 @@
//
// FetchChllengeHomecareRoutinesUseCase.swift
Copy link
Contributor

Choose a reason for hiding this comment

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

이거한번만 봐주세요!!

Comment on lines 19 to 21
self.repository = repository
}
func excute() async throws -> [RoutineEntity] {
Copy link
Contributor

Choose a reason for hiding this comment

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

줄바꿈 한번만 해주세요 ~~


@Published var path: NavigationPath = NavigationPath()

var makeChallengeViewModel: CreateChallengeViewModel?
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 없애주세요!!


import Foundation

enum challengeViewState: StepNavigatable {
Copy link
Contributor

Choose a reason for hiding this comment

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

대문자로 해주세요!

Copy link
Contributor

@wotjs020708 wotjs020708 left a comment

Choose a reason for hiding this comment

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

너무 고생하셨습니다...!

Comment on lines 44 to 47
DIContainer.shared.register(type: ChallengeInterface.self) {
return ChallengeRepository(
networkService: self.networkService, userDefaultService: self.userDefaultService)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

요고 네이민 수정 부탁드려용


import Foundation

struct ChallengeRoutineDTO: Decodable {
Copy link
Contributor

Choose a reason for hiding this comment

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

네이밍 수정 부탁드려요

case .fetchRoutines:
return nil
case .aiRecommendations(let homecareRoutineId):
return ["homecareRoutineId" : homecareRoutineId]
Copy link
Contributor

Choose a reason for hiding this comment

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

요거 extension 만든 거 사용하시면 될 거 가탕요

Comment on lines +54 to +59
var bodyParameters: Parameters? {
switch self {
case .createChallenge(_, let dto):
return try? dto.toDictionary()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@y-eonee 요고 확인 부탁드려요

Comment on lines +84 to +86
DIContainer.shared.register(type: FetchChllengeHomecareRoutinesUseCase.self) {
return DefaultFetchChallengeHomecareRoutinesUseCase(repository: challengeRepository)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

오타 수정 해주세요

Comment on lines +59 to +72
viewModel.next()

Task {
do {
async let requestTask: () = viewModel.postChallengeRecommend()
async let minimumDelay: () = Task.sleep(nanoseconds: 3_000_000_000)

_ = try await (requestTask, minimumDelay)

viewModel.isLoading = false
viewModel.next()
} catch {
CherrishLogger.error(error)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

요런 디테일 해주면 좋긴 할 거 같아요

Comment on lines +10 to +14
struct CreateChallengeView: View {
@EnvironmentObject var challengeCoordinator: ChallengeCoordinator
@EnvironmentObject var tabBarCoordinator: TabBarCoordinator
@StateObject var viewModel: CreateChallengeViewModel

Copy link
Contributor

Choose a reason for hiding this comment

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

@y-eonee init 에서 StateObject(wrappedValue:) 이렇게하는 거 좋은 거 같은데 어떻게 생각하시나요?

Copy link
Contributor

@soseoyo12 soseoyo12 left a comment

Choose a reason for hiding this comment

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

오타만 수정하면 될 것 같습니다 수고하셨습니다!

Comment on lines 2 to 12
// RecommentMisssionsResponseDTO.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/20/26.
//

import Foundation

struct RecommendMisssionsResponseDTO: Decodable {
let routines: [String]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

오타 수정부탁드립니둥

//

import Foundation
import Alamofire
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 한 줄 띄워주세요

@sum130 sum130 merged commit a71f34f into develop Jan 21, 2026
@sum130 sum130 deleted the hotfix/#132-chellengeAPIFix branch January 21, 2026 12:33
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Model/ChallengeRoutineRequestDTO.swift`:
- Around line 10-14: The struct ChallengeRoutineRequestDTO is misnamed for a
server response decoder; rename the type to ChallengeRoutineResponseDTO (the
struct currently declared as ChallengeRoutineRequestDTO : Decodable) and update
all references/usages and import points where decoding occurs (e.g., JSONDecoder
calls, decoding targets, tests, and any type annotations) to the new name so it
matches the RecommendMissionsResponseDTO naming convention and avoids
compilation errors.

In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/ChallengeAPI.swift`:
- Around line 40-47: The headers computed property in ChallengeAPI currently
uses a switch over cases fetchRoutines and aiRecommendations but always returns
.basic; remove the switch and simplify var headers: HeaderType to directly
return .basic (replace the switch in ChallengeAPI's headers property), keeping
the HeaderType return type and ensuring both cases no longer need individual
handling.
♻️ Duplicate comments (9)
Cherrish-iOS/Cherrish-iOS/Data/Model/RecommendMissionsResponseDTO.swift (1)

1-6: 헤더 주석 오타 수정 필요

Line 2의 파일명 주석에 오타가 있습니다: RecommentMisssionsResponseDTORecommendMissionsResponseDTO. 이전 리뷰에서도 지적된 사항입니다.

♻️ 제안하는 수정
 //
-//  RecommentMisssionsResponseDTO.swift
+//  RecommendMissionsResponseDTO.swift
 //  Cherrish-iOS
Cherrish-iOS/Cherrish-iOS/Data/Repository/ChallengeRepository.swift (1)

35-45: userID 기본값 1 사용 및 미사용 변수 문제

두 가지 문제가 있습니다:

  1. 보안 위험: userID가 없을 때 기본값 1을 사용하면, 인증되지 않은 상태에서 다른 사용자(ID=1)의 계정에 챌린지가 생성될 수 있습니다.

  2. 미사용 변수: response가 선언되었지만 사용되지 않습니다.

이전 리뷰에서도 지적된 사항입니다.

🔧 제안하는 수정
 func createChallenge(missionIds: Int, routineNames: [String]) async throws  {
-    let userID: Int = userDefaultService.load(key: .userID) ?? 1
-    let response: () = try await networkService.request(
+    guard let userID: Int = userDefaultService.load(key: .userID) else {
+        throw ChallengeRepositoryError.userNotAuthenticated
+    }
+    try await networkService.request(
         ChallengeDemoAPI.createChallenge(userID: userID, requestDTO:
                 .init(
                     homecareRoutineId: missionIds,
                     routineNames: routineNames
                 )
         )
     )
 }

에러 타입 정의 추가 필요:

enum ChallengeRepositoryError: Error {
    case userNotAuthenticated
}
Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/ChallengeAPI.swift (1)

67-74: DTO와 toDictionary() 확장 메서드 사용 권장

다른 API에서 MakeChallengeRequestDTO.toDictionary()를 사용하는 패턴과 일관성을 위해, RecommendMissionsRequestDTO를 활용하는 것을 고려해주세요. 이전 리뷰 코멘트에서도 extension 사용이 제안되었습니다.

♻️ 제안하는 수정
     var bodyParameters: Parameters? {
         switch self {
         case .fetchRoutines:
             return nil
         case .aiRecommendations(let homecareRoutineId):
-            return ["homecareRoutineId" : homecareRoutineId]
+            return RecommendMissionsRequestDTO(homecareRoutineId: homecareRoutineId).toDictionary()
         }
     }

참고: RecommendMissionsRequestDTOEncodable을 채택하고 있으므로, 프로젝트에 Encodable 확장의 toDictionary() 메서드가 있다면 바로 사용 가능합니다.

Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchChllengeHomecareRoutinesUseCase.swift (1)

10-23: 오타 및 긴 타입명 정리 필요(중복 지적)
Chllenge, excute() 오타는 유지되면 전반 호출부 가독성과 일관성을 해칩니다. 또한 구현체 이름이 SwiftLint 길이 제한(40자)을 초과합니다. 파일명/프로토콜/메서드/구현체를 일괄 정리해 주세요.

✏️ 제안 수정
-protocol FetchChllengeHomecareRoutinesUseCase {
-    func excute() async throws -> [RoutineEntity]
-}
+protocol FetchChallengeHomecareRoutinesUseCase {
+    func execute() async throws -> [RoutineEntity]
+}
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ViewModel/CreateChallengeViewModel.swift (2)

90-96: 로딩 상태가 영구 고정될 수 있음(중복 지적)
isLoading = true 후 성공/실패 모두에서 false로 복원되지 않아 UI가 무한 로딩에 빠질 수 있습니다. 또한 missions 로드 후 선택 상태 초기화가 필요합니다.

🐛 수정 제안
 `@MainActor`
 func postChallengeRecommend() async throws {
     guard let selectedRoutine else { return  }
-    isLoading = true
-    missions = try await postChallengeRecommendUseCase.excute(id: selectedRoutine.id)
-    CherrishLogger.debug("뷰모델 미션 \(missions)")
+    isLoading = true
+    defer { isLoading = false }
+    missions = try await postChallengeRecommendUseCase.excute(id: selectedRoutine.id)
+    missionsSelectedState = Dictionary(uniqueKeysWithValues: missions.map { ($0, false) })
+    CherrishLogger.debug("뷰모델 미션 \(missions)")
 }

98-100: 키 미존재 시 토글이 무시됨(중복 지적)
missionsSelectedState[mission]?.toggle()은 키가 없으면 동작하지 않습니다. 기본값을 먼저 세팅해 주세요.

🐛 수정 제안
 func selectMission(mission: ChallengeMissionEntity) {
-    missionsSelectedState[mission]?.toggle()
+    if missionsSelectedState[mission] == nil {
+        missionsSelectedState[mission] = false
+    }
+    missionsSelectedState[mission]?.toggle()
     CherrishLogger.debug("미션 체크 상태 \(missionsSelectedState)")
 }
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeSelectMissionView.swift (2)

1-6: 파일 헤더명 불일치(중복 지적)
주석의 파일명이 실제 타입명과 다릅니다.

🔧 수정 제안
-//SelectMissionView.swift
+//  ChallengeSelectMissionView.swift

67-72: 실패 시 사용자 피드백 누락(중복 지적)
makeChallenge() 실패 시 로그만 남고 UI 피드백이 없어 사용자 경험이 떨어집니다. Alert/Toast 등을 노출해 주세요.

💡 예시 수정
 struct ChallengeSelectMissionView: View {
+    `@State` private var showErrorAlert = false
+    `@State` private var errorMessage = ""
@@
                 Task {
                     do {
                         try await viewModel.makeChallenge()
                         challengeCoordinator.push(.challengeProgress)
                     } catch {
                         CherrishLogger.error(error)
+                        errorMessage = "챌린지 생성에 실패했습니다. 다시 시도해주세요."
+                        showErrorAlert = true
                     }
                 }
             }
@@
         .frame(maxHeight: .infinity)
         .ignoresSafeArea(edges: .bottom)
+        .alert("오류", isPresented: $showErrorAlert) {
+            Button("확인", role: .cancel) { }
+        } message: {
+            Text(errorMessage)
+        }
     }
 }
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/CreateChallengeView.swift (1)

10-13: 외부 주입 ViewModel 소유권 명확화 필요(중복 지적)
DI로 주입받는 인스턴스를 @StateObject로 선언하면 소유권이 모호해질 수 있습니다. @ObservedObject로 변경하거나 init에서 StateObject(wrappedValue:)로 명시해 주세요.

♻️ 변경 예시
-    `@StateObject` var viewModel: CreateChallengeViewModel
+    `@ObservedObject` var viewModel: CreateChallengeViewModel

Comment on lines +10 to +14
struct ChallengeRoutineRequestDTO: Decodable {
let id: Int
let name: String
let description: String
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

DTO 네이밍 검토 필요

이 DTO는 서버 응답을 디코딩하는 용도(Decodable)로 사용되므로, ChallengeRoutineRequestDTO보다 ChallengeRoutineResponseDTO가 더 적절합니다. RecommendMissionsResponseDTO와 네이밍 규칙을 통일하는 것이 좋습니다.

♻️ 네이밍 수정 제안
-struct ChallengeRoutineRequestDTO: Decodable {
+struct ChallengeRoutineResponseDTO: Decodable {
     let id: Int
     let name: String
     let description: String
 }
📝 Committable suggestion

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

Suggested change
struct ChallengeRoutineRequestDTO: Decodable {
let id: Int
let name: String
let description: String
}
struct ChallengeRoutineResponseDTO: Decodable {
let id: Int
let name: String
let description: String
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Model/ChallengeRoutineRequestDTO.swift` around
lines 10 - 14, The struct ChallengeRoutineRequestDTO is misnamed for a server
response decoder; rename the type to ChallengeRoutineResponseDTO (the struct
currently declared as ChallengeRoutineRequestDTO : Decodable) and update all
references/usages and import points where decoding occurs (e.g., JSONDecoder
calls, decoding targets, tests, and any type annotations) to the new name so it
matches the RecommendMissionsResponseDTO naming convention and avoids
compilation errors.

Comment on lines +40 to +47
var headers: HeaderType {
switch self {
case .fetchRoutines:
return .basic
case .aiRecommendations:
return .basic
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

headers 속성 단순화 가능

모든 케이스에서 동일한 .basic 값을 반환하므로 switch 문 없이 단순화할 수 있습니다.

♻️ 제안하는 수정
     var headers: HeaderType {
-        switch self {
-        case .fetchRoutines:
-            return .basic
-        case .aiRecommendations:
-            return .basic
-        }
+        return .basic
     }
📝 Committable suggestion

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

Suggested change
var headers: HeaderType {
switch self {
case .fetchRoutines:
return .basic
case .aiRecommendations:
return .basic
}
}
var headers: HeaderType {
return .basic
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/ChallengeAPI.swift` around
lines 40 - 47, The headers computed property in ChallengeAPI currently uses a
switch over cases fetchRoutines and aiRecommendations but always returns .basic;
remove the switch and simplify var headers: HeaderType to directly return .basic
(replace the switch in ChallengeAPI's headers property), keeping the HeaderType
return type and ensuring both cases no longer need individual handling.

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

Labels

Hotfix 충돌해결 등 devlop에 바로 반영해야하는 경우 사용 수민🐶

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Hotfix] 챌린지 api 복구

4 participants