Skip to content

Commit 1012250

Browse files
authored
[#356] 차트를 제거하고 히트맵 UI를 구체화하며 Todo 삭제도 집계되도록 개선한다 (#359)
* feat: 차트 기능 제거 * ui: 배지 ui와 최대 한개 띄울수 있도록 수정 * fix: 보이지 않는 셀에도 선택된 날짜와 동일하면 테두리가 생기는 현상 해결 * chore: 변수명 및 폴더링 정리 * fix: 작업 큐 처리량 조정 * feat: Todo 추가, 완료, 삭제 집계를 구성하는 함수 구현 * style: userId -> uid 수정 * feat: Firestore에 저장된 DailyActivity와 DailyActivityEvents를 받아오는 도메인, 데이터 레이어 구현 * style: todoID -> todoId 로 수정 * feat: 최초 가입 시 생성 날짜 저장 * refactor: 분기 범위 계산 기준을 가입 시점으로 변경 * fix: 인덱스가 달라 Acitvity를 통해 Todo를 조회하지 못하는 이슈 해결 * refactor: Todo 번호에서 적절하게 옵셔널 제거 * feat: Firestore에 저장된 DailyActivity와 DailyActivityEvents를 받아와 프레젠테이션, UI 계층에 사용하도록 구현 * fix: 과거 이벤트가 발생 이후에 삭제 이벤트가 있어도 Todo를 fetch 할 수 있는 경로가 존재했던 이슈를 해결 * fix: 필터링 조건이 'or' 였던 이슈 해결 * feat: 리프레쉬 구현 * refactor: Todo의 필드를 isDeleting, deletedAt으로 수정 * refactor: Todo로 SSOT를 만족하기 위해 기존 액티비티 구성들 제거 * refactor: 서버에서 Activity를 제거 및 Todo의 hard deletion 제거하여 SSOT 만족 * fix: vscode에서 ourDir 키에 밑줄 그여있는 현상 해결 * refactor: Todo 삭제 상태를 deletedAt 단일 모델로 정리 * feat: 프로필뷰에서 필요한 인덱스 추가 * fix: Todo 제거 시 필드를 제거하는게 아닌 Null로 처리하여 soft deletion이 작동하도록 수정 * feat: 삭제된 지 24시간이 지난 Todo에 대해 필요한 데이터만 남겨 DB 용량 최소화 * refactor: 압축된 Todo 필드도 디코딩이 가능하도록 수정 * docs: 변경된 UI에 맞게 이미지 수정 * refactor: compactedAt에 대한 쿼리를 제거하고 해당 필드의 존재 유무로 압축 여부를 결정하도록 개선 * refactor: 쓰지 않는 코드 제거
1 parent 6eb69cd commit 1012250

Some content is hidden

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

55 files changed

+818
-990
lines changed

DevLog/Data/DTO/TodoDTO.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ struct TodoRequest: Encodable {
1212
let isPinned: Bool
1313
let isCompleted: Bool
1414
let isChecked: Bool
15-
let isDeleted: Bool
16-
let number: Int?
1715
let title: String
1816
let content: String
1917
let createdAt: Date
2018
let updatedAt: Date
2119
let completedAt: Date?
20+
let deletedAt: Date?
2221
let dueDate: Date?
2322
let tags: [String]
2423
let category: String
@@ -35,6 +34,7 @@ struct TodoResponse {
3534
let createdAt: Date
3635
let updatedAt: Date
3736
let completedAt: Date?
37+
let deletedAt: Date?
3838
let dueDate: Date?
3939
let tags: [String]
4040
let category: TodoCategoryResponse

DevLog/Data/DTO/UserProfileResponse.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ struct UserProfileResponse: Decodable {
1212
let email: String
1313
let statusMessage: String
1414
let avatarURL: URL?
15+
let createdAt: Date
1516
}

DevLog/Data/Mapper/TodoMapping.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ extension TodoRequest {
1212
isPinned: entity.isPinned,
1313
isCompleted: entity.isCompleted,
1414
isChecked: entity.isChecked,
15-
isDeleted: false,
16-
number: entity.number,
1715
title: entity.title,
1816
content: entity.content,
1917
createdAt: entity.createdAt,
2018
updatedAt: entity.updatedAt,
2119
completedAt: entity.completedAt,
20+
deletedAt: entity.deletedAt,
2221
dueDate: entity.dueDate,
2322
tags: entity.tags,
2423
category: entity.category.storageValue
@@ -48,6 +47,7 @@ extension TodoResponse {
4847
createdAt: self.createdAt,
4948
updatedAt: self.updatedAt,
5049
completedAt: self.completedAt,
50+
deletedAt: self.deletedAt,
5151
dueDate: self.dueDate,
5252
tags: self.tags,
5353
category: todoCategory

DevLog/Data/Mapper/UserProfileMapping.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ extension UserProfileResponse {
1111
name: self.name,
1212
email: self.email,
1313
statusMessage: self.statusMessage,
14-
avatarURL: self.avatarURL
14+
avatarURL: self.avatarURL,
15+
createdAt: self.createdAt
1516
)
1617
}
1718
}

DevLog/Data/Repository/TodoRepositoryImpl.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ private extension TodoRepositoryImpl {
135135
createdAt: response.createdAt,
136136
updatedAt: response.updatedAt,
137137
completedAt: response.completedAt,
138+
deletedAt: response.deletedAt,
138139
dueDate: response.dueDate,
139140
tags: response.tags,
140141
category: .decoded(category)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// ActivityKind.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 4/4/26.
6+
//
7+
8+
import Foundation
9+
10+
enum ActivityKind: String, Hashable {
11+
case created
12+
case completed
13+
case deleted
14+
}

DevLog/Domain/Entity/Todo.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ struct Todo: Hashable {
1818
var createdAt: Date // 할 일 생성 날짜
1919
var updatedAt: Date // 할 일 수정 날짜
2020
var completedAt: Date? // 할 일 완료 날짜
21+
var deletedAt: Date? // 할 일 삭제 날짜
2122
var dueDate: Date? // 할 일의 마감 날짜 (선택 사항)
2223
var tags: [String] // 할 일에 연결된 태그들
2324
var category: TodoCategory // 할 일의 종류

DevLog/Domain/Entity/TodoQuery.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@ import Foundation
1010
struct TodoQuery: Equatable {
1111
enum SortTarget: Equatable, Hashable {
1212
case createdAt
13+
case completedAt
14+
case deletedAt
1315
case updatedAt
1416
case dueDate
1517

1618
var fieldName: String {
1719
switch self {
1820
case .createdAt:
1921
return "createdAt"
22+
case .completedAt:
23+
return "completedAt"
24+
case .deletedAt:
25+
return "deletedAt"
2026
case .updatedAt:
2127
return "updatedAt"
2228
case .dueDate:
@@ -62,8 +68,9 @@ struct TodoQuery: Equatable {
6268
var isPinned: Bool?
6369
var completionFilter: CompletionFilter
6470
var dueDateFilter: DueDateFilter
65-
var createdAtFrom: Date?
66-
var createdAtTo: Date?
71+
var sortDateFrom: Date?
72+
var sortDateTo: Date?
73+
var includesDeleted: Bool
6774
var sortTarget: SortTarget
6875
var sortOrder: SortOrder
6976
var pageSize: Int
@@ -75,8 +82,9 @@ struct TodoQuery: Equatable {
7582
isPinned: Bool? = nil,
7683
completionFilter: CompletionFilter = .all,
7784
dueDateFilter: DueDateFilter = .all,
78-
createdAtFrom: Date? = nil,
79-
createdAtTo: Date? = nil,
85+
sortDateFrom: Date? = nil,
86+
sortDateTo: Date? = nil,
87+
includesDeleted: Bool = false,
8088
sortTarget: SortTarget = .createdAt,
8189
sortOrder: SortOrder = .latest,
8290
pageSize: Int = 20,
@@ -87,8 +95,9 @@ struct TodoQuery: Equatable {
8795
self.isPinned = isPinned
8896
self.completionFilter = completionFilter
8997
self.dueDateFilter = dueDateFilter
90-
self.createdAtFrom = createdAtFrom
91-
self.createdAtTo = createdAtTo
98+
self.sortDateFrom = sortDateFrom
99+
self.sortDateTo = sortDateTo
100+
self.includesDeleted = includesDeleted
92101
self.sortTarget = sortTarget
93102
self.sortOrder = sortOrder
94103
self.pageSize = pageSize

DevLog/Domain/Entity/UserProfile.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ struct UserProfile {
1212
let email: String
1313
let statusMessage: String
1414
let avatarURL: URL?
15+
let createdAt: Date
1516
}

DevLog/Infra/Service/TodoService.swift

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ final class TodoService {
3636
query.isPinned != nil ? "pinned=\(query.isPinned!)" : nil,
3737
query.completionFilter.isCompletedValue != nil ? "completed=\(query.completionFilter.isCompletedValue!)" : nil,
3838
query.dueDateFilter != .all ? "dueDateFilter=\(query.dueDateFilter)" : nil,
39-
query.createdAtFrom != nil ? "createdAtFrom=\(query.createdAtFrom!)" : nil,
40-
query.createdAtTo != nil ? "createdAtTo=\(query.createdAtTo!)" : nil,
39+
query.sortDateFrom != nil ? "sortDateFrom=\(query.sortDateFrom!)" : nil,
40+
query.sortDateTo != nil ? "sortDateTo=\(query.sortDateTo!)" : nil,
41+
query.includesDeleted ? "includesDeleted=true" : nil,
4142
"pageSize=\(query.pageSize)",
4243
query.fetchAllPages ? "fetchAllPages=true" : nil,
4344
cursor != nil ? "cursor=\(cursor!)" : nil
@@ -73,17 +74,17 @@ final class TodoService {
7374
firestoreQuery = firestoreQuery.whereField("dueDate", isEqualTo: NSNull())
7475
}
7576

76-
if let createdAtFrom = query.createdAtFrom {
77+
if let sortDateFrom = query.sortDateFrom {
7778
firestoreQuery = firestoreQuery.whereField(
78-
"createdAt",
79-
isGreaterThanOrEqualTo: Timestamp(date: createdAtFrom)
79+
query.sortTarget.fieldName,
80+
isGreaterThanOrEqualTo: Timestamp(date: sortDateFrom)
8081
)
8182
}
8283

83-
if let createdAtTo = query.createdAtTo {
84+
if let sortDateTo = query.sortDateTo {
8485
firestoreQuery = firestoreQuery.whereField(
85-
"createdAt",
86-
isLessThan: Timestamp(date: createdAtTo)
86+
query.sortTarget.fieldName,
87+
isLessThan: Timestamp(date: sortDateTo)
8788
)
8889
}
8990

@@ -165,12 +166,12 @@ final class TodoService {
165166
let docRef = collection.document(request.id)
166167
var data = try encoder.encode(request)
167168
data.removeValue(forKey: TodoFieldKey.id.rawValue)
168-
if let number = request.number {
169-
data[TodoFieldKey.number.rawValue] = number
170-
}
171169
if request.completedAt == nil {
172170
data[TodoFieldKey.completedAt.rawValue] = NSNull()
173171
}
172+
if request.deletedAt == nil {
173+
data[TodoFieldKey.deletedAt.rawValue] = NSNull()
174+
}
174175
if request.dueDate == nil {
175176
data[TodoFieldKey.dueDate.rawValue] = NSNull()
176177
}
@@ -229,7 +230,7 @@ final class TodoService {
229230
do {
230231
let snapshot = try await store.collection(FirestorePath.todos(uid))
231232
.whereField(FieldPath.documentID(), isEqualTo: todoId)
232-
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
233+
.whereField(TodoFieldKey.deletedAt.rawValue, isEqualTo: NSNull())
233234
.limit(to: 1)
234235
.getDocuments()
235236
guard let document = snapshot.documents.first, let todo = makeResponse(from: document) else {
@@ -256,7 +257,7 @@ final class TodoService {
256257
group.addTask {
257258
let snapshot = try await collection
258259
.whereField(TodoFieldKey.number.rawValue, in: chunk)
259-
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
260+
.whereField(TodoFieldKey.deletedAt.rawValue, isEqualTo: NSNull())
260261
.getDocuments()
261262
return snapshot.documents
262263
}
@@ -272,8 +273,7 @@ final class TodoService {
272273
return snapshots.reduce(into: [Int: TodoReferenceResponse]()) { partialResult, document in
273274
let data = document.data()
274275
guard
275-
!(data[TodoFieldKey.deletingAt.rawValue] is Timestamp),
276-
(data[TodoFieldKey.isDeleted.rawValue] as? Bool) != true,
276+
data[TodoFieldKey.deletedAt.rawValue] is NSNull,
277277
let response = makeResponse(from: document)
278278
else {
279279
return
@@ -359,16 +359,19 @@ private extension TodoService {
359359
}
360360

361361
func makeQuery(uid: String, query: TodoQuery) -> Query {
362-
let collection = store.collection(FirestorePath.todos(uid))
363-
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
362+
var collection: Query = store.collection(FirestorePath.todos(uid))
363+
364+
if !query.includesDeleted {
365+
collection = collection.whereField(TodoFieldKey.deletedAt.rawValue, isEqualTo: NSNull())
366+
}
364367

365368
switch query.sortTarget {
366369
case .dueDate:
367370
return collection
368371
.order(by: query.sortTarget.fieldName, descending: query.sortOrder.isDescending)
369372
.order(by: "updatedAt", descending: true)
370373
.order(by: FieldPath.documentID())
371-
case .createdAt, .updatedAt:
374+
case .createdAt, .completedAt, .deletedAt, .updatedAt:
372375
return collection
373376
.order(by: query.sortTarget.fieldName, descending: query.sortOrder.isDescending)
374377
.order(by: FieldPath.documentID())
@@ -389,7 +392,7 @@ private extension TodoService {
389392
Timestamp(date: sortDate),
390393
cursor.documentID
391394
]
392-
case .createdAt, .updatedAt:
395+
case .createdAt, .completedAt, .deletedAt, .updatedAt:
393396
return [
394397
primaryValue,
395398
cursor.documentID
@@ -420,7 +423,7 @@ private extension TodoService {
420423
return nil
421424
}
422425
secondarySortDate = updatedAt.dateValue()
423-
case .createdAt, .updatedAt:
426+
case .createdAt, .completedAt, .deletedAt, .updatedAt:
424427
secondarySortDate = nil
425428
}
426429

@@ -432,10 +435,6 @@ private extension TodoService {
432435
}
433436

434437
func makeResponse(from snapshot: QueryDocumentSnapshot) -> TodoResponse? {
435-
if snapshot.data()[TodoFieldKey.deletingAt.rawValue] is Timestamp ||
436-
(snapshot.data()[TodoFieldKey.isDeleted.rawValue] as? Bool) == true {
437-
return nil
438-
}
439438
return makeResponse(documentID: snapshot.documentID, data: snapshot.data())
440439
}
441440

@@ -447,26 +446,25 @@ private extension TodoService {
447446
}
448447

449448
func makeResponse(documentID: String, data: [String: Any]) -> TodoResponse? {
450-
if data[TodoFieldKey.deletingAt.rawValue] is Timestamp ||
451-
(data[TodoFieldKey.isDeleted.rawValue] as? Bool) == true {
452-
return nil
453-
}
454449
guard
455-
let isPinned = data[TodoFieldKey.isPinned.rawValue] as? Bool,
456-
let isCompleted = data[TodoFieldKey.isCompleted.rawValue] as? Bool,
457-
let isChecked = data[TodoFieldKey.isChecked.rawValue] as? Bool,
458450
let number = data[TodoFieldKey.number.rawValue] as? Int,
459451
let title = data[TodoFieldKey.title.rawValue] as? String,
460-
let content = data[TodoFieldKey.content.rawValue] as? String,
461452
let createdAtTimestamp = data[TodoFieldKey.createdAt.rawValue] as? Timestamp,
462453
let updatedAtTimestamp = data[TodoFieldKey.updatedAt.rawValue] as? Timestamp,
463-
let tags = data[TodoFieldKey.tags.rawValue] as? [String],
464454
let category = data[TodoFieldKey.category.rawValue] as? String else {
465455
return nil
466456
}
467457

468458
let completedAt = (data[TodoFieldKey.completedAt.rawValue] as? Timestamp)?.dateValue()
459+
let deletedAt = (data[TodoFieldKey.deletedAt.rawValue] as? Timestamp)?.dateValue()
469460
let dueDate = (data[TodoFieldKey.dueDate.rawValue] as? Timestamp)?.dateValue()
461+
462+
let isPinned = data[TodoFieldKey.isPinned.rawValue] as? Bool ?? false
463+
let isCompleted = data[TodoFieldKey.isCompleted.rawValue] as? Bool ?? (completedAt != nil)
464+
let isChecked = data[TodoFieldKey.isChecked.rawValue] as? Bool ?? false
465+
let content = data[TodoFieldKey.content.rawValue] as? String ?? ""
466+
let tags = data[TodoFieldKey.tags.rawValue] as? [String] ?? []
467+
470468
return TodoResponse(
471469
id: documentID,
472470
isPinned: isPinned,
@@ -478,6 +476,7 @@ private extension TodoService {
478476
createdAt: createdAtTimestamp.dateValue(),
479477
updatedAt: updatedAtTimestamp.dateValue(),
480478
completedAt: completedAt,
479+
deletedAt: deletedAt,
481480
dueDate: dueDate,
482481
tags: tags,
483482
category: .raw(category)
@@ -495,11 +494,10 @@ private extension TodoService {
495494
case createdAt
496495
case updatedAt
497496
case completedAt
497+
case deletedAt
498498
case dueDate
499499
case tags
500500
case category
501-
case deletingAt // 삭제 요청으로 앱의 로컬 데이터에서 deletion이 된 상태
502-
case isDeleted // 삭제 요청으로 서버에서 soft deletion이 된 상태
503501
}
504502

505503
enum CounterFieldKey: String {

0 commit comments

Comments
 (0)