Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ final class DataDependencyAssembler: DependencyAssembler {
DIContainer.shared.register(type: TreatmentInterface.self) {
return DefaultTreatmentRepository(networkService: self.networkService, userDefaultService: self.userDefaultService)
}
DIContainer.shared.register(type: ChallengeInterface.self) {
return DefaultChallengeRepository(
networkService: self.networkService, userDefaultService: self.userDefaultService)
}
Comment on lines 44 to 47
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.

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


DIContainer.shared.register(type: DemoInterface.self) {
return DefaultDemoRepository(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// ChallengeRoutineRequestDTO.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/19/26.
//

import Foundation

struct ChallengeRoutineRequestDTO: Decodable {
let id: Int
let name: String
let description: String
}
Comment on lines +10 to +14
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.


extension ChallengeRoutineRequestDTO {
func toEntity() -> RoutineEntity {
RoutineEntity(
id: id,
name: name,
description: description
)
}
}
19 changes: 19 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/Model/Extension/Encodable+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Encodable+.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/21/26.
//

import Foundation

extension Encodable {
func toDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
let json = try JSONSerialization.jsonObject(with: data)
guard let dictionary = json as? [String: Any] else {
throw CherrishError.encodingError
}
return dictionary
}
}
13 changes: 13 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/Model/MakeChallengeRequestDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// MakeChallengeRequestDTO.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/21/26.
//

import Foundation

struct MakeChallengeRequestDTO: Encodable {
let homecareRoutineId: Int
let routineNames: [String]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// RecommendMissionsRequestDTO.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/20/26.
//

import Foundation

struct RecommendMissionsRequestDTO: Encodable {
let homecareRoutineId: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// RecommentMisssionsResponseDTO.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/20/26.
//

import Foundation

struct RecommendMissionsResponseDTO: Decodable {
let routines: [String]
}
Comment on lines 2 to 12
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.

오타 수정부탁드립니둥


extension RecommendMissionsResponseDTO {
func toEntities() -> [ChallengeMissionEntity] {
return routines.enumerated().map { index, title in
ChallengeMissionEntity(id: index, title: title)
}
}
}
76 changes: 76 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/ChallengeAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// ChallengeAPI.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/19/26.
//

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.

이거 한 줄 띄워주세요


enum ChallengeAPI: EndPoint {
case fetchRoutines
case aiRecommendations(homecareRoutineId: Int)

var basePath: String {
return "/api/challenges"
}

var path: String {
switch self {
case .fetchRoutines:
return "/homecare-routines"
case .aiRecommendations:
return "/ai-recommendations"
}
}


var method: Alamofire.HTTPMethod{
switch self {
case .fetchRoutines:
return .get
case .aiRecommendations:
return .post
}
}


var headers: HeaderType {
switch self {
case .fetchRoutines:
return .basic
case .aiRecommendations:
return .basic
}
}
Comment on lines +40 to +47
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.


var parameterEncoding: any Alamofire.ParameterEncoding {
switch self {
case .fetchRoutines:
return URLEncoding.default
case .aiRecommendations:
return JSONEncoding.default
}
}

var queryParameters: [String : Any]? {
switch self {
case .fetchRoutines:
return nil
case .aiRecommendations:
return nil
}
}

var bodyParameters: Parameters? {
switch self {
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 만든 거 사용하시면 될 거 가탕요

}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// ChallengeDemoAPI.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/21/26.
//

import Foundation
import Alamofire

enum ChallengeDemoAPI: EndPoint {
case createChallenge(userID: Int, requestDTO: MakeChallengeRequestDTO)

var basePath: String {
return "/api/demo"
}

var path: String {
switch self {
case .createChallenge:
return "/challenges"
}
}

var method: Alamofire.HTTPMethod{
switch self {
case .createChallenge:
return .post
}
}


var headers: HeaderType {
switch self {
case .createChallenge(let userID, _):
return .withAuth(userID: userID)
}
}

var parameterEncoding: any Alamofire.ParameterEncoding {
switch self {
case .createChallenge:
return JSONEncoding.default
}
}

var queryParameters: [String : Any]? {
switch self {
case .createChallenge:
return nil
}
}

var bodyParameters: Parameters? {
switch self {
case .createChallenge(_, let dto):
return try? dto.toDictionary()
}
}
Comment on lines +54 to +59
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 요고 확인 부탁드려요

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// ChallengeRepository.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/19/26.
//

import Foundation

import Alamofire

struct DefaultChallengeRepository: ChallengeInterface {
private let networkService: NetworkService
private let userDefaultService: UserDefaultService

init(networkService: NetworkService, userDefaultService: UserDefaultService) {
self.networkService = networkService
self.userDefaultService = userDefaultService
}

func fetchHomecareRoutines() async throws -> [RoutineEntity] {
let response = try await networkService.request(ChallengeAPI.fetchRoutines, decodingType: [ChallengeRoutineRequestDTO].self)

return response.map { $0.toEntity() }
}

func aiRecommendations(id: Int) async throws -> [ChallengeMissionEntity] {
let response = try await
networkService.request(ChallengeAPI.aiRecommendations(homecareRoutineId: id),
decodingType: RecommendMissionsResponseDTO.self)
CherrishLogger.debug(response)
return response.toEntities()
}

func createChallenge(missionIds: Int, routineNames: [String]) async throws {
let userID: Int = userDefaultService.load(key: .userID) ?? 1
let response: () = try await networkService.request(
ChallengeDemoAPI.createChallenge(userID: userID, requestDTO:
.init(
homecareRoutineId: missionIds,
routineNames: routineNames
)
)
)
}
Comment on lines +35 to +45
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

사용하지 않는 변수와 userID 폴백 값 검토 필요

두 가지 이슈가 있습니다:

  1. 사용하지 않는 response 변수: Line 36에서 response를 선언했지만 사용하지 않습니다. _ =로 변경하거나 변수 선언을 제거하세요.

  2. userID 기본값 1: 사용자 ID가 없을 때 기본값 1을 사용하면, 인증되지 않은 상태에서 다른 사용자의 데이터에 챌린지가 생성될 수 있습니다. 인증 문제를 조기에 발견하려면 에러를 던지는 것이 더 안전합니다.

🔧 제안하는 수정
 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 ChallengeError.userNotAuthenticated
+    }
+    try await networkService.request(
         ChallengeDemoAPI.createChallenge(userID: userID, requestDTO:
                 .init(
                     homecareRoutineId: missionIds,
                     routineNames: routineNames
                 )
         )
     )
 }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/ChallengeRepository.swift` around
lines 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.

}
17 changes: 17 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,22 @@ final class DomainDependencyAssembler: DependencyAssembler {
DIContainer.shared .register(type: CreateUserProcedureUseCase.self) {
return DefaultCreateUserProcedureUseCase(repository: treatmentRepository)
}

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

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

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

UseCase 이름에 오타 존재: "Chllenge" → "Challenge"

FetchChllengeHomecareRoutinesUseCase에서 "Challenge"가 "Chllenge"로 잘못 표기되어 있습니다. 이 타입이 다른 곳에서 정의되어 있다면, 해당 파일들도 함께 수정이 필요합니다.

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift` around
lines 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.

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

Choose a reason for hiding this comment

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

오타 수정 해주세요



DIContainer.shared.register(type: PostChallengeRecommendUseCase.self) {
return DefaultSubmitChallengRecommendUseCase(repository: challengeRepository)
}

DIContainer.shared.register(type: CreateChallengeUseCase.self) {
return DefaultCreateChallengeUseCase(repository: challengeRepository)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// ChallengeInterface.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/19/26.
//

import Foundation

protocol ChallengeInterface {
func fetchHomecareRoutines() async throws -> [RoutineEntity]
func aiRecommendations(id: Int) async throws -> [ChallengeMissionEntity]
func createChallenge(missionIds: Int, routineNames: [String]) async throws
}
Comment on lines +10 to +14
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

파라미터 명명이 타입과 일치하지 않음

createChallenge(missionIds: Int, ...) 메서드에서 missionIds라는 이름은 복수형이지만 타입은 단일 Int입니다. Repository 구현체를 보면 실제로는 homecareRoutineId로 사용되고 있습니다.

명확성을 위해 파라미터 이름을 실제 의미에 맞게 수정하는 것을 권장합니다:

 protocol ChallengeInterface {
     func fetchHomecareRoutines() async throws -> [RoutineEntity]
     func aiRecommendations(id: Int) async throws -> [ChallengeMissionEntity]
-    func createChallenge(missionIds: Int, routineNames: [String]) async throws
+    func createChallenge(homecareRoutineId: Int, routineNames: [String]) async throws
 }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/Interface/ChallengeInterface.swift` around
lines 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.

13 changes: 13 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/Model/ChallengeEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// ChallengeEntity.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/21/26.
//

import Foundation

struct ChallengeEntity: Identifiable {
let id: Int
let routineNames: [String]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// ChallengeMissionEntity.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/20/26.
//

import Foundation

struct ChallengeMissionEntity: Identifiable, Equatable, Hashable {
let id: Int
let title: String
}
12 changes: 12 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/Model/RoutineEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// RoutineEntity.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/19/26.
//

struct RoutineEntity: Identifiable, Equatable {
let id: Int
let name: String
let description: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// CreateChallengeUseCase.swift
// Cherrish-iOS
//
// Created by sumin Kong on 1/21/26.
//

import Foundation

protocol CreateChallengeUseCase {
func execute(id: Int, routines: [String]) async throws
}

struct DefaultCreateChallengeUseCase: CreateChallengeUseCase {
private let repository: ChallengeInterface

init(repository: ChallengeInterface) {
self.repository = repository
}

func execute(id: Int, routines: [String]) async throws {
_ = try await repository.createChallenge(missionIds: id, routineNames: routines)
}
}
Loading