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
11 changes: 9 additions & 2 deletions Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ final class DataDependencyAssembler: DependencyAssembler {
self.networkService = DefaultNetworkService()
self.userDefaultService = DefaultUserDefaultService()
}

func assemble() {
DIContainer.shared.register(type: CalendarInterface.self) {
return DefaultCalendarRepository(
networkService: self.networkService,
userDefaultService: self.userDefaultService
)
}

DIContainer.shared.register(type: HomeInterface.self) {
return DefaultHomeRepository(
networkService: self.networkService,
Expand All @@ -41,6 +41,13 @@ final class DataDependencyAssembler: DependencyAssembler {
DIContainer.shared.register(type: TreatmentInterface.self) {
return DefaultTreatmentRepository(networkService: self.networkService, userDefaultService: self.userDefaultService)
}

DIContainer.shared.register(type: DemoInterface.self) {
return DefaultDemoRepository(
networkService: self.networkService,
userDefaultService: self.userDefaultService
)
}

DIContainer.shared.register(type: MyPageInterface.self) {
return DefaultMyPageRepository(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// FetchChallengesResponseDTO.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/21/26.
//

import Foundation

struct FetchChallengesResponseDTO: Decodable {
let challengeId: Int
let title: String
let currentDay: Int
let progressPercentage: Int
let cherryLevel: Int
let cherryLevelName: String
let progressToNextLevel: Double
let remainingRoutinesToNextLevel: Int
let todayRoutines: [RoutineResponseDTO]
let cheeringMessage: String
}

struct RoutineResponseDTO: Decodable {
let routineId: Int
let name: String
let scheduledDate: String
let isComplete: Bool
}

extension FetchChallengesResponseDTO {
func toEntity() -> ChallengeEntity {
.init(
challengeID: challengeId,
title: title,
currentDay: currentDay,
progressPercentage: progressPercentage,
cherryLevel: cherryLevel,
cherryLevelName: cherryLevelName,
progressToNextLevel: progressToNextLevel,
remainingRoutinesToNextLevel: remainingRoutinesToNextLevel,
todayRoutines: todayRoutines.map { $0.toEntity() }
)
}
}

extension RoutineResponseDTO {
func toEntity() -> RoutineEntity {
.init(
routineID: routineId,
name: name,
isComplete: isComplete
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// RoutineToggleResponseDTO.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/21/26.
//

import Foundation

struct RoutineToggleResponseDTO: Decodable {
let routineId: Int
let name: String
let isComplete: Bool
}

extension RoutineToggleResponseDTO {
func toEntity() -> RoutineEntity {
.init(
routineID: routineId,
name: name,
isComplete: isComplete
)
}
}
72 changes: 72 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/DemoAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// DemoAPI.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/21/26.
//

import Foundation

import Alamofire

enum DemoAPI {
case fetchChallenges(userID: Int)
case advance(userID: Int)
case routineToggle(userID: Int, routineID: Int)
}

extension DemoAPI: EndPoint {
var basePath: String {
"/api/demo/challenges"
}

var path: String {
switch self {
case .fetchChallenges:
return ""
case .advance:
return "/advance-day"
case .routineToggle(_, let routineID):
return "/routines/\(routineID)/toggle"
}
}

var method: Alamofire.HTTPMethod {
switch self {
case .fetchChallenges:
return .get
case .advance:
return .post
case .routineToggle(_, let routineID):
return .patch
}
}

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

var parameterEncoding: any Alamofire.ParameterEncoding {
switch self {
case .fetchChallenges:
return URLEncoding.default
case .advance, .routineToggle:
return JSONEncoding.default
}
}

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

var bodyParameters: Alamofire.Parameters? {
return nil
}


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

import Foundation

struct DefaultDemoRepository: DemoInterface {
private let networkService: NetworkService
private let userDefaultService: UserDefaultService

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

func fetchChallenges() async throws -> ChallengeEntity {
let userID: Int = userDefaultService.load(key: .userID) ?? 1
let response = try await networkService.request(
DemoAPI.fetchChallenges(userID: userID),
decodingType: FetchChallengesResponseDTO.self
)

return response.toEntity()
}

func advance() async throws -> ChallengeEntity {
let userID: Int = userDefaultService.load(key: .userID) ?? 1
let response = try await networkService.request(
DemoAPI.advance(userID: userID),
decodingType: FetchChallengesResponseDTO.self
)
return response.toEntity()
}

func toggleRoutine(routineID: Int) async throws -> RoutineEntity {
let userID: Int = userDefaultService.load(key: .userID) ?? 1
let response = try await networkService.request(
DemoAPI.routineToggle(userID: userID, routineID: routineID),
decodingType: RoutineToggleResponseDTO.self
)
return response.toEntity()
Comment on lines +22 to +47
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 사용은 위험합니다.
유저ID가 없을 때 임의 값으로 호출하면 타 사용자 데이터 조회/변경 가능성이 있습니다. 누락 시 에러로 처리하도록 변경하세요.

🔧 수정 제안
+enum DemoRepositoryError: Error {
+    case missingUserID
+}
+
 struct DefaultDemoRepository: DemoInterface {
     private let networkService: NetworkService
     private let userDefaultService: UserDefaultService
@@
     func fetchChallenges() async throws -> ChallengeEntity {
-        let userID: Int = userDefaultService.load(key: .userID) ?? 1
+        guard let userID: Int = userDefaultService.load(key: .userID) else {
+            throw DemoRepositoryError.missingUserID
+        }
         let response = try await networkService.request(
             DemoAPI.fetchChallenges(userID: userID),
             decodingType: FetchChallengesResponseDTO.self
         )
         
         return response.toEntity()
     }
     
     func advance() async throws -> ChallengeEntity {
-        let userID: Int = userDefaultService.load(key: .userID) ?? 1
+        guard let userID: Int = userDefaultService.load(key: .userID) else {
+            throw DemoRepositoryError.missingUserID
+        }
         let response = try await networkService.request(
             DemoAPI.advance(userID: userID),
             decodingType: FetchChallengesResponseDTO.self
         )
         return response.toEntity()
     }
 
     func toggleRoutine(routineID: Int) async throws -> RoutineEntity {
-        let userID: Int = userDefaultService.load(key: .userID) ?? 1
+        guard let userID: Int = userDefaultService.load(key: .userID) else {
+            throw DemoRepositoryError.missingUserID
+        }
         let response = try await networkService.request(
             DemoAPI.routineToggle(userID: userID, routineID: routineID),
             decodingType: RoutineToggleResponseDTO.self
         )
         return response.toEntity()
     }
 }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/DemoRepository.swift` around lines
22 - 47, The code currently falls back to userID = 1 in fetchChallenges(),
advance(), and toggleRoutine() by using userDefaultService.load(key: .userID) ??
1 which can cause accidental access to other users' data; change these methods
to explicitly handle a missing userID by failing early: retrieve userID via
userDefaultService.load(key: .userID), if nil throw a clear domain error (e.g.,
create/throw a MissingUserError or use an existing RepositoryError) instead of
defaulting to 1, and then pass the non-optional userID into
DemoAPI.fetchChallenges, DemoAPI.advance, and DemoAPI.routineToggle; update
callers to handle the thrown error as needed.

}
}
18 changes: 17 additions & 1 deletion Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,26 @@ final class DomainDependencyAssembler: DependencyAssembler {
guard let treatmentRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) else {
return
}

DIContainer.shared.register(type: FetchTreatmentCategoriesUseCase.self) {
return DefaultFetchTreatmentCategoriesUseCase(repository: treatmentRepository)
}

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

DIContainer.shared.register(type: FetchChallengeUseCase.self) {
return DefaultFetchChallengeUseCase(repository: demoRepository)
}

DIContainer.shared.register(type: ToggleRoutineUseCase.self) {
return DefaultToggleRoutineUseCase(repository: demoRepository)
}

DIContainer.shared.register(type: AdvanceDayUseCase.self) {
return DefaultAdvanceDayUseCase(repository: demoRepository)
}
Comment on lines +68 to +82
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

DemoInterface 미해결 시 이후 DI 등록이 끊기는 문제

Line 68-70의 guard에서 실패하면 FetchTreatmentsUseCase, FetchUserInfoUseCase 등록까지 건너뛰게 됩니다. 데모 저장소 미등록이 전체 도메인 조립 실패로 이어질 수 있어 기능 영향이 큽니다. 데모 의존성은 옵션으로 처리하고 나머지 등록은 계속되도록 바꾸는 게 안전합니다.

✅ 제안 수정 (데모만 선택적으로 등록)
-        guard let demoRepository = DIContainer.shared.resolve(type: DemoInterface.self) else {
-            return
-        }
-
-        DIContainer.shared.register(type: FetchChallengeUseCase.self) {
-            return DefaultFetchChallengeUseCase(repository: demoRepository)
-        }
-
-        DIContainer.shared.register(type: ToggleRoutineUseCase.self) {
-            return DefaultToggleRoutineUseCase(repository: demoRepository)
-        }
-
-        DIContainer.shared.register(type: AdvanceDayUseCase.self) {
-            return DefaultAdvanceDayUseCase(repository: demoRepository)
-        }
+        if let demoRepository = DIContainer.shared.resolve(type: DemoInterface.self) {
+            DIContainer.shared.register(type: FetchChallengeUseCase.self) {
+                return DefaultFetchChallengeUseCase(repository: demoRepository)
+            }
+
+            DIContainer.shared.register(type: ToggleRoutineUseCase.self) {
+                return DefaultToggleRoutineUseCase(repository: demoRepository)
+            }
+
+            DIContainer.shared.register(type: AdvanceDayUseCase.self) {
+                return DefaultAdvanceDayUseCase(repository: demoRepository)
+            }
+        }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift` around
lines 68 - 82, The current guard on DIContainer.shared.resolve(type:
DemoInterface.self) aborts the rest of DomainDependencyAssembler registrations
if the demo repository is missing; change it to optional let demoRepository =
DIContainer.shared.resolve(type: DemoInterface.self) and only use demoRepository
when registering Demo-dependent use cases (DefaultFetchChallengeUseCase,
DefaultToggleRoutineUseCase, DefaultAdvanceDayUseCase), while allowing the
subsequent registrations (e.g., FetchTreatmentsUseCase, FetchUserInfoUseCase and
any others) to proceed regardless; ensure each DIContainer.shared.register
closure captures demoRepository? safely (optional) so missing demo dependency no
longer blocks other registrations.


DIContainer.shared.register(type: FetchTreatmentsUseCase.self) {
return DefaultFetchTreatmentsUseCase(repository: treatmentRepository)
Expand Down
14 changes: 14 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/Interface/DemoInterface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// DemoInterface.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/21/26.
//

import Foundation

protocol DemoInterface {
func fetchChallenges() async throws -> ChallengeEntity
func advance() async throws -> ChallengeEntity
func toggleRoutine(routineID: Int) async throws -> RoutineEntity
}
20 changes: 20 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/ChallengeEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// ChallengeEntity.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/21/26.
//

import Foundation

struct ChallengeEntity {
let challengeID: Int
let title: String
let currentDay: Int
let progressPercentage: Int
let cherryLevel: Int
let cherryLevelName: String
let progressToNextLevel: Double
let remainingRoutinesToNextLevel: Int
let todayRoutines: [RoutineEntity]
}
14 changes: 14 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/RoutineEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// RoutineEntity.swift
// Cherrish-iOS
//
// Created by 이나연 on 1/21/26.
//

import Foundation

struct RoutineEntity {
let routineID: Int
let name: String
let isComplete: Bool
}
24 changes: 24 additions & 0 deletions Cherrish-iOS/Cherrish-iOS/Domain/UseCase/AdvanceDayUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// AdvanceDayUseCase.swift
// Cherrish-iOS
//
// Created by 송성용 on 1/21/26.
//

import Foundation

protocol AdvanceDayUseCase {
func execute() async throws -> ChallengeEntity
}

struct DefaultAdvanceDayUseCase: AdvanceDayUseCase {
private let repository: DemoInterface

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

func execute() async throws -> ChallengeEntity {
try await repository.advance()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// FetchChallengeUseCase.swift
// Cherrish-iOS
//
// Created by 송성용 on 1/21/26.
//

import Foundation

protocol FetchChallengeUseCase {
func execute() async throws -> ChallengeEntity
}

struct DefaultFetchChallengeUseCase: FetchChallengeUseCase {
private let repository: DemoInterface

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

func execute() async throws -> ChallengeEntity {
try await repository.fetchChallenges()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// ToggleRoutineUseCase.swift
// Cherrish-iOS
//
// Created by 송성용 on 1/21/26.
//

import Foundation

protocol ToggleRoutineUseCase {
func execute(routineID: Int) async throws -> RoutineEntity
}

struct DefaultToggleRoutineUseCase: ToggleRoutineUseCase {
private let repository: DemoInterface

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

func execute(routineID: Int) async throws -> RoutineEntity {
try await repository.toggleRoutine(routineID: routineID)
}
}
Loading