Skip to content

Commit 10b5d13

Browse files
committed
fix: 푸시알림 데이터가 시스템 카테고리만 디코딩 가능한 현상 해결
1 parent 1ba4346 commit 10b5d13

7 files changed

Lines changed: 148 additions & 25 deletions

File tree

DevLog/App/Assembler/DataAssembler.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ final class DataAssembler: Assembler {
7474
}
7575

7676
container.register(PushNotificationRepository.self) {
77-
PushNotificationRepositoryImpl(pushNotificationService: container.resolve(PushNotificationService.self))
77+
PushNotificationRepositoryImpl(
78+
pushNotificationService: container.resolve(PushNotificationService.self),
79+
todoCategoryService: container.resolve(TodoCategoryService.self)
80+
)
7881
}
7982

8083
container.register(WebPageRepository.self) {

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: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77

88
import Foundation
99

10-
enum TodoCategoryResponse {
11-
case raw(String)
12-
case decoded(TodoCategory)
13-
}
14-
1510
struct TodoRequest: Encodable {
1611
let id: String
1712
let isPinned: Bool

DevLog/Data/Mapper/PushNotificationMapping.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@
77

88
extension PushNotificationResponse {
99
func toDomain() throws -> PushNotification {
10-
guard let todoCategory = SystemTodoCategory(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+
guard let systemTodoCategory = SystemTodoCategory(rawValue: category) else {
17+
throw DataError.invalidData("PushNotificationResponse.todoCategory is invalid: \(category)")
18+
}
19+
20+
todoCategory = .system(systemTodoCategory)
1221
}
1322

1423
return PushNotification(
@@ -18,7 +27,7 @@ extension PushNotificationResponse {
1827
receivedAt: self.receivedAt,
1928
isRead: self.isRead,
2029
todoId: self.todoId,
21-
todoCategory: .system(todoCategory)
30+
todoCategory: todoCategory
2231
)
2332
}
2433
}

DevLog/Data/Repository/PushNotificationRepositoryImpl.swift

Lines changed: 117 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,30 @@ import Foundation
99
import Combine
1010

1111
final class PushNotificationRepositoryImpl: PushNotificationRepository {
12-
private let service: PushNotificationService
12+
private let pushNotificationService: PushNotificationService
13+
private let todoCategoryService: TodoCategoryService
1314

14-
init(pushNotificationService: PushNotificationService) {
15-
self.service = pushNotificationService
15+
init(
16+
pushNotificationService: PushNotificationService,
17+
todoCategoryService: TodoCategoryService
18+
) {
19+
self.pushNotificationService = pushNotificationService
20+
self.todoCategoryService = todoCategoryService
1621
}
1722

1823
/// 푸시 알림 On/Off 설정
1924
func fetchPushNotificationEnabled() async throws -> Bool {
20-
return try await service.fetchPushNotificationEnabled()
25+
return try await pushNotificationService.fetchPushNotificationEnabled()
2126
}
2227

2328
/// 푸시 알림 시간 설정
2429
func fetchPushNotificationTime() async throws -> DateComponents {
25-
return try await service.fetchPushNotificationTime()
30+
return try await pushNotificationService.fetchPushNotificationTime()
2631
}
2732

2833
/// 푸시 알림 설정 업데이트
2934
func updatePushNotificationSettings(_ settings: PushNotificationSettings) async throws {
30-
try await service.updatePushNotificationSettings(
35+
try await pushNotificationService.updatePushNotificationSettings(
3136
isEnabled: settings.isEnabled, components: settings.scheduledTime
3237
)
3338
}
@@ -38,35 +43,133 @@ final class PushNotificationRepositoryImpl: PushNotificationRepository {
3843
cursor: PushNotificationCursor?
3944
) async throws -> PushNotificationPage {
4045
let cursorDTO = cursor.map { PushNotificationCursorDTO.fromDomain($0) }
41-
let response = try await service.requestNotifications(query, cursor: cursorDTO)
42-
return try response.toDomain()
46+
async let responseTask = pushNotificationService.requestNotifications(query, cursor: cursorDTO)
47+
async let preferencesTask = todoCategoryService.fetchPreferences()
48+
49+
let (response, preferences) = try await (responseTask, preferencesTask)
50+
let userTodoCategories: [UserTodoCategory] = preferences.compactMap { preference in
51+
guard case .user(let userTodoCategory) = preference.category else {
52+
return nil
53+
}
54+
55+
return userTodoCategory
56+
}
57+
58+
let responses = try response.items.map {
59+
try resolve($0, userTodoCategories: userTodoCategories)
60+
}
61+
62+
return try PushNotificationPageResponse(
63+
items: responses,
64+
nextCursor: response.nextCursor
65+
).toDomain()
4366
}
4467

4568
func observeNotifications(
4669
_ query: PushNotificationQuery,
4770
limit: Int
4871
) throws -> AnyPublisher<PushNotificationPage, Error> {
49-
try service.observeNotifications(query, limit: limit)
50-
.tryMap { try $0.toDomain() }
72+
let subject = PassthroughSubject<PushNotificationPage, Error>()
73+
var cancellable: AnyCancellable?
74+
75+
cancellable = try pushNotificationService.observeNotifications(query, limit: limit)
76+
.sink(
77+
receiveCompletion: { completion in
78+
switch completion {
79+
case .finished:
80+
subject.send(completion: .finished)
81+
case .failure(let error):
82+
subject.send(completion: .failure(error))
83+
}
84+
},
85+
receiveValue: { [weak self] response in
86+
guard let self else { return }
87+
88+
Task {
89+
do {
90+
let preferences = try await self.todoCategoryService.fetchPreferences()
91+
let userTodoCategories: [UserTodoCategory] = preferences.compactMap { preference in
92+
guard case .user(let userTodoCategory) = preference.category else {
93+
return nil
94+
}
95+
96+
return userTodoCategory
97+
}
98+
99+
let responses = try response.items.map {
100+
try self.resolve($0, userTodoCategories: userTodoCategories)
101+
}
102+
103+
let page = try PushNotificationPageResponse(
104+
items: responses,
105+
nextCursor: response.nextCursor
106+
).toDomain()
107+
108+
subject.send(page)
109+
} catch {
110+
subject.send(completion: .failure(error))
111+
}
112+
}
113+
}
114+
)
115+
116+
return subject
117+
.handleEvents(receiveCancel: { cancellable?.cancel() })
51118
.eraseToAnyPublisher()
52119
}
53120

54121
func observeUnreadPushCount() throws -> AnyPublisher<Int, Error> {
55-
try service.observeUnreadPushCount()
122+
try pushNotificationService.observeUnreadPushCount()
56123
.eraseToAnyPublisher()
57124
}
58125

59126
// 푸시 알림 기록 삭제
60127
func deleteNotification(_ notificationID: String) async throws {
61-
try await service.deleteNotification(notificationID)
128+
try await pushNotificationService.deleteNotification(notificationID)
62129
}
63130

64131
func undoDeleteNotification(_ notificationID: String) async throws {
65-
try await service.undoDeleteNotification(notificationID)
132+
try await pushNotificationService.undoDeleteNotification(notificationID)
66133
}
67134

68135
// 푸시 알림 읽음/안읽음 토글
69136
func toggleNotificationRead(_ todoId: String) async throws {
70-
try await service.toggleNotificationRead(todoId)
137+
try await pushNotificationService.toggleNotificationRead(todoId)
138+
}
139+
}
140+
141+
private extension PushNotificationRepositoryImpl {
142+
func resolve(
143+
_ response: PushNotificationResponse,
144+
userTodoCategories: [UserTodoCategory]
145+
) throws -> PushNotificationResponse {
146+
let categoryName: String
147+
switch response.todoCategory {
148+
case .raw(let rawValue):
149+
categoryName = rawValue
150+
case .decoded:
151+
return response
152+
}
153+
154+
let todoCategory: TodoCategory
155+
if let systemTodoCategory = SystemTodoCategory(rawValue: categoryName) {
156+
todoCategory = .system(systemTodoCategory)
157+
} else if let userTodoCategory = userTodoCategories.first(where: {
158+
$0.name == categoryName
159+
}) {
160+
todoCategory = .user(userTodoCategory)
161+
} else {
162+
throw DataError.invalidData("PushNotificationResponse.todoCategory is invalid: \(categoryName)")
163+
}
164+
165+
return PushNotificationResponse(
166+
id: response.id,
167+
title: response.title,
168+
body: response.body,
169+
receivedAt: response.receivedAt,
170+
isRead: response.isRead,
171+
todoId: response.todoId,
172+
todoCategory: .decoded(todoCategory)
173+
)
71174
}
72175
}

DevLog/Infra/Service/PushNotificationService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ private extension PushNotificationService {
318318
receivedAt: receivedAt.dateValue(),
319319
isRead: isRead,
320320
todoId: todoId,
321-
todoCategory: todoCategory
321+
todoCategory: .raw(todoCategory)
322322
)
323323
}
324324

0 commit comments

Comments
 (0)