Skip to content

Commit 5acab83

Browse files
authored
[#474] Todo 도메인 모델에서 옵셔널이 포함된 프로퍼티를 제거한다 (#520)
* refactor: TodoDraft 생성 모델 분리 * refactor: Todo 생성 수정 저장 흐름 분리 * refactor: TodoDraft 변경 감지 단순화 * refactor: Todo 태그 배열 변환 방식 정리
1 parent 42e9809 commit 5acab83

23 files changed

Lines changed: 242 additions & 110 deletions

Application/DevLogData/Sources/Mapper/TodoMapping.swift

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,39 @@ import DevLogCore
99
import DevLogDomain
1010

1111
public extension TodoRequest {
12-
static func fromDomain(_ entity: Todo) -> Self {
12+
static func fromDomain(_ todo: Todo) -> Self {
13+
TodoRequest(
14+
id: todo.id,
15+
isPinned: todo.isPinned,
16+
isCompleted: todo.isCompleted,
17+
isChecked: todo.isChecked,
18+
title: todo.title,
19+
content: todo.content,
20+
createdAt: todo.createdAt,
21+
updatedAt: todo.updatedAt,
22+
completedAt: todo.completedAt,
23+
deletedAt: todo.deletedAt,
24+
dueDate: todo.dueDate,
25+
tags: todo.tags,
26+
category: todo.category.storageValue
27+
)
28+
}
29+
30+
static func fromDomain(_ todoDraft: TodoDraft) -> Self {
1331
TodoRequest(
14-
id: entity.id,
15-
isPinned: entity.isPinned,
16-
isCompleted: entity.isCompleted,
17-
isChecked: entity.isChecked,
18-
title: entity.title,
19-
content: entity.content,
20-
createdAt: entity.createdAt,
21-
updatedAt: entity.updatedAt,
22-
completedAt: entity.completedAt,
23-
deletedAt: entity.deletedAt,
24-
dueDate: entity.dueDate,
25-
tags: entity.tags,
26-
category: entity.category.storageValue
32+
id: todoDraft.id,
33+
isPinned: todoDraft.isPinned,
34+
isCompleted: todoDraft.isCompleted,
35+
isChecked: todoDraft.isChecked,
36+
title: todoDraft.title,
37+
content: todoDraft.content,
38+
createdAt: todoDraft.createdAt,
39+
updatedAt: todoDraft.updatedAt,
40+
completedAt: todoDraft.completedAt,
41+
deletedAt: nil,
42+
dueDate: todoDraft.dueDate,
43+
tags: todoDraft.tags,
44+
category: todoDraft.category.storageValue
2745
)
2846
}
2947
}

Application/DevLogData/Sources/Repository/TodoRepositoryImpl.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,18 @@ final class TodoRepositoryImpl: TodoRepository {
105105
}
106106

107107
func upsertTodo(_ todo: Todo) async throws {
108-
let request = TodoRequest.fromDomain(todo)
108+
let todoRequest = TodoRequest.fromDomain(todo)
109+
try await upsertTodo(todoRequest)
110+
}
111+
112+
func upsertTodo(_ todoDraft: TodoDraft) async throws {
113+
let todoRequest = TodoRequest.fromDomain(todoDraft)
114+
try await upsertTodo(todoRequest)
115+
}
116+
117+
private func upsertTodo(_ todoRequest: TodoRequest) async throws {
109118
do {
110-
try await todoService.upsertTodo(request: request)
119+
try await todoService.upsertTodo(request: todoRequest)
111120
widgetSyncEventBus.publish(.syncRequested)
112121
} catch {
113122
throw error.toDomain()

Application/DevLogData/Tests/Widget/WidgetSyncEventHandlerTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ private actor WidgetSyncTodoRepositorySpy: TodoRepository {
232232
throw WidgetSyncTodoRepositorySpyError.unexpectedCall
233233
}
234234

235+
func upsertTodo(_ todoDraft: TodoDraft) async throws {
236+
throw WidgetSyncTodoRepositorySpyError.unexpectedCall
237+
}
238+
235239
func deleteTodo(_ todoId: String) async throws {
236240
throw WidgetSyncTodoRepositorySpyError.unexpectedCall
237241
}

Application/DevLogDomain/DevLogDomain.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
5F978D87E2642CFA203A01EB /* FetchTodayDisplayOptionsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9EFA52FBE798894A263625 /* FetchTodayDisplayOptionsUseCase.swift */; };
5151
60B31EFA1482AD2F1A83B13F /* FetchPushNotificationsUseCaseImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAB919C7D411DB4D9168030 /* FetchPushNotificationsUseCaseImpl.swift */; };
5252
6437BF9BCF3C6702C627B7AC /* FetchHeatmapActivityTypesUseCaseImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C878DA61089719667B00EE1 /* FetchHeatmapActivityTypesUseCaseImpl.swift */; };
53+
64F70B07260067A13C7F0FE0 /* TodoDraft.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D715313032680FBCAEC3272 /* TodoDraft.swift */; };
5354
6669BB8F446C8A59B4EDEB82 /* ObserveUnreadPushCountUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6BC92678E2D3F131E95AA44 /* ObserveUnreadPushCountUseCase.swift */; };
5455
6994DB7AC479B55D96759204 /* SystemTodoCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC9E59B49502EBE884E79F7 /* SystemTodoCategory.swift */; };
5556
6A1A2DDE4A21808768208B29 /* UpdatePushSettingsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43411396CEB5611FD6DC065 /* UpdatePushSettingsUseCase.swift */; };
@@ -167,6 +168,7 @@
167168
0919CFDC9E74DC60E2EA82DA /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
168169
0C2E5255AF2FDDCCF84C8B1E /* UpdatePushSettingsUseCaseImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatePushSettingsUseCaseImpl.swift; sourceTree = "<group>"; };
169170
0C449AA008FB11BE56D5339B /* UpdateRecentSearchQueriesUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateRecentSearchQueriesUseCase.swift; sourceTree = "<group>"; };
171+
0D715313032680FBCAEC3272 /* TodoDraft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDraft.swift; sourceTree = "<group>"; };
170172
0DE4C7B87FC4BDDA0F36164F /* TodoCategoryPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoCategoryPreference.swift; sourceTree = "<group>"; };
171173
0DE6079177C9C1BEB7729105 /* ObserveNetworkConnectivityUseCaseImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObserveNetworkConnectivityUseCaseImpl.swift; sourceTree = "<group>"; };
172174
0FFA6212304F79947234F6B6 /* FetchTodoCategoryPreferencesUseCaseImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchTodoCategoryPreferencesUseCaseImpl.swift; sourceTree = "<group>"; };
@@ -460,6 +462,7 @@
460462
8FB3EADEF22E74D670F9AC07 /* TodoCategory.swift */,
461463
0DE4C7B87FC4BDDA0F36164F /* TodoCategoryPreference.swift */,
462464
D568A3D0C748AC2EBA756981 /* TodoCursor.swift */,
465+
0D715313032680FBCAEC3272 /* TodoDraft.swift */,
463466
9215705C81F1AC8DFF028D5B /* TodoPage.swift */,
464467
ABA9FA543E3197EF5DF55ECB /* TodoReference.swift */,
465468
79E7E7D9AB8EC8B90D6DF0BD /* UserProfile.swift */,
@@ -912,6 +915,7 @@
912915
A9B574CED797235C974185A2 /* TodoCategory.swift in Sources */,
913916
EDA93E3E36530BA704D41A68 /* TodoCategoryPreference.swift in Sources */,
914917
C3253442982CCFCC7736197E /* TodoCursor.swift in Sources */,
918+
64F70B07260067A13C7F0FE0 /* TodoDraft.swift in Sources */,
915919
C8E9E1FB4F3F5B7851612EB2 /* TodoPage.swift in Sources */,
916920
41E798191E4C30558452302F /* TodoReference.swift in Sources */,
917921
F7063A1AB3A294E8BB2E585A /* UserProfile.swift in Sources */,

