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
18 changes: 18 additions & 0 deletions Projects/DataSource/Sources/Common/Extension/Encodable+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// Encodable+.swift
// DataSource
//
// Created by 이동현 on 8/3/25.
//

import Foundation

extension Encodable {
var dictionary: [String: Any] {
guard
let data = try? JSONEncoder().encode(self),
let dictionary = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else { return [:] }
return dictionary
}
}
13 changes: 13 additions & 0 deletions Projects/DataSource/Sources/DTO/RoutineCreationDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// RoutienCreationDTO.swift
// DataSource
//
// Created by 이동현 on 8/3/25.
//

struct RoutineCreationDTO: Codable {
let routineName: String
let repeatDay: [String]
let executionTime: String
let subRoutineName: [String]
}
4 changes: 3 additions & 1 deletion Projects/DataSource/Sources/DTO/RoutineResponseDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ struct RoutineResponseDTO: Decodable {

extension RoutineResponseDTO {
func toRoutineEntity() -> RoutineEntity {
let sortedSubRoutinesDTO = subRoutineSearchResultDto.sorted { $0.sortOrder < $1.sortOrder }

return RoutineEntity(
routineId: routineId,
historySeq: historySeq,
routineName: routineName,
repeatDay: repeatDay,
executionTime: executionTime,
subRoutineSearchResultDto: subRoutineSearchResultDto.map({ $0.toSubRoutineEntity() }),
subRoutineSearchResultDto: sortedSubRoutinesDTO.map({ $0.toSubRoutineEntity() }),
modifiedYn: modifiedYn,
routineCompletionId: routineCompletionId,
completeYn: completeYn,
Expand Down
14 changes: 14 additions & 0 deletions Projects/DataSource/Sources/DTO/RoutineUpdateDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// RoutineUpdateDTO.swift
// DataSource
//
// Created by 이동현 on 8/3/25.
//

struct RoutineUpdateDTO: Codable {
let routineId: String
let routineName: String
let repeatDay: [String]
let executionTime: String
let subRoutineInfos: [SubRoutineUpdateDTO]
}
12 changes: 12 additions & 0 deletions Projects/DataSource/Sources/DTO/SubRoutineUpdateDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// SubRoutineUpdateDTO.swift
// DataSource
//
// Created by 이동현 on 8/3/25.
//

struct SubRoutineUpdateDTO: Codable {
let subRoutineId: String?
let subRoutineName: String?
let sortOrder: Int?
}
26 changes: 23 additions & 3 deletions Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
//

enum RoutineEndpoint {
case createRoutine(routine: RoutineCreationDTO)
case fetchRoutine(routineId: String)
case fetchRoutines(startDate: String, endDate: String)
case updateRoutine(routine: RoutineUpdateDTO)
}

extension RoutineEndpoint: Endpoint {
Expand All @@ -16,13 +19,21 @@ extension RoutineEndpoint: Endpoint {

var path: String {
switch self {
case .fetchRoutines: baseURL
case .fetchRoutine(let routineId):
"\(baseURL)/\(routineId)"
default:
baseURL
}
}

var method: HTTPMethod {
switch self {
case .fetchRoutines: .get
case .createRoutine:
.post
case .fetchRoutine, .fetchRoutines:
.get
case .updateRoutine:
.patch
}
}

Expand All @@ -40,11 +51,20 @@ extension RoutineEndpoint: Endpoint {
return [
"startDate": startDate,
"endDate": endDate]
default:
return [:]
}
}

var bodyParameters: [String : Any] {
return [:]
switch self {
case .createRoutine(let routine):
return routine.dictionary
case .updateRoutine(let routine):
return routine.dictionary
default:
return [:]
}
}

var isAuthorized: Bool {
Expand Down
41 changes: 41 additions & 0 deletions Projects/DataSource/Sources/Repository/RoutineRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ import Domain
final class RoutineRepository: RoutineRepositoryProtocol {
private let networkService = NetworkService.shared

func createRoutine(routineSummary: RoutineSummaryEntity, subRoutineSummaries: [SubRoutineSummaryEntity]) async throws {
let subRoutineNames = subRoutineSummaries.compactMap { $0.subRoutineName }

let routineCreationDTO = RoutineCreationDTO(
routineName: routineSummary.routineName,
repeatDay: routineSummary.repeatDay.map { $0.rawValue },
executionTime: routineSummary.executionTime,
subRoutineName: subRoutineNames)
let endpoint = RoutineEndpoint.createRoutine(routine: routineCreationDTO)

_ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)
}

func fetchRoutine(routineId: String) async throws -> RoutineEntity? {
let endpoint = RoutineEndpoint.fetchRoutine(routineId: routineId)
guard let response = try await networkService.request(endpoint: endpoint, type: RoutineResponseDTO.self) else { return nil }

return response.toRoutineEntity()
}

func fetchRoutines(from startDate: String, to endDate: String) async throws -> [String: [RoutineEntity]] {
let endpoint = RoutineEndpoint.fetchRoutines(startDate: startDate, endDate: endDate)
guard let response = try await networkService.request(endpoint: endpoint, type: RoutineDictionaryDTO.self)
Expand All @@ -21,4 +41,25 @@ final class RoutineRepository: RoutineRepositoryProtocol {
}
return result
}

func updateRoutine(routineSummary: RoutineSummaryEntity, subRoutineSummaries: [SubRoutineSummaryEntity]) async throws {
guard let routineId = routineSummary.routineId else { return }
Comment on lines +45 to +46
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 3, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

routineId가 nil일 때 조용히 실패하는 문제

현재 routineId가 nil일 경우 아무런 오류 없이 메서드가 종료됩니다. 이는 호출자가 업데이트 실패를 인지하지 못하게 할 수 있습니다.

다음과 같이 오류를 던지는 것을 고려해보세요:

-guard let routineId = routineSummary.routineId else { return }
+guard let routineId = routineSummary.routineId else {
+    throw RoutineRepositoryError.missingRoutineId
+}

또는 메서드 시그니처를 변경하여 옵셔널 결과를 반환하거나, 완료 상태를 나타내는 Bool을 반환하는 것도 고려해볼 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/RoutineRepository.swift at lines
45-46, the method updateRoutine silently returns when routineId is nil, causing
callers to miss update failures. Modify the method to throw a specific error
when routineId is nil instead of returning quietly. Define a suitable error type
for this case and update the method signature to reflect that it can throw. This
ensures callers are properly informed of the failure.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

추후 디버깅을 위해서라도 추가하는 것이 좋을 것 같습니다. 감사합니다~!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@taipaise 동의해주셔서 감사합니다! 명시적인 에러 처리는 디버깅 시 문제를 빠르게 파악하는 데 정말 도움이 됩니다.

구현 시 다음과 같은 형태로 진행하시면 됩니다:

enum RoutineRepositoryError: Error {
    case missingRoutineId
}

func updateRoutine(routineSummary: RoutineSummaryEntity, subRoutineSummaries: [SubRoutineSummaryEntity]) async throws {
    guard let routineId = routineSummary.routineId else {
        throw RoutineRepositoryError.missingRoutineId
    }
    // 기존 로직 계속...
}

이렇게 하면 호출하는 쪽에서도 업데이트 실패를 명확히 인지할 수 있어 더 안정적인 코드가 될 것입니다.

  /|   /|  
 ( :v:  )
  |(_)|
 /     \
/       \

✏️ Learnings added
Learnt from: taipaise
PR: YAPP-Github/Bitnagil-iOS#33
File: Projects/DataSource/Sources/Repository/RoutineRepository.swift:45-46
Timestamp: 2025-08-04T23:43:28.481Z
Learning: taipaise는 디버깅을 위해 명시적인 에러 처리를 선호함. nil 체크 시 조용히 실패하는 것보다 에러를 던지는 방식을 추후 디버깅에 도움이 된다고 생각함.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!


let subRoutineDTO = subRoutineSummaries.map {
SubRoutineUpdateDTO(
subRoutineId: $0.subRoutineId,
subRoutineName: $0.subRoutineName,
sortOrder: $0.sortOrder)
}

let routineUpdateDTO = RoutineUpdateDTO(
routineId: routineId,
routineName: routineSummary.routineName,
repeatDay: routineSummary.repeatDay.map { $0.rawValue },
executionTime: routineSummary.executionTime,
subRoutineInfos: subRoutineDTO)
let endpoint = RoutineEndpoint.updateRoutine(routine: routineUpdateDTO)

_ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)
}
}
16 changes: 16 additions & 0 deletions Projects/Domain/Sources/Entity/Enum/WeekType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Week.swift
// Domain
//
// Created by 이동현 on 8/3/25.
//

public enum WeekType: String, CaseIterable {
case monday = "MONDAY"
case tuesday = "TUESDAY"
case wednesday = "WEDNESDAY"
case thursday = "THURSDAY"
case friday = "FRIDAY"
case saturday = "SATURDAY"
case sunday = "SUNDAY"
}
Comment on lines +8 to +16
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

요 WeekType이 Public인 이유가 있을까용 ???
Review Note에서 말씀해주신 Entity <-> DTO를 Repository에서 해야 한다 ! 라는 것에서 일단 Public으로 열어두신 것인지 궁금함니다 !!!

또한 Presentation에 Week과 WeekType이 비슷한 역할을 한다고 생각하는데
그럼 차라리 Domain의 WeekType만 public으로 남겨두고 Presentation에서 필요한 값만 extension 해서 사용하는 것은 어떻게 생각하시나요 ?? (물론 배포 먼저)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

이 부분에 대해 리뷰 노트를 남기는 것을 깜빡했습니다!!! 역시 조이네요.!.!
말씀해주신대로 역할이 200% 동일한 녀석입니다.. 요 녀석을 추가한 이유는, 엔티티 내에서 사용하는 repeatDay를 문자열이 아닌 제한된 네임스페이스 안에서 다루는 것이 조금 더 적절하지 않을까하여 추가했습니다! 하지만 Presentation에서 사용하는 녀석과 역할이 겹쳐버려서,, 마음같아서는 Presentation의 Week을 도메인으로 옮기고 싶었지만!! 관련 내용에 대해 말씀드리고 진행하는 것이 좋을 것 같다 생각했습니다!

제안해주신 extension으로 활용하는 것은 좋아 입니다!! (생각지도 못한 방법 bbb)

10 changes: 6 additions & 4 deletions Projects/Domain/Sources/Entity/RoutineEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
//

public struct RoutineEntity {
public let routineId: String
public let routineId: String?
public let historySeq: Int
public let routineName: String
public let repeatDay: [String]
public let repeatDay: [WeekType]
public let executionTime: String
public let subRoutineSearchResultDto: [SubRoutineEntity]
public let modifiedYn: Bool
Expand All @@ -18,7 +18,7 @@ public struct RoutineEntity {
public let routineType: String

public init(
routineId: String,
routineId: String?,
historySeq: Int,
routineName: String,
repeatDay: [String]?,
Expand All @@ -29,10 +29,12 @@ public struct RoutineEntity {
completeYn: Bool,
routineType: String
) {
let weekType: [WeekType] = repeatDay?.compactMap(WeekType.init(rawValue:)) ?? []

self.routineId = routineId
self.historySeq = historySeq
self.routineName = routineName
self.repeatDay = repeatDay ?? []
self.repeatDay = weekType
self.executionTime = executionTime
self.subRoutineSearchResultDto = subRoutineSearchResultDto
self.modifiedYn = modifiedYn
Expand Down
27 changes: 27 additions & 0 deletions Projects/Domain/Sources/Entity/RoutineSummaryEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// RoutineSummaryEntity.swift
// Domain
//
// Created by 이동현 on 8/3/25.
//

public struct RoutineSummaryEntity {
public let routineId: String?
public let routineName: String
public let repeatDay: [WeekType]
public let executionTime: String

public init(
routineId: String?,
routineName: String,
repeatDay: [String]?,
executionTime: String
) {
let weekType: [WeekType] = repeatDay?.compactMap(WeekType.init(rawValue:)) ?? []

self.routineId = routineId
self.routineName = routineName
self.repeatDay = weekType
self.executionTime = executionTime
}
}
2 changes: 1 addition & 1 deletion Projects/Domain/Sources/Entity/SubRoutineEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

public struct SubRoutineEntity: Decodable {
public let subRoutineId: String
public let subRoutineId: String?
public let historySeq: Int
public let subRoutineName: String
public let modifiedYn: Bool
Expand Down
22 changes: 22 additions & 0 deletions Projects/Domain/Sources/Entity/SubRoutineSummaryEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// SubRoutineSummaryEntity.swift
// Domain
//
// Created by 이동현 on 8/3/25.
//

public struct SubRoutineSummaryEntity: Decodable, Hashable {
public let subRoutineId: String?
public let subRoutineName: String?
public let sortOrder: Int?

public init(
subRoutineId: String?,
subRoutineName: String?,
sortOrder: Int?
) {
self.subRoutineId = subRoutineId
self.subRoutineName = subRoutineName
self.sortOrder = sortOrder
}
}
Comment on lines +8 to +22
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 3, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

엔티티 설계에서 비즈니스 규칙을 고려한 개선이 필요합니다.

현재 모든 프로퍼티가 옵셔널로 정의되어 있어 PR 목표에서 언급된 API DTO와의 tight coupling 우려가 현실화되고 있습니다. 도메인 관점에서 다음 사항들을 검토해보세요:

  1. subRoutineName은 필수값이어야 하지 않을까요?
  2. sortOrder는 기본값(예: 0)을 가져야 하지 않을까요?

도메인 중심의 설계로 개선을 제안합니다:

public struct SubRoutineSummaryEntity: Decodable, Hashable {
    public let subRoutineId: String?
-   public let subRoutineName: String?
+   public let subRoutineName: String
-   public let sortOrder: Int?
+   public let sortOrder: Int

    public init(
        subRoutineId: String?,
-       subRoutineName: String?,
+       subRoutineName: String,
-       sortOrder: Int?
+       sortOrder: Int = 0
    ) {
        self.subRoutineId = subRoutineId
        self.subRoutineName = subRoutineName
        self.sortOrder = sortOrder
    }
}
📝 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
public struct SubRoutineSummaryEntity: Decodable, Hashable {
public let subRoutineId: String?
public let subRoutineName: String?
public let sortOrder: Int?
public init(
subRoutineId: String?,
subRoutineName: String?,
sortOrder: Int?
) {
self.subRoutineId = subRoutineId
self.subRoutineName = subRoutineName
self.sortOrder = sortOrder
}
}
public struct SubRoutineSummaryEntity: Decodable, Hashable {
public let subRoutineId: String?
public let subRoutineName: String
public let sortOrder: Int
public init(
subRoutineId: String?,
subRoutineName: String,
sortOrder: Int = 0
) {
self.subRoutineId = subRoutineId
self.subRoutineName = subRoutineName
self.sortOrder = sortOrder
}
}
🤖 Prompt for AI Agents
In Projects/Domain/Sources/Entity/SubRoutineSummaryEntity.swift lines 8 to 22,
the properties are all optional, causing tight coupling with the API DTO and
weakening domain integrity. Change subRoutineName to a non-optional property to
enforce it as a required field, and assign a default value (e.g., 0) to
sortOrder to avoid optionality. Adjust the initializer accordingly to reflect
these changes and ensure domain-driven design principles are followed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

관련해서 회의 진행하고 리팩터링 예정입니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@taipaise 좋은 접근 방식이네요! 팀과 함께 엔티티 설계에 대해 논의하고 체계적으로 리팩터링을 진행하시는 것이 현명한 판단입니다.

도메인 무결성과 API와의 결합도 문제를 함께 고려하시면서 진행하시길 바랍니다. 추후 리팩터링 결과가 궁금하네요! 🚀


🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

7 changes: 7 additions & 0 deletions Projects/Domain/Sources/Entity/Untitled.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//
// Untitled.swift
// Domain
//
// Created by 이동현 on 8/3/25.
//

Comment on lines +1 to +7
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

엠티 파일 발견 !!!!!!!!!!!!!!! 🚨🚨🚨🚨🚨🚨🚨🚨🚨

Copy link
Copy Markdown
Collaborator Author

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 @@ -7,9 +7,27 @@

// 루틴 관련 로직(조회, 완료, 등록, 삭제 등)을 수행하는 Repository
public protocol RoutineRepositoryProtocol {
/// 루틴을 조회합니다. (기간)
/// 루틴을 생성합니다.
/// - Parameters:
/// - routineSummary: 루틴 요약 정보
/// - subRoutineSummaries: 서브 루틴 요약 정보 배열
func createRoutine(routineSummary: RoutineSummaryEntity, subRoutineSummaries: [SubRoutineSummaryEntity]) async throws

/// 루틴을 조회합니다.
/// - Parameter routineId: 조회할 루틴 id
/// - Returns: 조회된 루틴
func fetchRoutine(routineId: String) async throws -> RoutineEntity?

/// 루틴 목록을 조회합니다. (기간)
/// - Parameters:
/// - startDate: 조회 시작 날짜
/// - endDate: 조회 종료 날짜
func fetchRoutines(from startDate: String, to endDate: String) async throws -> [String: [RoutineEntity]]


/// 루틴을 수정합니다.
/// - Parameters:
/// - routineSummary: 루틴 요약 정보
/// - subRoutineSummaries: 서브 루틴 요약 정보 배열
func updateRoutine(routineSummary: RoutineSummaryEntity, subRoutineSummaries: [SubRoutineSummaryEntity]) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,13 @@
import Foundation

public protocol RoutineUseCaseProtocol {
func fetchRoutine(routineId: String) async throws -> RoutineEntity?

func fetchRoutines(startDate: Date, endDate: Date) async throws -> [String: [RoutineEntity]]

func saveRoutine(
routineSummary: RoutineSummaryEntity,
subRoutineSummaries: [SubRoutineSummaryEntity],
deletedSubRoutineSummaries: [SubRoutineSummaryEntity]
) async throws
}
Loading
Loading