Skip to content
Merged
47 changes: 30 additions & 17 deletions DevLog/Data/Repository/PushNotificationRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,37 @@ final class PushNotificationRepositoryImpl: PushNotificationRepository {
}

/// 푸시 알림 기록 요청
func requestNotifications() async throws -> [PushNotification] {
try await service.requestNotifications()
.compactMap { dto in
guard
let id = dto.id,
let todoKind = TodoKind(rawValue: dto.todoKind)
else { return nil }
func requestNotifications(
_ query: PushNotificationQuery,
cursor: PushNotificationCursor?
) async throws -> PushNotificationPage {
let response = try await service.requestNotifications(query, cursor: cursor)

return PushNotification(
id: id,
title: dto.title,
body: dto.body,
receivedAt: dto.receivedAt.dateValue(),
isRead: dto.isRead,
todoID: dto.todoID,
todoKind: todoKind
)
}
let items: [PushNotification] = response.items.compactMap { dto in
guard
let id = dto.id,
let todoKind = TodoKind(rawValue: dto.todoKind)
else { return nil }

return PushNotification(
id: id,
title: dto.title,
body: dto.body,
receivedAt: dto.receivedAt.dateValue(),
isRead: dto.isRead,
todoID: dto.todoID,
todoKind: todoKind
)
}

let nextCursor = response.nextCursor.map { cursor in
PushNotificationCursor(
receivedAt: cursor.receivedAt.dateValue(),
documentID: cursor.documentID
)
}

return PushNotificationPage(items: items, nextCursor: nextCursor)
}

// 푸시 알림 기록 삭제
Expand Down
13 changes: 13 additions & 0 deletions DevLog/Domain/Entity/PushNotificationCursor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// PushNotificationCursor.swift
// DevLog
//
// Created by opfic on 2/18/26.
//

import Foundation

struct PushNotificationCursor: Equatable {
let receivedAt: Date
let documentID: String
}
17 changes: 17 additions & 0 deletions DevLog/Domain/Entity/PushNotificationPage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// PushNotificationPage.swift
// DevLog
//
// Created by opfic on 2/18/26.
//

import Foundation

struct PushNotificationPage: Equatable {
let items: [PushNotification]
let nextCursor: PushNotificationCursor?

static func == (lhs: PushNotificationPage, rhs: PushNotificationPage) -> Bool {
lhs.items.map { $0.id } == rhs.items.map { $0.id } && lhs.nextCursor == rhs.nextCursor
}
}
46 changes: 46 additions & 0 deletions DevLog/Domain/Entity/PushNotificationQuery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// PushNotificationQuery.swift
// DevLog
//
// Created by opfic on 2/18/26.
//

import Foundation

struct PushNotificationQuery: Equatable {
enum SortOrder: Equatable {
case latest
case oldest
}

enum TimeFilter: Equatable {
case none
case hours(Int)
case days(Int)
}

var sortOrder: SortOrder
var timeFilter: TimeFilter
var unreadOnly: Bool
var pageSize: Int

static let `default` = PushNotificationQuery(
sortOrder: .latest,
timeFilter: .none,
unreadOnly: false,
pageSize: 20
)
}

extension PushNotificationQuery.TimeFilter {
var thresholdDate: Date? {
switch self {
case .none:
return nil
case .hours(let value):
return Date().addingTimeInterval(-Double(value) * 3600.0)
case .days(let value):
return Date().addingTimeInterval(-Double(value) * 86400.0)
}
}
}
5 changes: 4 additions & 1 deletion DevLog/Domain/Protocol/PushNotificationRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ protocol PushNotificationRepository {
func fetchPushNotificationEnabled() async throws -> Bool
func fetchPushNotificationTime() async throws -> DateComponents
func updatePushNotificationSettings(_ settings: PushNotificationSettings) async throws
func requestNotifications() async throws -> [PushNotification]
func requestNotifications(
_ query: PushNotificationQuery,
cursor: PushNotificationCursor?
) async throws -> PushNotificationPage
func deleteNotification(_ notificationID: String) async throws
func toggleNotificationRead(_ todoID: String) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
//

protocol FetchPushNotificationsUseCase {
func execute() async throws -> [PushNotification]
func execute(
_ query: PushNotificationQuery,
cursor: PushNotificationCursor?
) async throws -> PushNotificationPage
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ final class FetchPushNotificationsUseCaseImpl: FetchPushNotificationsUseCase {
self.repository = repository
}

func execute() async throws -> [PushNotification] {
try await repository.requestNotifications()
func execute(
_ query: PushNotificationQuery,
cursor: PushNotificationCursor?
) async throws -> PushNotificationPage {
try await repository.requestNotifications(query, cursor: cursor)
}
}
13 changes: 13 additions & 0 deletions DevLog/Infra/DTO/PushNotificationCursorResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// PushNotificationCursorResponse.swift
// DevLog
//
// Created by opfic on 2/18/26.
//

import FirebaseFirestore

struct PushNotificationCursorResponse {
let receivedAt: Timestamp
let documentID: String
}
11 changes: 11 additions & 0 deletions DevLog/Infra/DTO/PushNotificationPageResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// PushNotificationPageResponse.swift
// DevLog
//
// Created by opfic on 2/18/26.
//

struct PushNotificationPageResponse {
let items: [PushNotificationResponse]
let nextCursor: PushNotificationCursorResponse?
}
50 changes: 46 additions & 4 deletions DevLog/Infra/Service/PushNotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,57 @@ final class PushNotificationService {
}

/// 푸시 알림 기록 요청
func requestNotifications() async throws -> [PushNotificationResponse] {
func requestNotifications(
_ query: PushNotificationQuery,
cursor: PushNotificationCursor?
) async throws -> PushNotificationPageResponse {
guard let uid = Auth.auth().currentUser?.uid else { throw AuthError.notAuthenticated }

let collection = store.collection("users/\(uid)/notifications")
let snapshot = try await collection.getDocuments()
var firestoreQuery: Query = store.collection("users/\(uid)/notifications")

if let thresholdDate = query.timeFilter.thresholdDate {
firestoreQuery = firestoreQuery.whereField(
"receivedAt",
isGreaterThanOrEqualTo: Timestamp(date: thresholdDate)
)
}

return try snapshot.documents.compactMap { document in
if query.unreadOnly {
firestoreQuery = firestoreQuery.whereField("isRead", isEqualTo: false)
}

let isDescending = query.sortOrder == .latest
firestoreQuery = firestoreQuery
.order(by: "receivedAt", descending: isDescending)
.order(by: FieldPath.documentID())

if let cursor {
firestoreQuery = firestoreQuery.start(after: [
Timestamp(date: cursor.receivedAt),
cursor.documentID
])
}

let snapshot = try await firestoreQuery
.limit(to: query.pageSize)
.getDocuments()

let items = try snapshot.documents.compactMap { document in
try document.data(as: PushNotificationResponse.self)
}

let nextCursor: PushNotificationCursorResponse? = snapshot.documents.last.map { document in
guard let receivedAt = document.data()["receivedAt"] as? Timestamp else {
return nil
}

return PushNotificationCursorResponse(
receivedAt: receivedAt,
documentID: document.documentID
)
} ?? nil

return PushNotificationPageResponse(items: items, nextCursor: nextCursor)
}

/// 푸시 알림 기록 삭제
Expand Down
Loading