Skip to content

Commit d3517a4

Browse files
[#289] Todo 카테고리를 유저가 CRUD 할 수 있도록 개선한다 (#337)
* feat: 기존 TodoCategory를 SystemTodoCategory로 수정, UserTodoCategory를 생성하고 기존 카테코리는 두 종류를 아우르는 형태로 구성 * feat: 카테고리와 색상을 입력하는 프레젠테이션 구성 * feat: 사용자 커스텀 카테고리는 삭제 가능 * ui: 색상 대신 코드를 보여주고, 해당 색상으로 폰트 색 적용 * chore: 아키텍쳐 특성 상 발생하는 패턴 disable * feat: 서비스 카테고리 설정과 사용자 카테고리 설정을 Firestore을 통해 영속성을 지원하도록 구현 * ui: 사용자가 내릴 수 있다는 표시 추가 * feat: 커스텀 카테고리를 삭제하면 얼럿이 뜨고 확인 버튼을 눌러야 삭제되도록 구현 * feat: Firestore에서 fetch 시 로딩뷰가 뜨도록 추가 * feat: 카테고리를 제거 시 해당 카테고리를 선택한 TODO를 기타 카테고리로 수정하는 Cloud Functions 구현 * style: 이름이 어려운 메서드명을 직관적으로 수정 * fix: 커스텀 카테고리가 디코딩 되지 않는 현상 해결 * feat: TODO 수정 시 커스텀 카테고리도 떠있을 수 있도록 추가 * fix: 푸시알림 데이터가 시스템 카테고리만 디코딩 가능한 현상 해결 * feat: TODO의 카테고리를 변경하면 그에 대응되는 푸시 알림의 데이터도 수정되도록 구현 * refactor: 커스텀 카테고리를 id로 관리 * chore: firebase 업데이트 * feat: TODO에서 다른 TODO를 참조 시 커스텀 카테고리로 되어 있어도 참조가 가능하도록 구현 * feat: 사용자 카테고리를 수정할 수 있도록 구현 * feat: 카테고리를 수정해도 최근 수정 섹션에서는 반영되지 않는 현상 해결 * feat: 최대 20자 제한 * feat: 색상 hex 코드를 탭하면 랜덤으로 색상을 뽑도록 구현 * fix: oDomain 메서드가 다른 곳에서 resolve되지 않은 DTO와 함께 호출될 경우 잠재적 오류 발생 가능성 해결 * refactor: 공통 로직을 헬퍼로 묶음 * refactor: 불필요 case 문 제거 * fix: 사용자 커스텀 카테고리 끼리는 검사하지 않아 추가 * refactor: 각 레이어별 모델 사용처 명확화 * fix: 언어에 따라 비교값이 달라질 수 있는 문제 해결 Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * refactor: TodoCategoryPreferenceItem -> TodoCategoryItem --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 15cb106 commit d3517a4

62 files changed

Lines changed: 4929 additions & 192 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.swiftlint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ disabled_rules:
33
- multiple_closures_with_trailing_closure
44
- trailing_whitespace
55
- type_body_length
6+
- cyclomatic_complexity
7+
- function_body_length

DevLog/App/Assembler/DataAssembler.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ final class DataAssembler: Assembler {
2828

2929
container.register(TodoRepository.self) {
3030
TodoRepositoryImpl(
31-
todoService: container.resolve(TodoService.self)
31+
todoService: container.resolve(TodoService.self),
32+
todoCategoryService: container.resolve(TodoCategoryService.self)
33+
)
34+
}
35+
36+
container.register(TodoCategoryRepository.self) {
37+
TodoCategoryRepositoryImpl(
38+
todoCategoryService: container.resolve(TodoCategoryService.self)
3239
)
3340
}
3441

@@ -67,7 +74,10 @@ final class DataAssembler: Assembler {
6774
}
6875

6976
container.register(PushNotificationRepository.self) {
70-
PushNotificationRepositoryImpl(pushNotificationService: container.resolve(PushNotificationService.self))
77+
PushNotificationRepositoryImpl(
78+
pushNotificationService: container.resolve(PushNotificationService.self),
79+
todoCategoryService: container.resolve(TodoCategoryService.self)
80+
)
7181
}
7282

7383
container.register(WebPageRepository.self) {

DevLog/App/Assembler/DomainAssembler.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ final class DomainAssembler: Assembler {
1111
registerConnectivityUseCases(container)
1212
registerAuthProviderUseCases(container)
1313
registerTodoUseCases(container)
14+
registerTodoCategoryUseCases(container)
1415
registerUserDataUseCases(container)
1516
registerPushNotificationUseCases(container)
1617
registerWebPageUseCases(container)
@@ -85,6 +86,20 @@ private extension DomainAssembler {
8586
}
8687
}
8788

89+
func registerTodoCategoryUseCases(_ container: DIContainer) {
90+
container.register(FetchTodoCategoryPreferencesUseCase.self) {
91+
FetchTodoCategoryPreferencesUseCaseImpl(
92+
container.resolve(TodoCategoryRepository.self)
93+
)
94+
}
95+
96+
container.register(UpdateTodoCategoryPreferencesUseCase.self) {
97+
UpdateTodoCategoryPreferencesUseCaseImpl(
98+
container.resolve(TodoCategoryRepository.self)
99+
)
100+
}
101+
}
102+
88103
func registerUserDataUseCases(_ container: DIContainer) {
89104
container.register(FetchUserDataUseCase.self) {
90105
FetchUserDataUseCaseImpl(container.resolve(UserDataRepository.self))

DevLog/App/Assembler/InfraAssembler.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ final class InfraAssembler: Assembler {
3636
TodoService()
3737
}
3838

39+
container.register(TodoCategoryService.self) {
40+
TodoCategoryService()
41+
}
42+
3943
container.register(UserService.self) {
4044
UserService()
4145
}

DevLog/Data/DTO/PushNotificationResponse.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ struct PushNotificationResponse {
1414
let receivedAt: Date
1515
let isRead: Bool
1616
let todoId: String
17-
let todoCategory: String
17+
let todoCategory: TodoCategoryResponse
1818
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// TodoCategoryResponse.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/30/26.
6+
//
7+
8+
import Foundation
9+
10+
enum TodoCategoryResponse {
11+
case raw(String)
12+
case decoded(TodoCategory)
13+
}

DevLog/Data/DTO/TodoDTO.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct TodoRequest: Encodable {
2020
let completedAt: Date?
2121
let dueDate: Date?
2222
let tags: [String]
23-
let category: TodoCategory
23+
let category: String
2424
}
2525

2626
struct TodoResponse {
@@ -36,5 +36,5 @@ struct TodoResponse {
3636
let completedAt: Date?
3737
let dueDate: Date?
3838
let tags: [String]
39-
let category: String
39+
let category: TodoCategoryResponse
4040
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// TodoReferenceResponse.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/30/26.
6+
//
7+
8+
import Foundation
9+
10+
struct TodoReferenceResponse {
11+
let id: String
12+
let number: Int
13+
let title: String
14+
let category: TodoCategoryResponse
15+
}

DevLog/Data/Mapper/PushNotificationMapping.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@
77

88
extension PushNotificationResponse {
99
func toDomain() throws -> PushNotification {
10-
guard let todoCategory = TodoCategory(rawValue: self.todoCategory) else {
11-
throw DataError.invalidData("PushNotificationResponse.todoCategory is invalid: \(self.todoCategory)")
10+
let todoCategory: TodoCategory
11+
12+
switch self.todoCategory {
13+
case .decoded(let category):
14+
todoCategory = category
15+
case .raw(let category):
16+
throw DataError.invalidData(
17+
"PushNotificationResponse.todoCategory must be resolved before toDomain(): \(category)"
18+
)
1219
}
1320

1421
return PushNotification(

DevLog/Data/Mapper/TodoMapping.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,20 @@ extension TodoRequest {
2020
completedAt: entity.completedAt,
2121
dueDate: entity.dueDate,
2222
tags: entity.tags,
23-
category: entity.category
23+
category: entity.category.storageValue
2424
)
2525
}
2626
}
2727

2828
extension TodoResponse {
2929
func toDomain() throws -> Todo {
30-
guard let category = TodoCategory(rawValue: self.category) else {
31-
throw DataError.invalidData("TodoResponse.category is invalid: \(self.category)")
30+
let todoCategory: TodoCategory
31+
32+
switch category {
33+
case .decoded(let category):
34+
todoCategory = category
35+
case .raw(let category):
36+
throw DataError.invalidData("TodoResponse.category must be resolved before toDomain(): \(category)")
3237
}
3338

3439
return Todo(
@@ -44,7 +49,7 @@ extension TodoResponse {
4449
completedAt: self.completedAt,
4550
dueDate: self.dueDate,
4651
tags: self.tags,
47-
category: category
52+
category: todoCategory
4853
)
4954
}
5055
}

0 commit comments

Comments
 (0)