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
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,9 @@ public struct DataSourceDependencyAssembler: DependencyAssemblerProtocol {
DIContainer.shared.register(type: UserDataRepositoryProtocol.self) { _ in
return UserDataRepository()
}

DIContainer.shared.register(type: RecommendedRoutineRepositoryProtocol.self) { _ in
return RecommendedRoutineRepository()
}
}
}
36 changes: 25 additions & 11 deletions Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,56 @@
// RecommendedRoutineDTO.swift
// DataSource
//
// Created by 최정인 on 7/15/25.
// Created by 최정인 on 7/27/25.
//

import Domain

struct RecommendedRoutineListResponseDTO: Decodable {
let recommendedRoutines: [RecommendedRoutineDTO]
}

struct RecommendedRoutineDTO: Decodable {
let id: Int
let routineName: String
let routineDescription: String
let subRoutines: [SubRoutine]
let routineLevel: String?
let subRoutines: [SubRoutineDTO]

enum CodingKeys: String, CodingKey {
case id = "recommendedRoutineId"
case routineName = "recommendedRoutineName"
case routineDescription
case subRoutines = "recommendedSubRoutines"
case routineDescription = "recommendedRoutineDescription"
case routineLevel = "recommendedRoutineLevel"
case subRoutines = "recommendedSubRoutineSearchResult"
}

func toRecommendedRoutineEntity() -> RecommendedRoutineEntity {
func toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity {
var routineCategory: RoutineCategoryType?
if let category {
routineCategory = RoutineCategoryType(rawValue: category)
}

var level: RoutineLevelType?
if let routineLevel {
level = RoutineLevelType(rawValue: routineLevel)
}
return RecommendedRoutineEntity(
id: id,
title: routineName,
description: routineDescription)
description: routineDescription,
category: routineCategory,
level: level,
subRoutines: subRoutines.compactMap({ $0.toSubRoutineEntity() }))
}
Comment on lines +25 to 42
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

엔티티 변환 로직 개선을 검토해주세요.

변환 메서드가 향상되었지만 몇 가지 개선점이 있습니다:

  1. RoutineCategoryType(rawValue:) 초기화가 실패할 수 있으나 이에 대한 처리가 없습니다.
  2. compactMap을 사용하고 있지만 SubRoutineDTO.toSubRoutineEntity()가 실패하지 않는 것 같습니다.

다음과 같이 개선을 제안합니다:

func toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity {
    var routineCategory: RoutineCategoryType?
    if let category {
-       routineCategory = RoutineCategoryType(rawValue: category)
+       routineCategory = RoutineCategoryType(rawValue: category)
+       // 로깅 추가: 유효하지 않은 카테고리인 경우
+       if routineCategory == nil {
+           print("Warning: Invalid category value: \(category)")
+       }
    }

    var level: RoutineLevelType?
    if let routineLevel {
-       level = RoutineLevelType(rawValue: routineLevel)
+       level = RoutineLevelType(rawValue: routineLevel)
+       // 로깅 추가: 유효하지 않은 레벨인 경우
+       if level == nil {
+           print("Warning: Invalid level value: \(routineLevel)")
+       }
    }
    
    return RecommendedRoutineEntity(
        id: id,
        title: routineName,
        description: routineDescription,
        category: routineCategory,
        level: level,
-       subRoutines: subRoutines.compactMap({ $0.toSubRoutineEntity() }))
+       subRoutines: subRoutines.map({ $0.toSubRoutineEntity() }))
}
📝 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 toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity {
var routineCategory: RoutineCategoryType?
if let category {
routineCategory = RoutineCategoryType(rawValue: category)
}
var level: RoutineLevelType?
if let routineLevel {
level = RoutineLevelType(rawValue: routineLevel)
}
return RecommendedRoutineEntity(
id: id,
title: routineName,
description: routineDescription)
description: routineDescription,
category: routineCategory,
level: level,
subRoutines: subRoutines.compactMap({ $0.toSubRoutineEntity() }))
}
func toRecommendedRoutineEntity(category: String? = nil) -> RecommendedRoutineEntity {
var routineCategory: RoutineCategoryType?
if let category {
routineCategory = RoutineCategoryType(rawValue: category)
// 로깅 추가: 유효하지 않은 카테고리인 경우
if routineCategory == nil {
print("Warning: Invalid category value: \(category)")
}
}
var level: RoutineLevelType?
if let routineLevel {
level = RoutineLevelType(rawValue: routineLevel)
// 로깅 추가: 유효하지 않은 레벨인 경우
if level == nil {
print("Warning: Invalid level value: \(routineLevel)")
}
}
return RecommendedRoutineEntity(
id: id,
title: routineName,
description: routineDescription,
category: routineCategory,
level: level,
subRoutines: subRoutines.map({ $0.toSubRoutineEntity() }))
}
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift around lines 25
to 42, the toRecommendedRoutineEntity method does not handle the case where
RoutineCategoryType(rawValue:) initialization might fail, and compactMap is used
unnecessarily if toSubRoutineEntity() cannot fail. Fix this by safely unwrapping
the RoutineCategoryType initialization and handling failure (e.g., defaulting or
returning nil), and replace compactMap with map if toSubRoutineEntity() always
succeeds.

}

