Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions DevLog/Data/Common/Error+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,24 @@ enum FirestoreError: Error, LocalizedError {
enum UIError: Error {
case notFoundTopViewController
}

enum DataError: Error {
case invalidData(context: String)

private static let logger = Logger(category: "DataError")

static func invalidData(
_ context: String,
file: String = #file,
function: String = #function,
line: Int = #line
) -> DataError {
logger.error(
"Invalid data: \(context)",
file: file,
function: function,
line: line
)
return .invalidData(context: context)
}
}
24 changes: 0 additions & 24 deletions DevLog/Data/DTO/UserProfile+.swift

This file was deleted.

49 changes: 49 additions & 0 deletions DevLog/Data/Mapper/TodoMapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// TodoMapping.swift
// DevLog
//
// Created by 최윤진 on 2/19/26.
//

extension TodoRequest {
static func fromDomain(_ entity: Todo) -> Self {
TodoRequest(
id: entity.id,
isPinned: entity.isPinned,
isCompleted: entity.isCompleted,
isChecked: entity.isChecked,
title: entity.title,
content: entity.content,
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
dueDate: entity.dueDate,
tags: entity.tags,
kind: entity.kind
)
}
}

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)")
}

return Todo(
id: id,
isPinned: self.isPinned,
isCompleted: self.isCompleted,
isChecked: self.isChecked,
title: self.title,
content: self.content,
createdAt: self.createdAt,
updatedAt: self.updatedAt,
dueDate: self.dueDate,
tags: self.tags,
kind: kind
)
}
}
17 changes: 17 additions & 0 deletions DevLog/Data/Mapper/UserProfileMapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// UserProfileMapping.swift
// DevLog
//
// Created by 최윤진 on 2/19/26.
//

