Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e4c3070
feat: 차트 기능 제거
opficdev Apr 3, 2026
f61cb69
ui: 배지 ui와 최대 한개 띄울수 있도록 수정
opficdev Apr 3, 2026
f037eed
fix: 보이지 않는 셀에도 선택된 날짜와 동일하면 테두리가 생기는 현상 해결
opficdev Apr 3, 2026
8eae35d
chore: 변수명 및 폴더링 정리
opficdev Apr 3, 2026
41b4462
fix: 작업 큐 처리량 조정
opficdev Apr 3, 2026
49db263
feat: Todo 추가, 완료, 삭제 집계를 구성하는 함수 구현
opficdev Apr 3, 2026
2aaf020
style: userId -> uid 수정
opficdev Apr 3, 2026
5cfb7a7
feat: Firestore에 저장된 DailyActivity와 DailyActivityEvents를 받아오는 도메인, 데이…
opficdev Apr 3, 2026
fa67dc1
style: todoID -> todoId 로 수정
opficdev Apr 3, 2026
dadec85
feat: 최초 가입 시 생성 날짜 저장
opficdev Apr 3, 2026
448d802
refactor: 분기 범위 계산 기준을 가입 시점으로 변경
opficdev Apr 3, 2026
cf06849
fix: 인덱스가 달라 Acitvity를 통해 Todo를 조회하지 못하는 이슈 해결
opficdev Apr 3, 2026
3b6c69f
refactor: Todo 번호에서 적절하게 옵셔널 제거
opficdev Apr 3, 2026
3f790b1
feat: Firestore에 저장된 DailyActivity와 DailyActivityEvents를 받아와 프레젠테이션, …
opficdev Apr 3, 2026
1b3f45d
fix: 과거 이벤트가 발생 이후에 삭제 이벤트가 있어도 Todo를 fetch 할 수 있는 경로가 존재했던 이슈를 해결
opficdev Apr 3, 2026
c7d4fff
fix: 필터링 조건이 'or' 였던 이슈 해결
opficdev Apr 3, 2026
dc75f77
feat: 리프레쉬 구현
opficdev Apr 4, 2026
8b7a425
refactor: Todo의 필드를 isDeleting, deletedAt으로 수정
opficdev Apr 4, 2026
f3c140f
refactor: Todo로 SSOT를 만족하기 위해 기존 액티비티 구성들 제거
opficdev Apr 4, 2026
183cf4d
refactor: 서버에서 Activity를 제거 및 Todo의 hard deletion 제거하여 SSOT 만족
opficdev Apr 4, 2026
a88940f
fix: vscode에서 ourDir 키에 밑줄 그여있는 현상 해결
opficdev Apr 4, 2026
56248c3
refactor: Todo 삭제 상태를 deletedAt 단일 모델로 정리
opficdev Apr 4, 2026
d02514e
feat: 프로필뷰에서 필요한 인덱스 추가
opficdev Apr 4, 2026
69e6156
fix: Todo 제거 시 필드를 제거하는게 아닌 Null로 처리하여 soft deletion이 작동하도록 수정
opficdev Apr 5, 2026
6ef202b
feat: 삭제된 지 24시간이 지난 Todo에 대해 필요한 데이터만 남겨 DB 용량 최소화
opficdev Apr 5, 2026
0ff7b76
refactor: 압축된 Todo 필드도 디코딩이 가능하도록 수정
opficdev Apr 5, 2026
0068cde
docs: 변경된 UI에 맞게 이미지 수정
opficdev Apr 5, 2026
7af75f3
refactor: compactedAt에 대한 쿼리를 제거하고 해당 필드의 존재 유무로 압축 여부를 결정하도록 개선
opficdev Apr 5, 2026
38a0285
refactor: 쓰지 않는 코드 제거
opficdev Apr 5, 2026
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
4 changes: 2 additions & 2 deletions DevLog/Data/DTO/TodoDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ struct TodoRequest: Encodable {
let isPinned: Bool
let isCompleted: Bool
let isChecked: Bool
let isDeleted: Bool
let number: Int?
let title: String
let content: String
let createdAt: Date
let updatedAt: Date
let completedAt: Date?
let deletedAt: Date?
let dueDate: Date?
let tags: [String]
let category: String
Expand All @@ -35,6 +34,7 @@ struct TodoResponse {
let createdAt: Date
let updatedAt: Date
let completedAt: Date?
let deletedAt: Date?
let dueDate: Date?
let tags: [String]
let category: TodoCategoryResponse
Expand Down
1 change: 1 addition & 0 deletions DevLog/Data/DTO/UserProfileResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ struct UserProfileResponse: Decodable {
let email: String
let statusMessage: String
let avatarURL: URL?
let createdAt: Date
}
4 changes: 2 additions & 2 deletions DevLog/Data/Mapper/TodoMapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ extension TodoRequest {
isPinned: entity.isPinned,
isCompleted: entity.isCompleted,
isChecked: entity.isChecked,
isDeleted: false,
number: entity.number,
title: entity.title,
content: entity.content,
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
completedAt: entity.completedAt,
deletedAt: entity.deletedAt,
dueDate: entity.dueDate,
tags: entity.tags,
category: entity.category.storageValue
Expand Down Expand Up @@ -48,6 +47,7 @@ extension TodoResponse {
createdAt: self.createdAt,
updatedAt: self.updatedAt,
completedAt: self.completedAt,
deletedAt: self.deletedAt,
dueDate: self.dueDate,
tags: self.tags,
category: todoCategory
Expand Down
3 changes: 2 additions & 1 deletion DevLog/Data/Mapper/UserProfileMapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ extension UserProfileResponse {
name: self.name,
email: self.email,
statusMessage: self.statusMessage,
avatarURL: self.avatarURL
avatarURL: self.avatarURL,
createdAt: self.createdAt
)
}
}
1 change: 1 addition & 0 deletions DevLog/Data/Repository/TodoRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ private extension TodoRepositoryImpl {
createdAt: response.createdAt,
updatedAt: response.updatedAt,
completedAt: response.completedAt,
deletedAt: response.deletedAt,
dueDate: response.dueDate,
tags: response.tags,
category: .decoded(category)
Expand Down
14 changes: 14 additions & 0 deletions DevLog/Domain/Entity/ActivityKind.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// ActivityKind.swift
// DevLog
//
// Created by opfic on 4/4/26.
//

import Foundation

enum ActivityKind: String, Hashable {
case created
case completed
case deleted
}
1 change: 1 addition & 0 deletions DevLog/Domain/Entity/Todo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct Todo: Hashable {
var createdAt: Date // 할 일 생성 날짜
var updatedAt: Date // 할 일 수정 날짜
var completedAt: Date? // 할 일 완료 날짜
var deletedAt: Date? // 할 일 삭제 날짜
var dueDate: Date? // 할 일의 마감 날짜 (선택 사항)
var tags: [String] // 할 일에 연결된 태그들
var category: TodoCategory // 할 일의 종류
Expand Down
21 changes: 15 additions & 6 deletions DevLog/Domain/Entity/TodoQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import Foundation
struct TodoQuery: Equatable {
enum SortTarget: Equatable, Hashable {
case createdAt
case completedAt
case deletedAt
case updatedAt
case dueDate

var fieldName: String {
switch self {
case .createdAt:
return "createdAt"
case .completedAt:
return "completedAt"
case .deletedAt:
return "deletedAt"
case .updatedAt:
return "updatedAt"
case .dueDate:
Expand Down Expand Up @@ -62,8 +68,9 @@ struct TodoQuery: Equatable {
var isPinned: Bool?
var completionFilter: CompletionFilter
var dueDateFilter: DueDateFilter
var createdAtFrom: Date?
var createdAtTo: Date?
var sortDateFrom: Date?
var sortDateTo: Date?
var includesDeleted: Bool
var sortTarget: SortTarget
var sortOrder: SortOrder
var pageSize: Int
Expand All @@ -75,8 +82,9 @@ struct TodoQuery: Equatable {
isPinned: Bool? = nil,
completionFilter: CompletionFilter = .all,
dueDateFilter: DueDateFilter = .all,
createdAtFrom: Date? = nil,
createdAtTo: Date? = nil,
sortDateFrom: Date? = nil,
sortDateTo: Date? = nil,
includesDeleted: Bool = false,
sortTarget: SortTarget = .createdAt,
sortOrder: SortOrder = .latest,
pageSize: Int = 20,
Expand All @@ -87,8 +95,9 @@ struct TodoQuery: Equatable {
self.isPinned = isPinned
self.completionFilter = completionFilter
self.dueDateFilter = dueDateFilter
self.createdAtFrom = createdAtFrom
self.createdAtTo = createdAtTo
self.sortDateFrom = sortDateFrom
self.sortDateTo = sortDateTo
self.includesDeleted = includesDeleted
self.sortTarget = sortTarget
self.sortOrder = sortOrder
self.pageSize = pageSize
Expand Down
1 change: 1 addition & 0 deletions DevLog/Domain/Entity/UserProfile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ struct UserProfile {
let email: String
let statusMessage: String
let avatarURL: URL?
let createdAt: Date
}
68 changes: 33 additions & 35 deletions DevLog/Infra/Service/TodoService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ final class TodoService {
query.isPinned != nil ? "pinned=\(query.isPinned!)" : nil,
query.completionFilter.isCompletedValue != nil ? "completed=\(query.completionFilter.isCompletedValue!)" : nil,
query.dueDateFilter != .all ? "dueDateFilter=\(query.dueDateFilter)" : nil,
query.createdAtFrom != nil ? "createdAtFrom=\(query.createdAtFrom!)" : nil,
query.createdAtTo != nil ? "createdAtTo=\(query.createdAtTo!)" : nil,
query.sortDateFrom != nil ? "sortDateFrom=\(query.sortDateFrom!)" : nil,
query.sortDateTo != nil ? "sortDateTo=\(query.sortDateTo!)" : nil,
query.includesDeleted ? "includesDeleted=true" : nil,
"pageSize=\(query.pageSize)",
query.fetchAllPages ? "fetchAllPages=true" : nil,
cursor != nil ? "cursor=\(cursor!)" : nil
Expand Down Expand Up @@ -73,17 +74,17 @@ final class TodoService {
firestoreQuery = firestoreQuery.whereField("dueDate", isEqualTo: NSNull())
}

if let createdAtFrom = query.createdAtFrom {
if let sortDateFrom = query.sortDateFrom {
firestoreQuery = firestoreQuery.whereField(
"createdAt",
isGreaterThanOrEqualTo: Timestamp(date: createdAtFrom)
query.sortTarget.fieldName,
isGreaterThanOrEqualTo: Timestamp(date: sortDateFrom)
)
}

if let createdAtTo = query.createdAtTo {
if let sortDateTo = query.sortDateTo {
firestoreQuery = firestoreQuery.whereField(
"createdAt",
isLessThan: Timestamp(date: createdAtTo)
query.sortTarget.fieldName,
isLessThan: Timestamp(date: sortDateTo)
)
}

Expand Down Expand Up @@ -165,12 +166,12 @@ final class TodoService {
let docRef = collection.document(request.id)
var data = try encoder.encode(request)
data.removeValue(forKey: TodoFieldKey.id.rawValue)
if let number = request.number {
data[TodoFieldKey.number.rawValue] = number
}
if request.completedAt == nil {
data[TodoFieldKey.completedAt.rawValue] = NSNull()
}
if request.deletedAt == nil {
data[TodoFieldKey.deletedAt.rawValue] = NSNull()
}
if request.dueDate == nil {
data[TodoFieldKey.dueDate.rawValue] = NSNull()
}
Expand Down Expand Up @@ -229,7 +230,7 @@ final class TodoService {
do {
let snapshot = try await store.collection(FirestorePath.todos(uid))
.whereField(FieldPath.documentID(), isEqualTo: todoId)
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
.whereField(TodoFieldKey.deletedAt.rawValue, isEqualTo: NSNull())
.limit(to: 1)
.getDocuments()
guard let document = snapshot.documents.first, let todo = makeResponse(from: document) else {
Expand All @@ -256,7 +257,7 @@ final class TodoService {
group.addTask {
let snapshot = try await collection
.whereField(TodoFieldKey.number.rawValue, in: chunk)
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
.whereField(TodoFieldKey.deletedAt.rawValue, isEqualTo: NSNull())
.getDocuments()
return snapshot.documents
}
Expand All @@ -272,8 +273,7 @@ final class TodoService {
return snapshots.reduce(into: [Int: TodoReferenceResponse]()) { partialResult, document in
let data = document.data()
guard
!(data[TodoFieldKey.deletingAt.rawValue] is Timestamp),
(data[TodoFieldKey.isDeleted.rawValue] as? Bool) != true,
data[TodoFieldKey.deletedAt.rawValue] is NSNull,
let response = makeResponse(from: document)
else {
return
Expand Down Expand Up @@ -359,16 +359,19 @@ private extension TodoService {
}

func makeQuery(uid: String, query: TodoQuery) -> Query {
let collection = store.collection(FirestorePath.todos(uid))
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
var collection: Query = store.collection(FirestorePath.todos(uid))

if !query.includesDeleted {
collection = collection.whereField(TodoFieldKey.deletedAt.rawValue, isEqualTo: NSNull())
}

switch query.sortTarget {
case .dueDate:
return collection
.order(by: query.sortTarget.fieldName, descending: query.sortOrder.isDescending)
.order(by: "updatedAt", descending: true)
.order(by: FieldPath.documentID())
case .createdAt, .updatedAt:
case .createdAt, .completedAt, .deletedAt, .updatedAt:
return collection
.order(by: query.sortTarget.fieldName, descending: query.sortOrder.isDescending)
.order(by: FieldPath.documentID())
Expand All @@ -389,7 +392,7 @@ private extension TodoService {
Timestamp(date: sortDate),
cursor.documentID
]
case .createdAt, .updatedAt:
case .createdAt, .completedAt, .deletedAt, .updatedAt:
return [
primaryValue,
cursor.documentID
Expand Down Expand Up @@ -420,7 +423,7 @@ private extension TodoService {
return nil
}
secondarySortDate = updatedAt.dateValue()
case .createdAt, .updatedAt:
case .createdAt, .completedAt, .deletedAt, .updatedAt:
secondarySortDate = nil
}

Expand All @@ -432,10 +435,6 @@ private extension TodoService {
}

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

Expand All @@ -447,26 +446,25 @@ private extension TodoService {
}

func makeResponse(documentID: String, data: [String: Any]) -> TodoResponse? {
if data[TodoFieldKey.deletingAt.rawValue] is Timestamp ||
(data[TodoFieldKey.isDeleted.rawValue] as? Bool) == true {
return nil
}
guard
let isPinned = data[TodoFieldKey.isPinned.rawValue] as? Bool,
let isCompleted = data[TodoFieldKey.isCompleted.rawValue] as? Bool,
let isChecked = data[TodoFieldKey.isChecked.rawValue] as? Bool,
let number = data[TodoFieldKey.number.rawValue] as? Int,
let title = data[TodoFieldKey.title.rawValue] as? String,
let content = data[TodoFieldKey.content.rawValue] as? String,
let createdAtTimestamp = data[TodoFieldKey.createdAt.rawValue] as? Timestamp,
let updatedAtTimestamp = data[TodoFieldKey.updatedAt.rawValue] as? Timestamp,
let tags = data[TodoFieldKey.tags.rawValue] as? [String],
let category = data[TodoFieldKey.category.rawValue] as? String else {
return nil
}

let completedAt = (data[TodoFieldKey.completedAt.rawValue] as? Timestamp)?.dateValue()
let deletedAt = (data[TodoFieldKey.deletedAt.rawValue] as? Timestamp)?.dateValue()
let dueDate = (data[TodoFieldKey.dueDate.rawValue] as? Timestamp)?.dateValue()

let isPinned = data[TodoFieldKey.isPinned.rawValue] as? Bool ?? false
let isCompleted = data[TodoFieldKey.isCompleted.rawValue] as? Bool ?? (completedAt != nil)
let isChecked = data[TodoFieldKey.isChecked.rawValue] as? Bool ?? false
let content = data[TodoFieldKey.content.rawValue] as? String ?? ""
let tags = data[TodoFieldKey.tags.rawValue] as? [String] ?? []

return TodoResponse(
id: documentID,
isPinned: isPinned,
Expand All @@ -478,6 +476,7 @@ private extension TodoService {
createdAt: createdAtTimestamp.dateValue(),
updatedAt: updatedAtTimestamp.dateValue(),
completedAt: completedAt,
deletedAt: deletedAt,
dueDate: dueDate,
tags: tags,
category: .raw(category)
Expand All @@ -495,11 +494,10 @@ private extension TodoService {
case createdAt
case updatedAt
case completedAt
case deletedAt
case dueDate
case tags
case category
case deletingAt // 삭제 요청으로 앱의 로컬 데이터에서 deletion이 된 상태
case isDeleted // 삭제 요청으로 서버에서 soft deletion이 된 상태
}

enum CounterFieldKey: String {
Expand Down
Loading