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
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 @@ -10,13 +10,7 @@ import SwiftUI
struct SplashView: View {
@EnvironmentObject private var appCoordinator: AppCoordinator
private let userDefaultService: UserDefaultService = DefaultUserDefaultService()

private let userDefaultService: UserDefaultService

init(userDefaultService: UserDefaultService = DefaultUserDefaultService()) {
self.userDefaultService = userDefaultService
}


var body: some View {
ZStack(alignment: .center) {
LinearGradient(
Expand Down
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