Application/DevLogDomain/Sources/Entity/Todo.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public struct Todo: Hashable {
1212
public var isPinned: Bool // 해당 할 일이 상단에 고정되어 있는지 여부
1313
public var isCompleted: Bool // 해당 할 일의 완료 여부
1414
public var isChecked: Bool // 해당 할 일의 체크 여부
15-
public var number: Int? // 사용자에게 노출되는 Todo 번호
15+
public var number: Int // 사용자에게 노출되는 Todo 번호
1616
public var title: String // 할 일의 제목
1717
public var content: String // 할 일의 설명
1818
public var createdAt: Date // 할 일 생성 날짜
@@ -28,7 +28,7 @@ public struct Todo: Hashable {
2828
isPinned: Bool,
2929
isCompleted: Bool,
3030
isChecked: Bool,
31-
number: Int?,
31+
number: Int,
3232
title: String,
3333
content: String,
3434
createdAt: Date,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//
2+
// TodoDraft.swift
3+
// DevLogDomain
4+
//
5+
// Created by opfic on 6/2/26.
6+
//
7+
8+
import Foundation
9+
10+
public struct TodoDraft: Equatable {
11+
public var id: String
12+
public var isPinned: Bool
13+
public var isCompleted: Bool
14+
public var isChecked: Bool
15+
public var title: String
16+
public var content: String
17+
public var createdAt: Date
18+
public var updatedAt: Date
19+
public var completedAt: Date?
20+
public var dueDate: Date?
21+
public var tags: [String]
22+
public var category: TodoCategory
23+
24+
public init(
25+
id: String,
26+
isPinned: Bool,
27+
isCompleted: Bool,
28+
isChecked: Bool,
29+
title: String,
30+
content: String,
31+
createdAt: Date,
32+
updatedAt: Date,
33+
completedAt: Date?,
34+
dueDate: Date?,
35+
tags: [String],
36+
category: TodoCategory
37+
) {
38+
self.id = id
39+
self.isPinned = isPinned
40+
self.isCompleted = isCompleted
41+
self.isChecked = isChecked
42+
self.title = title
43+
self.content = content
44+
self.createdAt = createdAt
45+
self.updatedAt = updatedAt
46+
self.completedAt = completedAt
47+
self.dueDate = dueDate
48+
self.tags = tags
49+
self.category = category
50+
}
51+
52+
public init(todo: Todo) {
53+
self.id = todo.id
54+
self.isPinned = todo.isPinned
55+
self.isCompleted = todo.isCompleted
56+
self.isChecked = todo.isChecked
57+
self.title = todo.title
58+
self.content = todo.content
59+
self.createdAt = todo.createdAt
60+
self.updatedAt = todo.updatedAt
61+
self.completedAt = todo.completedAt
62+
self.dueDate = todo.dueDate
63+
self.tags = todo.tags
64+
self.category = todo.category
65+
}
66+
67+
public static func == (lhs: TodoDraft, rhs: TodoDraft) -> Bool {
68+
lhs.id == rhs.id &&
69+
lhs.isPinned == rhs.isPinned &&
70+
lhs.isCompleted == rhs.isCompleted &&
71+
lhs.isChecked == rhs.isChecked &&
72+
lhs.title == rhs.title &&
73+
lhs.content == rhs.content &&
74+
lhs.completedAt == rhs.completedAt &&
75+
lhs.dueDate == rhs.dueDate &&
76+
lhs.tags == rhs.tags &&
77+
lhs.category == rhs.category
78+
}
79+
}

Application/DevLogDomain/Sources/Protocol/TodoRepository.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public protocol TodoRepository {
1313
func fetchTodo(_ todoId: String) async throws -> Todo
1414
func fetchReferences(_ numbers: [Int]) async throws -> [Int: TodoReference]
1515
func upsertTodo(_ todo: Todo) async throws
16+
func upsertTodo(_ todoDraft: TodoDraft) async throws
1617
func deleteTodo(_ todoId: String) async throws
1718
func undoDeleteTodo(_ todoId: String) async throws
1819
}

Application/DevLogDomain/Sources/UseCase/Todo/Upsert/UpsertTodoUseCase.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77

88
public protocol UpsertTodoUseCase {
99
func execute(_ todo: Todo) async throws
10+
func execute(_ todoDraft: TodoDraft) async throws
1011
}

Application/DevLogDomain/Sources/UseCase/Todo/Upsert/UpsertTodoUseCaseImpl.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ public final class UpsertTodoUseCaseImpl: UpsertTodoUseCase {
1515
public func execute(_ todo: Todo) async throws {
1616
try await repository.upsertTodo(todo)
1717
}
18+
19+
public func execute(_ todoDraft: TodoDraft) async throws {
20+
try await repository.upsertTodo(todoDraft)
21+
}
1822
}

Application/DevLogPresentation/Sources/Home/Home/HomeViewCoordinator.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ final class HomeViewCoordinator {
4343

4444
cancellable = windowEvent.submits
4545
.sink { [weak self] submit in
46-
guard submit.value.matchesCreate(source: .home) else { return }
46+
guard case .create(let value) = submit,
47+
value.matchesCreate(source: .home) else { return }
4748
self?.viewModel.send(.fetchData)
4849
}
4950
}
@@ -59,7 +60,7 @@ final class HomeViewCoordinator {
5960
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self),
6061
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
6162
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self),
62-
onUpsertSuccess: { [weak self] _ in
63+
onCreateSuccess: { [weak self] in
6364
self?.viewModel.send(.setPresentation(.todoEditor, false))
6465
self?.viewModel.send(.fetchData)
6566
}

0 commit comments

Comments
 (0)