extension UserProfileResponse {
func toDomain() -> UserProfile {
UserProfile(
name: self.name,
email: self.email,
statusMessage: self.statusMessage,
avatarURL: self.avatarURL
)
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
//
// WebPageMetadata.swift
// WebPageMapping.swift
// DevLog
//
// Created by 최윤진 on 2/9/26.
// Created by 최윤진 on 2/19/26.
//

import Foundation

struct WebPageMetadata: Hashable {
let title: String?
let url: URL
let displayURL: URL
let imageURL: URL?

extension WebPageResponse {
func toDomain() -> WebPage {
WebPage(
title: title,
Expand Down
2 changes: 1 addition & 1 deletion DevLog/Data/Protocol/AuthenticationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

protocol AuthenticationService {
func signIn() async throws -> AuthenticationDataResponse
func signIn() async throws -> AuthDataResponse
func signOut(_ uid: String) async throws
func deleteAuth(_ uid: String) async throws
func link(uid: String, email: String) async throws
Expand Down
2 changes: 1 addition & 1 deletion DevLog/Data/Repository/AuthenticationRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository {
}

func signIn(_ provider: AuthProvider) async throws {
let response: AuthenticationDataResponse
let response: AuthDataResponse
switch provider {
case .apple:
response = try await appleAuthService.signIn()
Expand Down
10 changes: 7 additions & 3 deletions DevLog/Data/Repository/TodoRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ final class TodoRepositoryImpl: TodoRepository {

func fetchTodos(_ kind: TodoKind) async throws -> [Todo] {
let response = try await todoService.fetchTodos(kind: kind)
return response.map { $0.toDomain() }
return try response.map { item in
try item.toDomain()
}
}

func fetchPinnedTodos() async throws -> [Todo] {
let response = try await todoService.fetchPinnedTodos()
return response.map { $0.toDomain() }
return try response.map { item in
try item.toDomain()
}
}

func fetchTodo(_ todoID: String) async throws -> Todo {
let response = try await todoService.fetchTodo(todoID: todoID)
return response.toDomain()
return try response.toDomain()
}

func upsertTodo(_ todo: Todo) async throws {
Expand Down
15 changes: 8 additions & 7 deletions DevLog/Data/Repository/WebPageRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,32 @@ final class WebPageRepositoryImpl: WebPageRepository {
self.metadataService = metadataService
}

func fetch() async throws -> [WebPageMetadata] {
func fetch() async throws -> [WebPage] {
let responses = try await webPageService.fetchWebPages()
let indexedResponses = responses.enumerated().map { ($0.offset, $0.element) }

return try await withThrowingTaskGroup(of: (Int, WebPageMetadata?).self) { group in
return try await withThrowingTaskGroup(of: (Int, WebPageResponse?).self) { group in
for (index, response) in indexedResponses {
group.addTask {
let metadata = try? await self.metadataService.fetchMetadata(from: response)
return (index, metadata)
}
}

var results: [WebPageMetadata?] = Array(repeating: nil, count: responses.count)
var results: [WebPageResponse?] = Array(repeating: nil, count: responses.count)
for try await (index, metadata) in group {
results[index] = metadata
}

return results.compactMap { $0 }
return results.compactMap { $0?.toDomain() }
}
}

func upsert(_ urlString: String) async throws -> WebPageMetadata {
func upsert(_ urlString: String) async throws -> WebPage {
try await webPageService.upsertWebPage(urlString)
let response = WebPageResponse(urlString: urlString)
return try await metadataService.fetchMetadata(from: response)
let response = WebPageURLResponse(urlString: urlString)
let metadata = try await metadataService.fetchMetadata(from: response)
return metadata.toDomain()
}

func delete(_ urlString: String) async throws {
Expand Down
4 changes: 2 additions & 2 deletions DevLog/Domain/Protocol/WebPageRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

protocol WebPageRepository {
func fetch() async throws -> [WebPageMetadata]
func upsert(_ urlString: String) async throws -> WebPageMetadata
func fetch() async throws -> [WebPage]
func upsert(_ urlString: String) async throws -> WebPage
func delete(_ urlString: String) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ final class FetchWebPagesUseCaseImpl: FetchWebPagesUseCase {
}

func execute() async throws -> [WebPage] {
return try await repository.fetch().map { $0.toDomain() }
try await repository.fetch()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ final class AddWebPageUseCaseImpl: AddWebPageUseCase {
}

func execute(_ urlString: String) async throws -> WebPage {
try await repository.upsert(urlString).toDomain()
try await repository.upsert(urlString)
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//
// AuthenticationData+.swift
// AuthDataResponse.swift
// DevLog
//
// Created by 최윤진 on 11/2/25.
//

import Foundation

struct AuthenticationDataResponse {
struct AuthDataResponse {
let uid: String
let displayName: String?
let email: String?
Expand Down
52 changes: 13 additions & 39 deletions DevLog/Data/DTO/Todo+.swift → DevLog/Infra/DTO/TodoDTO.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Todo+.swift
// TodoDTO.swift
// DevLog
//
// Created by 최윤진 on 12/14/25.
Expand All @@ -21,21 +21,6 @@ struct TodoRequest: Dictionaryable {
let tags: [String]
let kind: TodoKind

static func fromDomain(_ entity: Todo) -> Self {
TodoRequest(
id: entity.id,
isPinned: entity.isPinned,
isCompleted: entity.isCompleted,
isChecked: entity.isChecked,
title: entity.title,
content: entity.content,
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
dueDate: entity.dueDate,
tags: entity.tags,
kind: entity.kind
)
}
}

struct TodoResponse: Decodable {
Expand All @@ -45,9 +30,9 @@ struct TodoResponse: Decodable {
let isChecked: Bool
let title: String
let content: String
let createdAt: Timestamp
let updatedAt: Timestamp
let dueDate: Timestamp?
let createdAt: Date
let updatedAt: Date
let dueDate: Date?
let tags: [String]
let kind: String

Expand All @@ -68,8 +53,8 @@ struct TodoResponse: Decodable {
let isChecked = data["isChecked"] as? Bool,
let title = data["title"] as? String,
let content = data["content"] as? String,
let createdAt = data["createdAt"] as? Timestamp,
let updatedAt = data["updatedAt"] as? Timestamp,
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
Expand All @@ -80,26 +65,15 @@ struct TodoResponse: Decodable {
self.isChecked = isChecked
self.title = title
self.content = content
self.createdAt = createdAt
self.updatedAt = updatedAt
self.dueDate = data["dueDate"] as? Timestamp
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
}

func toDomain() -> Todo {
Todo(
id: self.id == nil ? UUID().uuidString : self.id!,
isPinned: self.isPinned,
isCompleted: self.isCompleted,
isChecked: self.isChecked,
title: self.title,
content: self.content,
createdAt: self.createdAt.dateValue(),
updatedAt: self.updatedAt.dateValue(),
dueDate: self.dueDate?.dateValue(),
tags: self.tags,
kind: TodoKind(rawValue: self.kind) ?? .etc
)
}
}
15 changes: 15 additions & 0 deletions DevLog/Infra/DTO/UserProfileResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// UserProfileResponse.swift
// DevLog
//
// Created by 최윤진 on 1/10/26.
//

import Foundation

struct UserProfileResponse: Decodable {
let name: String
let email: String
let statusMessage: String
let avatarURL: URL?
}
7 changes: 6 additions & 1 deletion DevLog/Infra/DTO/WebPageResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
// Created by 최윤진 on 2/9/26.
//

import Foundation

struct WebPageResponse {
let urlString: String
let title: String?
let url: URL
let displayURL: URL
let imageURL: URL?
}
10 changes: 10 additions & 0 deletions DevLog/Infra/DTO/WebPageURLResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// WebPageURLResponse.swift
// DevLog
//
// Created by 최윤진 on 2/9/26.
//

struct WebPageURLResponse {
let urlString: String
}
Loading