From e9ecba539ae4f1d6d628c9e118916a549fc21858 Mon Sep 17 00:00:00 2001 From: opficdev Date: Sat, 28 Feb 2026 10:11:11 +0900 Subject: [PATCH 1/7] =?UTF-8?q?chore:=20=ED=8C=8C=EC=9D=BC=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/{Data => Domain}/Protocol/UserPreferencesRepository.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename DevLog/{Data => Domain}/Protocol/UserPreferencesRepository.swift (100%) diff --git a/DevLog/Data/Protocol/UserPreferencesRepository.swift b/DevLog/Domain/Protocol/UserPreferencesRepository.swift similarity index 100% rename from DevLog/Data/Protocol/UserPreferencesRepository.swift rename to DevLog/Domain/Protocol/UserPreferencesRepository.swift From b75b3037ff7dcc7b134a20392721b9417a3cb905 Mon Sep 17 00:00:00 2001 From: opficdev Date: Sat, 28 Feb 2026 10:20:18 +0900 Subject: [PATCH 2/7] refactor: TodoCursorResponse -> TodoCursorDTO --- DevLog/Data/Mapper/TodoMapping.swift | 4 ++-- DevLog/Data/Repository/TodoRepositoryImpl.swift | 2 +- .../DTO/{TodoCursorResponse.swift => TodoCursorDTO.swift} | 4 ++-- DevLog/Infra/DTO/TodoPageResponse.swift | 2 +- DevLog/Infra/Service/TodoService.swift | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) rename DevLog/Infra/DTO/{TodoCursorResponse.swift => TodoCursorDTO.swift} (70%) diff --git a/DevLog/Data/Mapper/TodoMapping.swift b/DevLog/Data/Mapper/TodoMapping.swift index 77cef84..4caf658 100644 --- a/DevLog/Data/Mapper/TodoMapping.swift +++ b/DevLog/Data/Mapper/TodoMapping.swift @@ -50,7 +50,7 @@ extension TodoResponse { } } -extension TodoCursorResponse { +extension TodoCursorDTO { func toDomain() -> TodoCursor { TodoCursor( createdAt: createdAt.dateValue(), @@ -59,7 +59,7 @@ extension TodoCursorResponse { } static func fromDomain(_ cursor: TodoCursor) -> Self { - TodoCursorResponse( + TodoCursorDTO( createdAt: Timestamp(date: cursor.createdAt), documentID: cursor.documentID ) diff --git a/DevLog/Data/Repository/TodoRepositoryImpl.swift b/DevLog/Data/Repository/TodoRepositoryImpl.swift index 871ba83..d020ba7 100644 --- a/DevLog/Data/Repository/TodoRepositoryImpl.swift +++ b/DevLog/Data/Repository/TodoRepositoryImpl.swift @@ -15,7 +15,7 @@ final class TodoRepositoryImpl: TodoRepository { } func fetchTodos(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage { - let responseCursor = cursor.map { TodoCursorResponse.fromDomain($0) } + let responseCursor = cursor.map { TodoCursorDTO.fromDomain($0) } let response = try await todoService.fetchTodos(query, cursor: responseCursor) return try response.toDomain() } diff --git a/DevLog/Infra/DTO/TodoCursorResponse.swift b/DevLog/Infra/DTO/TodoCursorDTO.swift similarity index 70% rename from DevLog/Infra/DTO/TodoCursorResponse.swift rename to DevLog/Infra/DTO/TodoCursorDTO.swift index 6d108ca..f2899a1 100644 --- a/DevLog/Infra/DTO/TodoCursorResponse.swift +++ b/DevLog/Infra/DTO/TodoCursorDTO.swift @@ -1,5 +1,5 @@ // -// TodoCursorResponse.swift +// TodoCursorDTO.swift // DevLog // // Created by opfic on 2/21/26. @@ -7,7 +7,7 @@ import FirebaseFirestore -struct TodoCursorResponse { +struct TodoCursorDTO { let createdAt: Timestamp let documentID: String } diff --git a/DevLog/Infra/DTO/TodoPageResponse.swift b/DevLog/Infra/DTO/TodoPageResponse.swift index bc71b3a..56faf1e 100644 --- a/DevLog/Infra/DTO/TodoPageResponse.swift +++ b/DevLog/Infra/DTO/TodoPageResponse.swift @@ -7,5 +7,5 @@ struct TodoPageResponse { let items: [TodoResponse] - let nextCursor: TodoCursorResponse? + let nextCursor: TodoCursorDTO? } diff --git a/DevLog/Infra/Service/TodoService.swift b/DevLog/Infra/Service/TodoService.swift index 6dafad9..898f0bc 100644 --- a/DevLog/Infra/Service/TodoService.swift +++ b/DevLog/Infra/Service/TodoService.swift @@ -14,7 +14,7 @@ final class TodoService { func fetchTodos( _ query: TodoQuery, - cursor: TodoCursorResponse? + cursor: TodoCursorDTO? ) async throws -> TodoPageResponse { guard let uid = Auth.auth().currentUser?.uid else { throw AuthError.notAuthenticated } @@ -52,12 +52,12 @@ final class TodoService { let items = snapshot.documents.compactMap { TodoResponse(from: $0) } - let nextCursor: TodoCursorResponse? = snapshot.documents.last.flatMap { document in + let nextCursor: TodoCursorDTO? = snapshot.documents.last.flatMap { document in guard let createdAt = document.data()["createdAt"] as? Timestamp else { return nil } - return TodoCursorResponse( + return TodoCursorDTO( createdAt: createdAt, documentID: document.documentID ) From 9afa21020b49b99a944b757921d0ffc1916ee315 Mon Sep 17 00:00:00 2001 From: opficdev Date: Sat, 28 Feb 2026 10:26:56 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20DTO=EB=A5=BC=20=EC=9D=B8=ED=94=84?= =?UTF-8?q?=EB=9D=BC=20->=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/{Infra => Data}/DTO/AppleAuthResponse.swift | 0 DevLog/{Infra => Data}/DTO/AuthDataResponse.swift | 0 DevLog/{Infra => Data}/DTO/NotificationKind.swift | 0 DevLog/{Infra => Data}/DTO/PushNotificationCursorDTO.swift | 0 DevLog/{Infra => Data}/DTO/PushNotificationPageResponse.swift | 0 DevLog/{Infra => Data}/DTO/PushNotificationResponse.swift | 0 DevLog/{Infra => Data}/DTO/TodoCursorDTO.swift | 0 DevLog/{Infra => Data}/DTO/TodoDTO.swift | 0 DevLog/{Infra => Data}/DTO/TodoPageResponse.swift | 0 DevLog/{Infra => Data}/DTO/UserProfileResponse.swift | 0 DevLog/{Infra => Data}/DTO/WebPageDTO.swift | 0 DevLog/{Infra => Data}/DTO/WebPageMetadataResponse.swift | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename DevLog/{Infra => Data}/DTO/AppleAuthResponse.swift (100%) rename DevLog/{Infra => Data}/DTO/AuthDataResponse.swift (100%) rename DevLog/{Infra => Data}/DTO/NotificationKind.swift (100%) rename DevLog/{Infra => Data}/DTO/PushNotificationCursorDTO.swift (100%) rename DevLog/{Infra => Data}/DTO/PushNotificationPageResponse.swift (100%) rename DevLog/{Infra => Data}/DTO/PushNotificationResponse.swift (100%) rename DevLog/{Infra => Data}/DTO/TodoCursorDTO.swift (100%) rename DevLog/{Infra => Data}/DTO/TodoDTO.swift (100%) rename DevLog/{Infra => Data}/DTO/TodoPageResponse.swift (100%) rename DevLog/{Infra => Data}/DTO/UserProfileResponse.swift (100%) rename DevLog/{Infra => Data}/DTO/WebPageDTO.swift (100%) rename DevLog/{Infra => Data}/DTO/WebPageMetadataResponse.swift (100%) diff --git a/DevLog/Infra/DTO/AppleAuthResponse.swift b/DevLog/Data/DTO/AppleAuthResponse.swift similarity index 100% rename from DevLog/Infra/DTO/AppleAuthResponse.swift rename to DevLog/Data/DTO/AppleAuthResponse.swift diff --git a/DevLog/Infra/DTO/AuthDataResponse.swift b/DevLog/Data/DTO/AuthDataResponse.swift similarity index 100% rename from DevLog/Infra/DTO/AuthDataResponse.swift rename to DevLog/Data/DTO/AuthDataResponse.swift diff --git a/DevLog/Infra/DTO/NotificationKind.swift b/DevLog/Data/DTO/NotificationKind.swift similarity index 100% rename from DevLog/Infra/DTO/NotificationKind.swift rename to DevLog/Data/DTO/NotificationKind.swift diff --git a/DevLog/Infra/DTO/PushNotificationCursorDTO.swift b/DevLog/Data/DTO/PushNotificationCursorDTO.swift similarity index 100% rename from DevLog/Infra/DTO/PushNotificationCursorDTO.swift rename to DevLog/Data/DTO/PushNotificationCursorDTO.swift diff --git a/DevLog/Infra/DTO/PushNotificationPageResponse.swift b/DevLog/Data/DTO/PushNotificationPageResponse.swift similarity index 100% rename from DevLog/Infra/DTO/PushNotificationPageResponse.swift rename to DevLog/Data/DTO/PushNotificationPageResponse.swift diff --git a/DevLog/Infra/DTO/PushNotificationResponse.swift b/DevLog/Data/DTO/PushNotificationResponse.swift similarity index 100% rename from DevLog/Infra/DTO/PushNotificationResponse.swift rename to DevLog/Data/DTO/PushNotificationResponse.swift diff --git a/DevLog/Infra/DTO/TodoCursorDTO.swift b/DevLog/Data/DTO/TodoCursorDTO.swift similarity index 100% rename from DevLog/Infra/DTO/TodoCursorDTO.swift rename to DevLog/Data/DTO/TodoCursorDTO.swift diff --git a/DevLog/Infra/DTO/TodoDTO.swift b/DevLog/Data/DTO/TodoDTO.swift similarity index 100% rename from DevLog/Infra/DTO/TodoDTO.swift rename to DevLog/Data/DTO/TodoDTO.swift diff --git a/DevLog/Infra/DTO/TodoPageResponse.swift b/DevLog/Data/DTO/TodoPageResponse.swift similarity index 100% rename from DevLog/Infra/DTO/TodoPageResponse.swift rename to DevLog/Data/DTO/TodoPageResponse.swift diff --git a/DevLog/Infra/DTO/UserProfileResponse.swift b/DevLog/Data/DTO/UserProfileResponse.swift similarity index 100% rename from DevLog/Infra/DTO/UserProfileResponse.swift rename to DevLog/Data/DTO/UserProfileResponse.swift diff --git a/DevLog/Infra/DTO/WebPageDTO.swift b/DevLog/Data/DTO/WebPageDTO.swift similarity index 100% rename from DevLog/Infra/DTO/WebPageDTO.swift rename to DevLog/Data/DTO/WebPageDTO.swift diff --git a/DevLog/Infra/DTO/WebPageMetadataResponse.swift b/DevLog/Data/DTO/WebPageMetadataResponse.swift similarity index 100% rename from DevLog/Infra/DTO/WebPageMetadataResponse.swift rename to DevLog/Data/DTO/WebPageMetadataResponse.swift From b7bc425b33f6279238da6bba2aa4835fff23234c Mon Sep 17 00:00:00 2001 From: opficdev Date: Sat, 28 Feb 2026 12:03:57 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20DTO=EC=97=90=EC=84=9C=20Firesto?= =?UTF-8?q?re=20=EC=9D=98=EC=A1=B4=EC=84=B1=EC=9D=84=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=ED=9B=84=20=EB=B3=80=ED=99=98=20=ED=98=95=ED=83=9C=EB=A5=BC?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A0=88=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=8B=B4=EB=8B=B9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/DTO/PushNotificationCursorDTO.swift | 4 +- .../Data/DTO/PushNotificationResponse.swift | 8 +-- DevLog/Data/DTO/TodoCursorDTO.swift | 4 +- DevLog/Data/DTO/TodoDTO.swift | 46 +--------------- DevLog/Data/DTO/WebPageDTO.swift | 6 +-- .../Data/Mapper/PushNotificationMapping.swift | 8 ++- DevLog/Data/Mapper/TodoMapping.swift | 6 +-- DevLog/Infra/Extension/FirebaseAuthUser.swift | 2 +- .../Service/PushNotificationService.swift | 33 ++++++++++-- .../AppleAuthenticationService.swift | 2 +- .../GithubAuthenticationService.swift | 2 +- .../GoogleAuthenticationService.swift | 2 +- DevLog/Infra/Service/TodoService.swift | 53 +++++++++++++++++-- DevLog/Infra/Service/WebPageService.swift | 25 +++++++-- 14 files changed, 120 insertions(+), 81 deletions(-) diff --git a/DevLog/Data/DTO/PushNotificationCursorDTO.swift b/DevLog/Data/DTO/PushNotificationCursorDTO.swift index 0d2aaac..ea67ce0 100644 --- a/DevLog/Data/DTO/PushNotificationCursorDTO.swift +++ b/DevLog/Data/DTO/PushNotificationCursorDTO.swift @@ -5,9 +5,9 @@ // Created by 최윤진 on 2/27/26. // -import FirebaseFirestore +import Foundation struct PushNotificationCursorDTO { - let receivedAt: Timestamp + let receivedAt: Date let documentID: String } diff --git a/DevLog/Data/DTO/PushNotificationResponse.swift b/DevLog/Data/DTO/PushNotificationResponse.swift index 2ae7a3f..29288bc 100644 --- a/DevLog/Data/DTO/PushNotificationResponse.swift +++ b/DevLog/Data/DTO/PushNotificationResponse.swift @@ -5,13 +5,13 @@ // Created by 최윤진 on 2/10/26. // -import FirebaseFirestore +import Foundation -struct PushNotificationResponse: Decodable { - @DocumentID var id: String? +struct PushNotificationResponse { + let id: String? let title: String let body: String - let receivedAt: Timestamp + let receivedAt: Date let isRead: Bool let todoID: String let todoKind: String diff --git a/DevLog/Data/DTO/TodoCursorDTO.swift b/DevLog/Data/DTO/TodoCursorDTO.swift index f2899a1..bf99f89 100644 --- a/DevLog/Data/DTO/TodoCursorDTO.swift +++ b/DevLog/Data/DTO/TodoCursorDTO.swift @@ -5,9 +5,9 @@ // Created by opfic on 2/21/26. // -import FirebaseFirestore +import Foundation struct TodoCursorDTO { - let createdAt: Timestamp + let createdAt: Date let documentID: String } diff --git a/DevLog/Data/DTO/TodoDTO.swift b/DevLog/Data/DTO/TodoDTO.swift index f45b0fc..c4e57a1 100644 --- a/DevLog/Data/DTO/TodoDTO.swift +++ b/DevLog/Data/DTO/TodoDTO.swift @@ -6,7 +6,6 @@ // import Foundation -import FirebaseFirestore struct TodoRequest: Dictionaryable { let id: String @@ -23,8 +22,8 @@ struct TodoRequest: Dictionaryable { } -struct TodoResponse: Decodable { - @DocumentID var id: String? +struct TodoResponse { + let id: String? let isPinned: Bool let isCompleted: Bool let isChecked: Bool @@ -35,45 +34,4 @@ struct TodoResponse: Decodable { let dueDate: Date? let tags: [String] let kind: String - - init?(from snapshot: QueryDocumentSnapshot) { - self.init(documentID: snapshot.documentID, data: snapshot.data()) - } - - init?(from snapshot: DocumentSnapshot) { - guard let data = snapshot.data() else { return nil } - self.init(documentID: snapshot.documentID, data: data) - } - - private init?(documentID: String, data: [String: Any]) { - guard - let id = documentID as String?, - let isPinned = data["isPinned"] as? Bool, - let isCompleted = data["isCompleted"] as? Bool, - let isChecked = data["isChecked"] as? Bool, - let title = data["title"] as? String, - let content = data["content"] as? String, - let createdAtTimestamp = data["createdAt"] as? Timestamp, - let updatedAtTimestamp = data["updatedAt"] as? Timestamp, - let tags = data["tags"] as? [String], - let kind = data["kind"] as? String else { - return nil - } - self.id = id - self.isPinned = isPinned - self.isCompleted = isCompleted - self.isChecked = isChecked - self.title = title - self.content = content - self.createdAt = createdAtTimestamp.dateValue() - self.updatedAt = updatedAtTimestamp.dateValue() - if let dueDateTimestamp = data["dueDate"] as? Timestamp { - self.dueDate = dueDateTimestamp.dateValue() - } else { - self.dueDate = nil - } - self.tags = tags - self.kind = kind - } - } diff --git a/DevLog/Data/DTO/WebPageDTO.swift b/DevLog/Data/DTO/WebPageDTO.swift index 9da9998..0349174 100644 --- a/DevLog/Data/DTO/WebPageDTO.swift +++ b/DevLog/Data/DTO/WebPageDTO.swift @@ -5,7 +5,7 @@ // Created by 최윤진 on 2/9/26. // -import FirebaseFirestore +import Foundation struct WebPageRequest: Encodable { let title: String @@ -14,8 +14,8 @@ struct WebPageRequest: Encodable { let imageURL: String } -struct WebPageResponse: Decodable { - @DocumentID var id: String? +struct WebPageResponse { + let id: String? let title: String let url: String let displayURL: String diff --git a/DevLog/Data/Mapper/PushNotificationMapping.swift b/DevLog/Data/Mapper/PushNotificationMapping.swift index 66150e9..d0086d0 100644 --- a/DevLog/Data/Mapper/PushNotificationMapping.swift +++ b/DevLog/Data/Mapper/PushNotificationMapping.swift @@ -5,8 +5,6 @@ // Created by 최윤진 on 2/27/26. // -import FirebaseFirestore - extension PushNotificationResponse { func toDomain() throws -> PushNotification { guard let id = self.id else { @@ -20,7 +18,7 @@ extension PushNotificationResponse { id: id, title: self.title, body: self.body, - receivedAt: self.receivedAt.dateValue(), + receivedAt: self.receivedAt, isRead: self.isRead, todoID: self.todoID, todoKind: todoKind @@ -31,14 +29,14 @@ extension PushNotificationResponse { extension PushNotificationCursorDTO { func toDomain() -> PushNotificationCursor { PushNotificationCursor( - receivedAt: self.receivedAt.dateValue(), + receivedAt: self.receivedAt, documentID: self.documentID ) } static func fromDomain(_ cursor: PushNotificationCursor) -> Self { PushNotificationCursorDTO( - receivedAt: Timestamp(date: cursor.receivedAt), + receivedAt: cursor.receivedAt, documentID: cursor.documentID ) } diff --git a/DevLog/Data/Mapper/TodoMapping.swift b/DevLog/Data/Mapper/TodoMapping.swift index 4caf658..535dd91 100644 --- a/DevLog/Data/Mapper/TodoMapping.swift +++ b/DevLog/Data/Mapper/TodoMapping.swift @@ -5,8 +5,6 @@ // Created by 최윤진 on 2/19/26. // -import FirebaseFirestore - extension TodoRequest { static func fromDomain(_ entity: Todo) -> Self { TodoRequest( @@ -53,14 +51,14 @@ extension TodoResponse { extension TodoCursorDTO { func toDomain() -> TodoCursor { TodoCursor( - createdAt: createdAt.dateValue(), + createdAt: createdAt, documentID: documentID ) } static func fromDomain(_ cursor: TodoCursor) -> Self { TodoCursorDTO( - createdAt: Timestamp(date: cursor.createdAt), + createdAt: cursor.createdAt, documentID: cursor.documentID ) } diff --git a/DevLog/Infra/Extension/FirebaseAuthUser.swift b/DevLog/Infra/Extension/FirebaseAuthUser.swift index a2b3e2d..7aea4ba 100644 --- a/DevLog/Infra/Extension/FirebaseAuthUser.swift +++ b/DevLog/Infra/Extension/FirebaseAuthUser.swift @@ -9,7 +9,7 @@ import Foundation import FirebaseAuth extension FirebaseAuth.User { - func toResponse( + func makeResponse( providerID: AuthProviderID, fcmToken: String, accessToken: String? = nil diff --git a/DevLog/Infra/Service/PushNotificationService.swift b/DevLog/Infra/Service/PushNotificationService.swift index 4a3f722..c31d42f 100644 --- a/DevLog/Infra/Service/PushNotificationService.swift +++ b/DevLog/Infra/Service/PushNotificationService.swift @@ -115,7 +115,7 @@ final class PushNotificationService { if let cursor { firestoreQuery = firestoreQuery.start(after: [ - cursor.receivedAt, + Timestamp(date: cursor.receivedAt), cursor.documentID ]) } @@ -124,9 +124,7 @@ final class PushNotificationService { .limit(to: query.pageSize) .getDocuments() - let items = try snapshot.documents.compactMap { document in - try document.data(as: PushNotificationResponse.self) - } + let items = snapshot.documents.compactMap { makeResponse(from: $0) } let nextCursor: PushNotificationCursorDTO? = snapshot.documents.last.map { document in guard let receivedAt = document.data()["receivedAt"] as? Timestamp else { @@ -134,7 +132,7 @@ final class PushNotificationService { } return PushNotificationCursorDTO( - receivedAt: receivedAt, + receivedAt: receivedAt.dateValue(), documentID: document.documentID ) } ?? nil @@ -177,3 +175,28 @@ final class PushNotificationService { logger.info("Successfully toggled notification read") } } + +private extension PushNotificationService { + func makeResponse(from snapshot: QueryDocumentSnapshot) -> PushNotificationResponse? { + let data = snapshot.data() + guard + let title = data["title"] as? String, + let body = data["body"] as? String, + let receivedAt = data["receivedAt"] as? Timestamp, + let isRead = data["isRead"] as? Bool, + let todoID = data["todoID"] as? String, + let todoKind = data["todoKind"] as? String else { + return nil + } + + return PushNotificationResponse( + id: snapshot.documentID, + title: title, + body: body, + receivedAt: receivedAt.dateValue(), + isRead: isRead, + todoID: todoID, + todoKind: todoKind + ) + } +} diff --git a/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift index c8b9df6..d7afdb9 100644 --- a/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift @@ -81,7 +81,7 @@ final class AppleAuthenticationService: AuthenticationService { let fcmToken = try await messaging.token() logger.info("Successfully signed in with Apple") - return result.user.toResponse(providerID: .apple, fcmToken: fcmToken) + return result.user.makeResponse(providerID: .apple, fcmToken: fcmToken) } catch { logger.error("Failed to sign in with Apple", error: error) throw error diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index 5a00f15..340ecbb 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -56,7 +56,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { let fcmToken = try await messaging.token() logger.info("Successfully signed in with GitHub") - return result.user.toResponse( + return result.user.makeResponse( providerID: .gitHub, fcmToken: fcmToken, accessToken: accessToken diff --git a/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift index e297eff..31e99b0 100644 --- a/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift @@ -54,7 +54,7 @@ final class GoogleAuthenticationService: AuthenticationService { let fcmToken = try await messaging.token() logger.info("Successfully signed in with Google") - return result.user.toResponse(providerID: .google, fcmToken: fcmToken) + return result.user.makeResponse(providerID: .google, fcmToken: fcmToken) } catch { logger.error("Failed to sign in with Google", error: error) throw error diff --git a/DevLog/Infra/Service/TodoService.swift b/DevLog/Infra/Service/TodoService.swift index 898f0bc..c9841a6 100644 --- a/DevLog/Infra/Service/TodoService.swift +++ b/DevLog/Infra/Service/TodoService.swift @@ -41,7 +41,7 @@ final class TodoService { if trimmedKeyword.isEmpty { if let cursor { firestoreQuery = firestoreQuery.start(after: [ - cursor.createdAt, + Timestamp(date: cursor.createdAt), cursor.documentID ]) } @@ -50,7 +50,7 @@ final class TodoService { .limit(to: query.pageSize) .getDocuments() - let items = snapshot.documents.compactMap { TodoResponse(from: $0) } + let items = snapshot.documents.compactMap { makeResponse(from: $0) } let nextCursor: TodoCursorDTO? = snapshot.documents.last.flatMap { document in guard let createdAt = document.data()["createdAt"] as? Timestamp else { @@ -58,7 +58,7 @@ final class TodoService { } return TodoCursorDTO( - createdAt: createdAt, + createdAt: createdAt.dateValue(), documentID: document.documentID ) } @@ -67,7 +67,7 @@ final class TodoService { } let snapshot = try await firestoreQuery.getDocuments() - let todos = snapshot.documents.compactMap { TodoResponse(from: $0) } + let todos = snapshot.documents.compactMap { makeResponse(from: $0) } let filtered = todos.filter { todo in todo.title.localizedCaseInsensitiveContains(trimmedKeyword) @@ -120,7 +120,7 @@ final class TodoService { do { let docRef = store.collection("users/\(uid)/todoLists/").document(todoID) let snapshot = try await docRef.getDocument() - guard snapshot.exists, let todo = TodoResponse(from: snapshot) else { + guard snapshot.exists, let todo = makeResponse(from: snapshot) else { throw FirestoreError.dataNotFound("Todo") } @@ -132,3 +132,46 @@ final class TodoService { } } } + +private extension TodoService { + func makeResponse(from snapshot: QueryDocumentSnapshot) -> TodoResponse? { + makeResponse(documentID: snapshot.documentID, data: snapshot.data()) + } + + func makeResponse(from snapshot: DocumentSnapshot) -> TodoResponse? { + guard let data = snapshot.data() else { + return nil + } + return makeResponse(documentID: snapshot.documentID, data: data) + } + + func makeResponse(documentID: String, data: [String: Any]) -> TodoResponse? { + guard + let isPinned = data["isPinned"] as? Bool, + let isCompleted = data["isCompleted"] as? Bool, + let isChecked = data["isChecked"] as? Bool, + let title = data["title"] as? String, + let content = data["content"] as? String, + let createdAtTimestamp = data["createdAt"] as? Timestamp, + let updatedAtTimestamp = data["updatedAt"] as? Timestamp, + let tags = data["tags"] as? [String], + let kind = data["kind"] as? String else { + return nil + } + + let dueDate = (data["dueDate"] as? Timestamp)?.dateValue() + return TodoResponse( + id: documentID, + isPinned: isPinned, + isCompleted: isCompleted, + isChecked: isChecked, + title: title, + content: content, + createdAt: createdAtTimestamp.dateValue(), + updatedAt: updatedAtTimestamp.dateValue(), + dueDate: dueDate, + tags: tags, + kind: kind + ) + } +} diff --git a/DevLog/Infra/Service/WebPageService.swift b/DevLog/Infra/Service/WebPageService.swift index 42db6a3..053ef39 100644 --- a/DevLog/Infra/Service/WebPageService.swift +++ b/DevLog/Infra/Service/WebPageService.swift @@ -24,9 +24,7 @@ final class WebPageService { do { let collectionRef = store.collection("users/\(uid)/webPages") let snapshot = try await collectionRef.getDocuments() - let items: [WebPageResponse] = snapshot.documents.compactMap { doc in - try? doc.data(as: WebPageResponse.self) - } + let items: [WebPageResponse] = snapshot.documents.compactMap { makeResponse(from: $0) } let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedQuery.isEmpty else { @@ -98,3 +96,24 @@ final class WebPageService { .replacingOccurrences(of: "=", with: "") } } + +private extension WebPageService { + func makeResponse(from snapshot: QueryDocumentSnapshot) -> WebPageResponse? { + let data = snapshot.data() + guard + let title = data["title"] as? String, + let url = data["url"] as? String, + let displayURL = data["displayURL"] as? String, + let imageURL = data["imageURL"] as? String else { + return nil + } + + return WebPageResponse( + id: snapshot.documentID, + title: title, + url: url, + displayURL: displayURL, + imageURL: imageURL + ) + } +} From b557883d567d5685600d5a22076ddd533734823a Mon Sep 17 00:00:00 2001 From: opficdev Date: Sat, 28 Feb 2026 12:22:34 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20id=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=97=90=20=EC=98=B5=EC=85=94=EB=84=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Data/DTO/PushNotificationResponse.swift | 2 +- DevLog/Data/DTO/TodoDTO.swift | 2 +- DevLog/Data/DTO/WebPageDTO.swift | 2 +- DevLog/Data/Mapper/PushNotificationMapping.swift | 3 --- DevLog/Data/Mapper/TodoMapping.swift | 3 --- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/DevLog/Data/DTO/PushNotificationResponse.swift b/DevLog/Data/DTO/PushNotificationResponse.swift index 29288bc..3a791b7 100644 --- a/DevLog/Data/DTO/PushNotificationResponse.swift +++ b/DevLog/Data/DTO/PushNotificationResponse.swift @@ -8,7 +8,7 @@ import Foundation struct PushNotificationResponse { - let id: String? + let id: String let title: String let body: String let receivedAt: Date diff --git a/DevLog/Data/DTO/TodoDTO.swift b/DevLog/Data/DTO/TodoDTO.swift index c4e57a1..feab2b7 100644 --- a/DevLog/Data/DTO/TodoDTO.swift +++ b/DevLog/Data/DTO/TodoDTO.swift @@ -23,7 +23,7 @@ struct TodoRequest: Dictionaryable { } struct TodoResponse { - let id: String? + let id: String let isPinned: Bool let isCompleted: Bool let isChecked: Bool diff --git a/DevLog/Data/DTO/WebPageDTO.swift b/DevLog/Data/DTO/WebPageDTO.swift index 0349174..980ec25 100644 --- a/DevLog/Data/DTO/WebPageDTO.swift +++ b/DevLog/Data/DTO/WebPageDTO.swift @@ -15,7 +15,7 @@ struct WebPageRequest: Encodable { } struct WebPageResponse { - let id: String? + let id: String let title: String let url: String let displayURL: String diff --git a/DevLog/Data/Mapper/PushNotificationMapping.swift b/DevLog/Data/Mapper/PushNotificationMapping.swift index d0086d0..617aeba 100644 --- a/DevLog/Data/Mapper/PushNotificationMapping.swift +++ b/DevLog/Data/Mapper/PushNotificationMapping.swift @@ -7,9 +7,6 @@ extension PushNotificationResponse { func toDomain() throws -> PushNotification { - guard let id = self.id else { - throw DataError.invalidData("PushNotificationResponse.id is nil") - } guard let todoKind = TodoKind(rawValue: self.todoKind) else { throw DataError.invalidData("PushNotificationResponse.todoKind is invalid: \(self.todoKind)") } diff --git a/DevLog/Data/Mapper/TodoMapping.swift b/DevLog/Data/Mapper/TodoMapping.swift index 535dd91..9cb8dbf 100644 --- a/DevLog/Data/Mapper/TodoMapping.swift +++ b/DevLog/Data/Mapper/TodoMapping.swift @@ -25,9 +25,6 @@ extension TodoRequest { extension TodoResponse { func toDomain() throws -> Todo { - guard let id = self.id else { - throw DataError.invalidData("TodoResponse.id is nil") - } guard let kind = TodoKind(rawValue: self.kind) else { throw DataError.invalidData("TodoResponse.kind is invalid: \(self.kind)") } From 670494740fe36df878c545f78a0f8e7930dba5d8 Mon Sep 17 00:00:00 2001 From: opficdev Date: Sat, 28 Feb 2026 12:34:35 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20key=EB=A5=BC=20enum=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/PushNotificationService.swift | 23 ++++++++---- DevLog/Infra/Service/TodoService.swift | 35 +++++++++++++------ DevLog/Infra/Service/WebPageService.swift | 15 +++++--- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/DevLog/Infra/Service/PushNotificationService.swift b/DevLog/Infra/Service/PushNotificationService.swift index c31d42f..1a0dac2 100644 --- a/DevLog/Infra/Service/PushNotificationService.swift +++ b/DevLog/Infra/Service/PushNotificationService.swift @@ -127,7 +127,7 @@ final class PushNotificationService { let items = snapshot.documents.compactMap { makeResponse(from: $0) } let nextCursor: PushNotificationCursorDTO? = snapshot.documents.last.map { document in - guard let receivedAt = document.data()["receivedAt"] as? Timestamp else { + guard let receivedAt = document.data()[NotificationFieldKey.receivedAt.rawValue] as? Timestamp else { return nil } @@ -180,12 +180,12 @@ private extension PushNotificationService { func makeResponse(from snapshot: QueryDocumentSnapshot) -> PushNotificationResponse? { let data = snapshot.data() guard - let title = data["title"] as? String, - let body = data["body"] as? String, - let receivedAt = data["receivedAt"] as? Timestamp, - let isRead = data["isRead"] as? Bool, - let todoID = data["todoID"] as? String, - let todoKind = data["todoKind"] as? String else { + let title = data[NotificationFieldKey.title.rawValue] as? String, + let body = data[NotificationFieldKey.body.rawValue] as? String, + let receivedAt = data[NotificationFieldKey.receivedAt.rawValue] as? Timestamp, + let isRead = data[NotificationFieldKey.isRead.rawValue] as? Bool, + let todoID = data[NotificationFieldKey.todoID.rawValue] as? String, + let todoKind = data[NotificationFieldKey.todoKind.rawValue] as? String else { return nil } @@ -199,4 +199,13 @@ private extension PushNotificationService { todoKind: todoKind ) } + + enum NotificationFieldKey: String { + case title + case body + case receivedAt + case isRead + case todoID + case todoKind + } } diff --git a/DevLog/Infra/Service/TodoService.swift b/DevLog/Infra/Service/TodoService.swift index c9841a6..049bfb1 100644 --- a/DevLog/Infra/Service/TodoService.swift +++ b/DevLog/Infra/Service/TodoService.swift @@ -53,7 +53,7 @@ final class TodoService { let items = snapshot.documents.compactMap { makeResponse(from: $0) } let nextCursor: TodoCursorDTO? = snapshot.documents.last.flatMap { document in - guard let createdAt = document.data()["createdAt"] as? Timestamp else { + guard let createdAt = document.data()[TodoFieldKey.createdAt.rawValue] as? Timestamp else { return nil } @@ -147,19 +147,19 @@ private extension TodoService { func makeResponse(documentID: String, data: [String: Any]) -> TodoResponse? { guard - let isPinned = data["isPinned"] as? Bool, - let isCompleted = data["isCompleted"] as? Bool, - let isChecked = data["isChecked"] as? Bool, - let title = data["title"] as? String, - let content = data["content"] as? String, - let createdAtTimestamp = data["createdAt"] as? Timestamp, - let updatedAtTimestamp = data["updatedAt"] as? Timestamp, - let tags = data["tags"] as? [String], - let kind = data["kind"] as? String else { + 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 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 kind = data[TodoFieldKey.kind.rawValue] as? String else { return nil } - let dueDate = (data["dueDate"] as? Timestamp)?.dateValue() + let dueDate = (data[TodoFieldKey.dueDate.rawValue] as? Timestamp)?.dateValue() return TodoResponse( id: documentID, isPinned: isPinned, @@ -174,4 +174,17 @@ private extension TodoService { kind: kind ) } + + enum TodoFieldKey: String { + case isPinned + case isCompleted + case isChecked + case title + case content + case createdAt + case updatedAt + case dueDate + case tags + case kind + } } diff --git a/DevLog/Infra/Service/WebPageService.swift b/DevLog/Infra/Service/WebPageService.swift index 053ef39..35a4c4c 100644 --- a/DevLog/Infra/Service/WebPageService.swift +++ b/DevLog/Infra/Service/WebPageService.swift @@ -101,10 +101,10 @@ private extension WebPageService { func makeResponse(from snapshot: QueryDocumentSnapshot) -> WebPageResponse? { let data = snapshot.data() guard - let title = data["title"] as? String, - let url = data["url"] as? String, - let displayURL = data["displayURL"] as? String, - let imageURL = data["imageURL"] as? String else { + let title = data[WebPageFieldKey.title.rawValue] as? String, + let url = data[WebPageFieldKey.url.rawValue] as? String, + let displayURL = data[WebPageFieldKey.displayURL.rawValue] as? String, + let imageURL = data[WebPageFieldKey.imageURL.rawValue] as? String else { return nil } @@ -116,4 +116,11 @@ private extension WebPageService { imageURL: imageURL ) } + + enum WebPageFieldKey: String { + case title + case url + case displayURL + case imageURL + } } From b8ca6023b49367629968ce48c7c93d97824b5edf Mon Sep 17 00:00:00 2001 From: opficdev Date: Sat, 28 Feb 2026 12:49:44 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20Dictionaryable=EC=9D=84=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=ED=95=98=EA=B3=A0=20=EC=97=94=EC=BD=94?= =?UTF-8?q?=EB=8D=94=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Data/DTO/TodoDTO.swift | 2 +- DevLog/Data/Protocol/Dictionaryable.swift | 35 ----------------------- DevLog/Infra/Service/TodoService.swift | 9 +++++- DevLog/Infra/Service/WebPageService.swift | 3 +- 4 files changed, 11 insertions(+), 38 deletions(-) delete mode 100644 DevLog/Data/Protocol/Dictionaryable.swift diff --git a/DevLog/Data/DTO/TodoDTO.swift b/DevLog/Data/DTO/TodoDTO.swift index feab2b7..4402e02 100644 --- a/DevLog/Data/DTO/TodoDTO.swift +++ b/DevLog/Data/DTO/TodoDTO.swift @@ -7,7 +7,7 @@ import Foundation -struct TodoRequest: Dictionaryable { +struct TodoRequest: Encodable { let id: String let isPinned: Bool let isCompleted: Bool diff --git a/DevLog/Data/Protocol/Dictionaryable.swift b/DevLog/Data/Protocol/Dictionaryable.swift deleted file mode 100644 index fd7e1c8..0000000 --- a/DevLog/Data/Protocol/Dictionaryable.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Dictionaryable.swift -// DevLog -// -// Created by 최윤진 on 12/14/25. -// - -import FirebaseFirestore - -protocol Dictionaryable: Encodable { - func toDictionary() -> [String: Any] -} - -extension Dictionaryable { - func toDictionary() -> [String: Any] { - let encoder = Firestore.Encoder() - guard var dictionary = try? encoder.encode(self) else { return [:] } - - let mirror = Mirror(reflecting: self) - for child in mirror.children { - guard let key = child.label else { continue } - if isNilValue(child.value) { - dictionary[key] = NSNull() - } - } - - dictionary.removeValue(forKey: "id") - return dictionary - } - - private func isNilValue(_ value: Any) -> Bool { - let mirror = Mirror(reflecting: value) - return mirror.displayStyle == .optional && mirror.children.isEmpty - } -} diff --git a/DevLog/Infra/Service/TodoService.swift b/DevLog/Infra/Service/TodoService.swift index 049bfb1..98668d9 100644 --- a/DevLog/Infra/Service/TodoService.swift +++ b/DevLog/Infra/Service/TodoService.swift @@ -10,6 +10,7 @@ import FirebaseFirestore final class TodoService { private let store = Firestore.firestore() + private let encoder = Firestore.Encoder() private let logger = Logger(category: "TodoService") func fetchTodos( @@ -86,7 +87,12 @@ final class TodoService { do { let collection = store.collection("users/\(uid)/todoLists/") let docRef = collection.document(request.id) - try await docRef.setData(request.toDictionary(), merge: true) + var data = try encoder.encode(request) + data.removeValue(forKey: TodoFieldKey.id.rawValue) + if request.dueDate == nil { + data[TodoFieldKey.dueDate.rawValue] = NSNull() + } + try await docRef.setData(data, merge: true) logger.info("Successfully upserted todo") } catch { @@ -176,6 +182,7 @@ private extension TodoService { } enum TodoFieldKey: String { + case id case isPinned case isCompleted case isChecked diff --git a/DevLog/Infra/Service/WebPageService.swift b/DevLog/Infra/Service/WebPageService.swift index 35a4c4c..f2b05c9 100644 --- a/DevLog/Infra/Service/WebPageService.swift +++ b/DevLog/Infra/Service/WebPageService.swift @@ -10,6 +10,7 @@ import FirebaseFirestore final class WebPageService { private let store = Firestore.firestore() + private let encoder = Firestore.Encoder() private let logger = Logger(category: "WebPageService") /// 저장한 웹페이지를 모두 불러옴 @@ -56,7 +57,7 @@ final class WebPageService { do { let documentID = documentID(for: request.url) let docRef = store.document("users/\(uid)/webPages/\(documentID)") - let data = try Firestore.Encoder().encode(request) + let data = try encoder.encode(request) try await docRef.setData(data, merge: true) logger.info("Successfully upserted web page") } catch {