struct SubRoutine: Decodable {
struct SubRoutineDTO: Decodable {
let id: Int
let routineName: String

enum CodingKeys: String, CodingKey {
case id = "recommendedSubRoutineId"
case routineName = "recommendedSubRoutineName"
}

func toSubRoutineEntity() -> SubRoutineEntity {
return SubRoutineEntity(id: id, title: routineName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// RecommendedRoutineDictionaryResponseDTO.swift
// DataSource
//
// Created by 최정인 on 7/27/25.
//

struct RecommendedRoutineDictionaryResponseDTO: Decodable {
let recommendedRoutines: [String: [RecommendedRoutineDTO]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// RecommendedRoutineListResponseDTO.swift
// DataSource
//
// Created by 최정인 on 7/15/25.
//

struct RecommendedRoutineListResponseDTO: Decodable {
let recommendedRoutines: [RecommendedRoutineDTO]
}
1 change: 0 additions & 1 deletion Projects/DataSource/Sources/Endpoint/AuthEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// Created by 최정인 on 6/30/25.
//

import Foundation
import Domain

enum AuthEndpoint {
Expand Down
2 changes: 0 additions & 2 deletions Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
// Created by 최정인 on 7/15/25.
//

import Foundation

enum OnboardingEndpoint {
case registerOnboarding(choices: [String: String])
case registerRecommendedRoutine(selectedRoutines: [Int])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// RecommendedRoutineEndpoint.swift
// DataSource
//
// Created by 최정인 on 7/27/25.
//

enum RecommendedRoutineEndpoint {
case fetchRecommendedRoutines
}

extension RecommendedRoutineEndpoint: Endpoint {
var baseURL: String {
return AppProperties.baseURL + "/api/v1/recommend-routines"
}

var path: String {
switch self {
case .fetchRecommendedRoutines: baseURL
}
}

var method: HTTPMethod {
return .get
}

var headers: [String : String] {
let headers: [String: String] = [
"Content-Type": "application/json",
"accept": "*/*"
]
return headers
}

var queryParameters: [String : String] {
return [:]
}

var bodyParameters: [String : Any] {
return [:]
}

var isAuthorized: Bool {
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ final class KeychainStorage {
}

private func baseQuery(for key: String) -> [String: Any] {
var query: [String: Any] = [
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key
Expand Down
6 changes: 3 additions & 3 deletions Projects/DataSource/Sources/Repository/AuthRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
// Created by 최정인 on 6/30/25.
//

import Foundation
import Domain
import Shared
import KakaoSDKUser
import Foundation
import KakaoSDKAuth
import KakaoSDKUser
import Shared

final class AuthRepository: AuthRepositoryProtocol {
private let networkService = NetworkService.shared
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// RecommendedRoutineRepository.swift
// DataSource
//
// Created by 최정인 on 7/27/25.
//

import Domain

final class RecommendedRoutineRepository: RecommendedRoutineRepositoryProtocol {
private let networkService = NetworkService.shared

func fetchRecommendedRoutines() async throws -> [RecommendedRoutineEntity] {
let endpoint = RecommendedRoutineEndpoint.fetchRecommendedRoutines
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDictionaryResponseDTO.self)
else { return [] }

var entities: [RecommendedRoutineEntity] = []
for (category, recommendedRoutines) in response.recommendedRoutines {
let recommendedRoutineEntity = recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity(category: category) })
entities.append(contentsOf: recommendedRoutineEntity)
}

return entities
}
}
7 changes: 7 additions & 0 deletions Projects/Domain/Sources/DomainDependencyAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,12 @@ public struct DomainDependencyAssembler: DependencyAssemblerProtocol {

return OnboardingUseCase(onboardingRepository: onboardingRepository)
}

DIContainer.shared.register(type: RecommendedRoutineUseCaseProtocol.self) { container in
guard let recommendedRoutineRepository = container.resolve(type: RecommendedRoutineRepositoryProtocol.self)
else { fatalError("recommendedRoutineRepository 의존성이 등록되지 않았습니다.") }

return RecommendedRoutineUseCase(recommendedRoutineRepository: recommendedRoutineRepository)
}
}
}
16 changes: 16 additions & 0 deletions Projects/Domain/Sources/Entity/Enum/RoutineCategoryType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// RoutineCategoryType.swift
// Domain
//
// Created by 최정인 on 7/27/25.
//

public enum RoutineCategoryType: String, CaseIterable {
case recommendation = "PERSONALIZED"
case outdoor = "OUTING"
case outdoorReport = "OUTING_REPORT"
case wakeup = "WAKE_UP"
case connection = "CONNECT"
case rest = "REST"
case growth = "GROW"
}
12 changes: 12 additions & 0 deletions Projects/Domain/Sources/Entity/Enum/RoutineLevelType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// RoutineLevelType.swift
// Domain
//
// Created by 최정인 on 7/27/25.
//

public enum RoutineLevelType: String, CaseIterable {
case easy = "LEVEL1"
case normal = "LEVEL2"
case hard = "LEVEL3"
}
21 changes: 20 additions & 1 deletion Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,33 @@ public struct RecommendedRoutineEntity {
public let id: Int
public let title: String
public let description: String
public let category: RoutineCategoryType?
public let level: RoutineLevelType?
public let subRoutines: [SubRoutineEntity]

public init(
id: Int,
title: String,
description: String
description: String,
category: RoutineCategoryType?,
level: RoutineLevelType?,
subRoutines: [SubRoutineEntity]
) {
self.id = id
self.title = title
self.description = description
self.category = category
self.level = level
self.subRoutines = subRoutines
}
}

public struct SubRoutineEntity {
public let id: Int
public let title: String

public init(id: Int, title: String) {
self.id = id
self.title = title
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// RecommendedRoutineRepositoryProtocol.swift
// Domain
//
// Created by 최정인 on 7/27/25.
//

// 추천 루틴에 관련된 데이터를 가져오는 Repository
public protocol RecommendedRoutineRepositoryProtocol {
/// 추천 루틴 데이터를 가져옵니다.
/// - Returns: 추천 루틴 목록
func fetchRecommendedRoutines() async throws -> [RecommendedRoutineEntity]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// RecommendedRoutineUseCaseProtocol.swift
// Domain
//
// Created by 최정인 on 7/27/25.
//

public protocol RecommendedRoutineUseCaseProtocol {
/// 추천 루틴 데이터를 가져옵니다.
/// - Returns: 추천 루틴 목록
func fetchRecommendedRoutines() async throws -> [RecommendedRoutineEntity]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// RecommendedRoutineUseCase.swift
// Domain
//
// Created by 최정인 on 7/27/25.
//

public final class RecommendedRoutineUseCase: RecommendedRoutineUseCaseProtocol {
private let recommendedRoutineRepository: RecommendedRoutineRepositoryProtocol

public init(recommendedRoutineRepository: RecommendedRoutineRepositoryProtocol) {
self.recommendedRoutineRepository = recommendedRoutineRepository
}

public func fetchRecommendedRoutines() async throws -> [RecommendedRoutineEntity] {
let recommendedRoutines = try await recommendedRoutineRepository.fetchRecommendedRoutines()
return recommendedRoutines
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "add_routine_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "add_routine_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "add_routine_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading