Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 6 additions & 1 deletion Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ final class DataDependencyAssembler: DependencyAssembler {
return DefaultTreatmentRepository(networkService: self.networkService, userDefaultService: self.userDefaultService)
}


DIContainer.shared.register(type: MyPageInterface.self) {
return DefaultMyPageRepository(
networkService: self.networkService,
userDefaultService: self.userDefaultService
)
}
}
}
19 changes: 19 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/Model/UserInfoResponseDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// UserInfoResponseDTO.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/20/26.
//

import Foundation

struct UserInfoResponseDTO: Decodable {
let name: String
let daysSinceSignup: Int
}

extension UserInfoResponseDTO {
func toEntity() -> UserInfoEntity {
.init(name: name, daysSinceSignup: daysSinceSignup)
}
Comment on lines +10 to +18
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

🧩 Analysis chain

🏁 Script executed:

# First, find the file and examine its context
find . -name "UserInfoResponseDTO.swift" -type f

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 134


🏁 Script executed:

# Search for test files related to this DTO
find . -name "*UserInfo*" -type f | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 266


🏁 Script executed:

# Look for API response examples, mock data, or test fixtures
find . -type f \( -name "*.json" -o -name "*Mock*" -o -name "*Test*" \) | grep -i user | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

# Search for actual usage of UserInfoResponseDTO to understand the API response format
rg "UserInfoResponseDTO" -A 5 -B 5 --type swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 2597


🏁 Script executed:

# Look for any API documentation or response mapping
find . -name "*.swift" -type f | xargs rg -l "days_since_signup\|daysSinceSignup" | head -10

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

# Find MyPageAPI definition to understand the endpoint
rg "enum MyPageAPI\|case users" -A 10 --type swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

# Look for API documentation, OpenAPI specs, or response examples
find . -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" -o -name "*.json" \) | xargs rg "days_since_signup\|daysSinceSignup" 2>/dev/null | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

# Check the network service implementation to understand how JSONDecoder is configured
rg "JSONDecoder\|keyDecodingStrategy" -B 3 -A 5 --type swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

# Look for any test files or fixtures that might have actual API responses
find . -type f -name "*Test*" -o -name "*Fixture*" -o -name "*Mock*" | xargs rg "name\|daysSinceSignup" 2>/dev/null | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

# Check if there's API documentation or README
find . -name "README*" -o -name "API*" | xargs cat 2>/dev/null | grep -A 10 -B 2 -i "user\|response" | head -40

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

# List files in the data layer to understand structure
find ./Cherrish-iOS/Cherrish-iOS/Data -type f -name "*.swift" | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1194


🏁 Script executed:

# Find API definitions
find . -name "*API.swift" -o -name "*api.swift" | head -10

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 199


🏁 Script executed:

# Look at MyPageRepository more carefully
cat ./Cherrish-iOS/Cherrish-iOS/Data/Repository/MyPageRepository.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 877


🏁 Script executed:

# Search for any configuration or comments about API response format
rg "name|days|signup" -B 3 -A 3 --type swift | head -50

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 5072


🏁 Script executed:

# Check if there are any unit tests
find . -name "*Test*.swift" -o -name "*test.swift" | head -10

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 133


🏁 Script executed:

# Examine MyPageAPI.swift
cat ./Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/MyPageAPI.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1039


🏁 Script executed:

# Examine NetworkService.swift
cat ./Cherrish-iOS/Cherrish-iOS/Data/Network/NetworkService.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 4441


🏁 Script executed:

# Check the single test file to see if there are response examples
cat ./Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/TestAPI.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 269


API 응답 키 형식과의 매핑이 명시되지 않아 디코딩 실패 위험.

daysSinceSignup은 서버 응답이 snake_case 형식(예: days_since_signup)일 경우 디코딩에 실패합니다. NetworkService에서 JSONDecoder()를 기본 설정으로 사용하고 있어서 키명이 정확히 일치해야 하는데, 실제 API 응답 형식이 코드에 명시되지 않았습니다. CodingKeys를 추가하거나 keyDecodingStrategy를 설정하여 안전하게 처리해주세요.

수정안
 struct UserInfoResponseDTO: Decodable {
     let name: String
     let daysSinceSignup: Int
+
+    private enum CodingKeys: String, CodingKey {
+        case name
+        case daysSinceSignup = "days_since_signup"
+    }
 }
📝 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 UserInfoResponseDTO: Decodable {
let name: String
let daysSinceSignup: Int
}
extension UserInfoResponseDTO {
func toEntity() -> UserInfoEntity {
.init(name: name, daysSinceSignup: daysSinceSignup)
}
struct UserInfoResponseDTO: Decodable {
let name: String
let daysSinceSignup: Int
private enum CodingKeys: String, CodingKey {
case name
case daysSinceSignup = "days_since_signup"
}
}
extension UserInfoResponseDTO {
func toEntity() -> UserInfoEntity {
.init(name: name, daysSinceSignup: daysSinceSignup)
}
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Model/UserInfoResponseDTO.swift` around lines
10 - 18, The DTO currently risks decoding failures because API uses snake_case
keys; update UserInfoResponseDTO to explicitly map days_since_signup to
daysSinceSignup by adding a CodingKeys enum (case name, daysSinceSignup =
"days_since_signup") inside the struct so Decodable uses the correct key, and
keep the toEntity() conversion unchanged; alternatively ensure NetworkService's
JSONDecoder uses keyDecodingStrategy = .convertFromSnakeCase if you prefer a
global fix.

}
58 changes: 58 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/MyPageAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// MyPageAPI.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/20/26.
//

import Foundation

import Alamofire

enum MyPageAPI {
case users(userID: Int)
}

extension MyPageAPI: EndPoint {
var basePath: String {
return "/api"
}

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

var method: Alamofire.HTTPMethod {
switch self {
case .users:
return .get
}
}

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

var parameterEncoding: any Alamofire.ParameterEncoding {
switch self {
case .users:
return URLEncoding.default
}
}

var queryParameters: [String : Any]? {
return nil
}

var bodyParameters: Alamofire.Parameters? {
return nil
}


}
30 changes: 30 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/Repository/MyPageRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// MyPageRepository.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/20/26.
//

import Foundation

struct DefaultMyPageRepository: MyPageInterface {
private let networkService: NetworkService
private let userDefaultService: UserDefaultService

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

func fetchUserInfo() async throws -> UserInfoEntity {
let userID: Int = userDefaultService.load(key: .userID) ?? 1
let response = try await networkService.request(
MyPageAPI.users(userID: userID),
decodingType: UserInfoResponseDTO.self)

return response.toEntity()
Comment on lines +22 to +28
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은 타 사용자 정보 노출 위험.

Line 23에서 userID가 없을 때 1로 대체하면 다른 유저의 정보를 가져올 수 있습니다. 필수 값이 없으면 오류를 던지거나 상위로 처리하도록 변경하세요.

🔧 권장 수정안
 func fetchUserInfo() async throws -> UserInfoEntity {
-    let userID: Int = userDefaultService.load(key: .userID) ?? 1
+    guard let userID: Int = userDefaultService.load(key: .userID) else {
+        throw MyPageRepositoryError.missingUserID
+    }
     let response = try await networkService.request(
         MyPageAPI.users(userID: userID),
         decodingType: UserInfoResponseDTO.self)
     
     return response.toEntity()
 }
+
+enum MyPageRepositoryError: Error {
+    case missingUserID
+}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/MyPageRepository.swift` around
lines 22 - 28, In fetchUserInfo(), stop defaulting userID to 1
(userDefaultService.load(key: .userID) ?? 1) because it can fetch another user's
data; instead validate the optional result from userDefaultService.load(key:
.userID) and if missing either throw a clear error (e.g., define and throw a
MissingUserID error) or propagate a failure to the caller before calling
MyPageAPI.users(userID:), ensuring the function does not proceed with a
hardcoded fallback.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,13 @@ final class DomainDependencyAssembler: DependencyAssembler {
DIContainer.shared.register(type: FetchTreatmentsUseCase.self) {
return DefaultFetchTreatmentsUseCase(repository: treatmentRepository)
}

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

DIContainer.shared.register(type: FetchUserInfoUseCase.self) {
return DefaultFetchUserInfoUserCase(repository: myPageRepository)
}
}
}
12 changes: 12 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/Interface/MyPageInterface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// MyPageInterface.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/20/26.
//

import Foundation

protocol MyPageInterface {
func fetchUserInfo() async throws -> UserInfoEntity
}
13 changes: 13 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/Model/UserInfoEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// UserInfoEntity.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/20/26.
//

import Foundation

struct UserInfoEntity {
let name: String
let daysSinceSignup: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// FetchUserInfoUseCase.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/20/26.
//

import Foundation

protocol FetchUserInfoUseCase {
func execute() async throws -> UserInfoEntity
}

struct DefaultFetchUserInfoUserCase: FetchUserInfoUseCase {
private let repository: MyPageInterface

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

func execute() async throws -> UserInfoEntity {
return try await repository.fetchUserInfo()
Comment on lines +14 to +22
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

타입명 오타(UserCase) 정리 권장.

Line 14의 DefaultFetchUserInfoUserCaseUseCase 오타로 보입니다. 추후 혼동을 줄이기 위해 DefaultFetchUserInfoUseCase로 정리하는 것을 권장합니다(관련 참조들도 함께 변경).

♻️ 권장 수정안
-struct DefaultFetchUserInfoUserCase: FetchUserInfoUseCase {
+struct DefaultFetchUserInfoUseCase: FetchUserInfoUseCase {
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchUserInfoUseCase.swift` around
lines 14 - 22, Rename the struct DefaultFetchUserInfoUserCase to
DefaultFetchUserInfoUseCase throughout the codebase and update all references
(including initializers, injections, protocol conformance to
FetchUserInfoUseCase, and any places that instantiate or type-annotate it) so
the identifier matches the corrected spelling; ensure the initializer
init(repository: MyPageInterface), the execute() async throws -> UserInfoEntity
method, and any tests or DI registrations are updated to the new name to avoid
compilation errors or mismatches.

Copy link
Contributor

Choose a reason for hiding this comment

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

요고 확인 부탁드릴게욤

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import SwiftUI

struct MyPageView: View {
@StateObject var viewModel: MyPageViewModel

Comment on lines +11 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

주입된 뷰모델은 @ObservedObject(또는 명시적 StateObject 초기화) 권장.

Line 11-12의 @StateObject는 외부에서 주입된 인스턴스를 부모가 교체할 때 업데이트가 무시될 수 있습니다. 주입된 객체라면 @ObservedObject로 바꾸거나, init에서 _viewModel = StateObject(wrappedValue:)로 명시적으로 소유권을 선언하는 쪽이 안전합니다.

♻️ 권장 수정안
-    `@StateObject` var viewModel: MyPageViewModel
+    `@ObservedObject` var viewModel: MyPageViewModel

또는

`@StateObject` private var viewModel: MyPageViewModel
init(viewModel: MyPageViewModel) {
    _viewModel = StateObject(wrappedValue: viewModel)
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/MyPage/MyPageView.swift`
around lines 11 - 12, 현재 MyPageView에 외부에서 주입된 `@StateObject` var viewModel:
MyPageViewModel 사용은 부모가 인스턴스를 교체할 때 업데이트를 놓칠 수 있으니, 주입된 뷰모델이면 선언을
`@ObservedObject로` 변경하거나 뷰가 소유해야 할 경우에는 private `@StateObject로` 만들고 init(viewModel:
MyPageViewModel)에서 _viewModel = StateObject(wrappedValue: viewModel)로 명시적으로
초기화하여 소유권을 선언하십시오; 관련 심볼: MyPageView, MyPageViewModel, `@StateObject`,
`@ObservedObject`, init, _viewModel = StateObject(wrappedValue:).

Copy link
Contributor

Choose a reason for hiding this comment

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

요 방법이 맞는 거 같기는 한데 한번 확인 부탁드려요!

var body: some View {
VStack {
Spacer()
Expand All @@ -18,6 +20,13 @@ struct MyPageView: View {
.padding(.horizontal, 35)
grayEmptyBar
}
.task {
do {
try await viewModel.fetchUserInfo()
} catch {
CherrishLogger.error(error)
}
}
}
}

Expand All @@ -29,10 +38,10 @@ extension MyPageView {
.frame(width: 48.adjustedW, height: 48.adjustedH)

VStack(alignment: .leading, spacing: 0){
TypographyText("안녕하세요, 김채채 님", style: .title1_sb_18, color: .gray1000)
TypographyText("안녕하세요, \(viewModel.name)님", style: .title1_sb_18, color: .gray1000)
.frame(height: 27.adjustedH)

TypographyText("관리 시작 D + 13", style: .body1_m_14, color: .gray800)
TypographyText("관리 시작 D + \(viewModel.day)", style: .body1_m_14, color: .gray800)
.frame(height: 20.adjustedH)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// MyPageViewModel.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/20/26.
//

import Foundation

final class MyPageViewModel: ObservableObject {
@Published private(set) var name: String = ""
@Published private(set) var day: Int = 1

private let fetchUserInfoUseCase: FetchUserInfoUseCase

init(fetchUserInfoUseCase: FetchUserInfoUseCase) {
self.fetchUserInfoUseCase = fetchUserInfoUseCase
}

@MainActor
func fetchUserInfo() async throws {
let response = try await fetchUserInfoUseCase.execute()
name = response.name
day = response.daysSinceSignup
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,13 @@ final class PresentationDependencyAssembler: DependencyAssembler {
DIContainer.shared.register(type: TreatmentViewModel.self) {
return TreatmentViewModel(fetchTreatmentsUseCase: fetchTreatmentsUseCase)
}

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

DIContainer.shared.register(type: MyPageViewModel.self) {
return MyPageViewModel(fetchUserInfoUseCase: fetchUserInfoUseCase)
}
Comment on lines +75 to +81
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

DI 실패 로그 누락으로 원인 추적이 어려움

fetchUserInfoUseCase가 nil일 때 조용히 반환해 이후 makeMyPageView()에서 fatalError로 이어질 수 있습니다. 다른 DI 실패와 동일하게 로그를 남겨 원인 파악 가능하게 해주세요.

🛠️ 제안 수정안
-        guard let fetchUserInfoUseCase = DIContainer.shared.resolve(type: FetchUserInfoUseCase.self) else {
-            return
-        }
+        guard let fetchUserInfoUseCase = DIContainer.shared.resolve(type: FetchUserInfoUseCase.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 fetchUserInfoUseCase = DIContainer.shared.resolve(type: FetchUserInfoUseCase.self) else {
return
}
DIContainer.shared.register(type: MyPageViewModel.self) {
return MyPageViewModel(fetchUserInfoUseCase: fetchUserInfoUseCase)
}
guard let fetchUserInfoUseCase = DIContainer.shared.resolve(type: FetchUserInfoUseCase.self) else {
CherrishLogger.error(CherrishError.DIFailedError)
return
}
DIContainer.shared.register(type: MyPageViewModel.self) {
return MyPageViewModel(fetchUserInfoUseCase: fetchUserInfoUseCase)
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift`
around lines 60 - 66, The guard early-returns silently when
DIContainer.shared.resolve(type: FetchUserInfoUseCase.self) yields nil, which
later leads to a fatalError in makeMyPageView(); update the guard to log an
error (using the same logger/mechanism used for other DI failures) that includes
the resolved type and context before returning so the DI failure is visible and
traceable; ensure the log references fetchUserInfoUseCase,
DIContainer.shared.resolve, MyPageViewModel and makeMyPageView() to make the
cause clear.

}
}
5 changes: 4 additions & 1 deletion Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ final class ViewFactory: ViewFactoryProtocol {
}

func makeMyPageView() -> MyPageView {
return MyPageView()
guard let viewModel = DIContainer.shared.resolve(type: MyPageViewModel.self) else {
fatalError()
}
return MyPageView(viewModel: viewModel)
}

func makeSelectTreatmentView() -> SelectTreatmentView {
Expand Down