66//
77
88import FirebaseAuth
9+ import Combine
910import FirebaseFirestore
1011
1112final class PushNotificationService {
@@ -90,28 +91,12 @@ final class PushNotificationService {
9091
9192 /// 푸시 알림 기록 요청
9293 func requestNotifications(
93- _ query : PushNotificationQuery ,
94+ _ notificationQuery : PushNotificationQuery ,
9495 cursor: PushNotificationCursorDTO ?
9596 ) async throws -> PushNotificationPageResponse {
9697 guard let uid = Auth . auth ( ) . currentUser? . uid else { throw AuthError . notAuthenticated }
9798
98- var firestoreQuery : Query = store. collection ( " users/ \( uid) /notifications " )
99-
100- if let thresholdDate = query. timeFilter. thresholdDate {
101- firestoreQuery = firestoreQuery. whereField (
102- " receivedAt " ,
103- isGreaterThanOrEqualTo: Timestamp ( date: thresholdDate)
104- )
105- }
106-
107- if query. unreadOnly {
108- firestoreQuery = firestoreQuery. whereField ( " isRead " , isEqualTo: false )
109- }
110-
111- let isDescending = query. sortOrder == . latest
112- firestoreQuery = firestoreQuery
113- . order ( by: " receivedAt " , descending: isDescending)
114- . order ( by: FieldPath . documentID ( ) )
99+ var firestoreQuery = makeQuery ( uid: uid, query: notificationQuery)
115100
116101 if let cursor {
117102 firestoreQuery = firestoreQuery. start ( after: [
@@ -121,13 +106,13 @@ final class PushNotificationService {
121106 }
122107
123108 let snapshot = try await firestoreQuery
124- . limit ( to: query . pageSize)
109+ . limit ( to: notificationQuery . pageSize)
125110 . getDocuments ( )
126111
127112 let items = snapshot. documents. compactMap { makeResponse ( from: $0) }
128113
129114 let nextCursor : PushNotificationCursorDTO ? = snapshot. documents. last. map { document in
130- guard let receivedAt = document. data ( ) [ NotificationFieldKey . receivedAt. rawValue] as? Timestamp else {
115+ guard let receivedAt = document. data ( ) [ Key . receivedAt. rawValue] as? Timestamp else {
131116 return nil
132117 }
133118
@@ -140,6 +125,39 @@ final class PushNotificationService {
140125 return PushNotificationPageResponse ( items: items, nextCursor: nextCursor)
141126 }
142127
128+ func observeNotifications(
129+ _ query: PushNotificationQuery ,
130+ limit: Int
131+ ) throws -> AnyPublisher < PushNotificationPageResponse , Error > {
132+ guard let uid = Auth . auth ( ) . currentUser? . uid else { throw AuthError . notAuthenticated }
133+
134+ let subject = PassthroughSubject < PushNotificationPageResponse , Error > ( )
135+ let pageLimit = max ( query. pageSize, limit)
136+ let listener = makeQuery ( uid: uid, query: query)
137+ . limit ( to: pageLimit)
138+ . addSnapshotListener { [ weak self] snapshot, error in
139+ if let error {
140+ subject. send ( completion: . failure( error) )
141+ return
142+ }
143+
144+ guard let self, let snapshot else { return }
145+
146+ let items = snapshot. documents. compactMap { self . makeResponse ( from: $0) }
147+ let nextCursor = self . makeNextCursor ( from: snapshot. documents. last)
148+ subject. send (
149+ PushNotificationPageResponse (
150+ items: items,
151+ nextCursor: nextCursor
152+ )
153+ )
154+ }
155+
156+ return subject
157+ . handleEvents ( receiveCancel: { listener. remove ( ) } )
158+ . eraseToAnyPublisher ( )
159+ }
160+
143161 /// 푸시 알림 기록 삭제
144162 func deleteNotification( _ notificationID: String ) async throws {
145163 guard let uid = Auth . auth ( ) . currentUser? . uid else { throw AuthError . notAuthenticated }
@@ -177,15 +195,51 @@ final class PushNotificationService {
177195}
178196
179197private extension PushNotificationService {
198+ func makeQuery(
199+ uid: String ,
200+ query: PushNotificationQuery
201+ ) -> Query {
202+ var firestoreQuery : Query = store. collection ( " users/ \( uid) /notifications " )
203+
204+ if let thresholdDate = query. timeFilter. thresholdDate {
205+ firestoreQuery = firestoreQuery. whereField (
206+ " receivedAt " ,
207+ isGreaterThanOrEqualTo: Timestamp ( date: thresholdDate)
208+ )
209+ }
210+
211+ if query. unreadOnly {
212+ firestoreQuery = firestoreQuery. whereField ( " isRead " , isEqualTo: false )
213+ }
214+
215+ let isDescending = query. sortOrder == . latest
216+ return firestoreQuery
217+ . order ( by: " receivedAt " , descending: isDescending)
218+ . order ( by: FieldPath . documentID ( ) )
219+ }
220+
221+ func makeNextCursor( from document: QueryDocumentSnapshot ? ) -> PushNotificationCursorDTO ? {
222+ guard
223+ let document,
224+ let receivedAt = document. data ( ) [ Key . receivedAt. rawValue] as? Timestamp else {
225+ return nil
226+ }
227+
228+ return PushNotificationCursorDTO (
229+ receivedAt: receivedAt. dateValue ( ) ,
230+ documentID: document. documentID
231+ )
232+ }
233+
180234 func makeResponse( from snapshot: QueryDocumentSnapshot ) -> PushNotificationResponse ? {
181235 let data = snapshot. data ( )
182236 guard
183- let title = data [ NotificationFieldKey . title. rawValue] as? String ,
184- let body = data [ NotificationFieldKey . body. rawValue] as? String ,
185- let receivedAt = data [ NotificationFieldKey . receivedAt. rawValue] as? Timestamp ,
186- let isRead = data [ NotificationFieldKey . isRead. rawValue] as? Bool ,
187- let todoId = data [ NotificationFieldKey . todoId. rawValue] as? String ,
188- let todoKind = data [ NotificationFieldKey . todoKind. rawValue] as? String else {
237+ let title = data [ Key . title. rawValue] as? String ,
238+ let body = data [ Key . body. rawValue] as? String ,
239+ let receivedAt = data [ Key . receivedAt. rawValue] as? Timestamp ,
240+ let isRead = data [ Key . isRead. rawValue] as? Bool ,
241+ let todoId = data [ Key . todoId. rawValue] as? String ,
242+ let todoKind = data [ Key . todoKind. rawValue] as? String else {
189243 return nil
190244 }
191245
@@ -200,7 +254,7 @@ private extension PushNotificationService {
200254 )
201255 }
202256
203- enum NotificationFieldKey : String {
257+ enum Key : String {
204258 case title
205259 case body
206260 case receivedAt
0 commit comments