Files
daily-notification-plugin/ios/Plugin/NotificationContentDAO.swift
Jose Olarte III 1bfd87a0e4 fix(ios): resolve build errors and add missing configureNativeFetcher method
Fixed Swift compilation errors preventing iOS build:
- Added explicit self capture [self] in closures in DailyNotificationReactivationManager
- Removed invalid BGTaskScheduler.shared.registeredTaskIdentifiers API call
- Fixed initialization order in DailyNotificationModel (verifyEntities after container init)

Added missing configureNativeFetcher method to iOS plugin:
- Implemented method matching Android functionality
- Stores configuration in UserDefaults for persistence
- Registered method in pluginMethods array
- Supports both jwtToken and jwtSecret parameters for compatibility

This resolves the runtime error "configureNativeFetcher is not a function"
that was preventing the test app from configuring the plugin.
2025-12-11 16:44:18 +08:00

441 lines
15 KiB
Swift

/**
* NotificationContentDAO.swift
*
* Data Access Object (DAO) for NotificationContent Core Data entity
* Provides CRUD operations and query helpers for notification content
*
* @author Matthew Raymer
* @version 1.0.0
* @created 2025-12-08
*/
import Foundation
import CoreData
/**
* Extension providing DAO methods for NotificationContent entity
*
* This extension adds CRUD operations and query helpers to the
* auto-generated NotificationContent Core Data class.
*/
extension NotificationContentEntity {
// MARK: - Constants
private static let TAG = "DNP-NOTIFICATION-CONTENT-DAO"
// MARK: - Create/Insert Methods
/**
* Create a new NotificationContent entity in the given context
*
* @param context Core Data managed object context
* @param id Unique notification identifier
* @param pluginVersion Plugin version string
* @param timesafariDid TimeSafari device ID
* @param notificationType Type of notification
* @param title Notification title
* @param body Notification body
* @param scheduledTime Scheduled delivery time (Date)
* @param timezone Timezone string
* @param priority Notification priority (0-10)
* @param vibrationEnabled Whether vibration is enabled
* @param soundEnabled Whether sound is enabled
* @param mediaUrl URL to media content
* @param encryptedContent Encrypted content string
* @param encryptionKeyId Encryption key identifier
* @param ttlSeconds Time-to-live in seconds
* @param deliveryStatus Current delivery status
* @param deliveryAttempts Number of delivery attempts
* @param metadata Additional metadata (JSON string)
* @return Created NotificationContent entity
*/
static func create(
in context: NSManagedObjectContext,
id: String,
pluginVersion: String? = nil,
timesafariDid: String? = nil,
notificationType: String? = nil,
title: String? = nil,
body: String? = nil,
scheduledTime: Date,
timezone: String? = nil,
priority: Int32 = 0,
vibrationEnabled: Bool = false,
soundEnabled: Bool = true,
mediaUrl: String? = nil,
encryptedContent: String? = nil,
encryptionKeyId: String? = nil,
ttlSeconds: Int64 = 604800, // 7 days default
deliveryStatus: String? = nil,
deliveryAttempts: Int32 = 0,
metadata: String? = nil
) -> NotificationContentEntity {
let entity = NotificationContentEntity(context: context)
let now = Date()
entity.id = id
entity.pluginVersion = pluginVersion
entity.timesafariDid = timesafariDid
entity.notificationType = notificationType
entity.title = title
entity.body = body
entity.scheduledTime = scheduledTime
entity.timezone = timezone
entity.priority = priority
entity.vibrationEnabled = vibrationEnabled
entity.soundEnabled = soundEnabled
entity.mediaUrl = mediaUrl
entity.encryptedContent = encryptedContent
entity.encryptionKeyId = encryptionKeyId
entity.createdAt = now
entity.updatedAt = now
entity.ttlSeconds = ttlSeconds
entity.deliveryStatus = deliveryStatus
entity.deliveryAttempts = deliveryAttempts
entity.lastDeliveryAttempt = nil
entity.userInteractionCount = 0
entity.lastUserInteraction = nil
entity.metadata = metadata
print("\(Self.TAG): Created NotificationContent with id: \(id)")
return entity
}
/**
* Create from dictionary representation
*
* @param context Core Data managed object context
* @param dict Dictionary with notification data
* @return Created NotificationContent entity or nil
*/
static func create(
in context: NSManagedObjectContext,
from dict: [String: Any]
) -> NotificationContentEntity? {
guard let id = dict["id"] as? String else {
print("\(Self.TAG): Missing required 'id' field")
return nil
}
// Convert scheduledTime from epoch milliseconds or Date
let scheduledTime: Date
if let timeMillis = dict["scheduledTime"] as? Int64 {
scheduledTime = DailyNotificationDataConversions.dateFromEpochMillis(timeMillis)
} else if let timeDate = dict["scheduledTime"] as? Date {
scheduledTime = timeDate
} else {
print("\(Self.TAG): Missing or invalid 'scheduledTime' field")
return nil
}
// Convert createdAt/updatedAt if present
let createdAt: Date
if let createdMillis = dict["createdAt"] as? Int64 {
createdAt = DailyNotificationDataConversions.dateFromEpochMillis(createdMillis)
} else if let createdDate = dict["createdAt"] as? Date {
createdAt = createdDate
} else {
createdAt = Date()
}
let updatedAt: Date
if let updatedMillis = dict["updatedAt"] as? Int64 {
updatedAt = DailyNotificationDataConversions.dateFromEpochMillis(updatedMillis)
} else if let updatedDate = dict["updatedAt"] as? Date {
updatedAt = updatedDate
} else {
updatedAt = Date()
}
let entity = NotificationContentEntity(context: context)
entity.id = id
entity.pluginVersion = dict["pluginVersion"] as? String
entity.timesafariDid = dict["timesafariDid"] as? String
entity.notificationType = dict["notificationType"] as? String
entity.title = dict["title"] as? String
entity.body = dict["body"] as? String
entity.scheduledTime = scheduledTime
entity.timezone = dict["timezone"] as? String
entity.priority = DailyNotificationDataConversions.int32FromInt(
dict["priority"] as? Int ?? 0
)
entity.vibrationEnabled = dict["vibrationEnabled"] as? Bool ?? false
entity.soundEnabled = dict["soundEnabled"] as? Bool ?? true
entity.mediaUrl = dict["mediaUrl"] as? String
entity.encryptedContent = dict["encryptedContent"] as? String
entity.encryptionKeyId = dict["encryptionKeyId"] as? String
entity.createdAt = createdAt
entity.updatedAt = updatedAt
entity.ttlSeconds = dict["ttlSeconds"] as? Int64 ?? 604800
entity.deliveryStatus = dict["deliveryStatus"] as? String
entity.deliveryAttempts = DailyNotificationDataConversions.int32FromInt(
dict["deliveryAttempts"] as? Int ?? 0
)
if let lastAttemptMillis = dict["lastDeliveryAttempt"] as? Int64 {
entity.lastDeliveryAttempt = DailyNotificationDataConversions.dateFromEpochMillis(lastAttemptMillis)
}
entity.userInteractionCount = DailyNotificationDataConversions.int32FromInt(
dict["userInteractionCount"] as? Int ?? 0
)
if let lastInteractionMillis = dict["lastUserInteraction"] as? Int64 {
entity.lastUserInteraction = DailyNotificationDataConversions.dateFromEpochMillis(lastInteractionMillis)
}
entity.metadata = dict["metadata"] as? String
return entity
}
// MARK: - Read/Query Methods
/**
* Fetch NotificationContent by ID
*
* @param context Core Data managed object context
* @param id Notification ID
* @return NotificationContent entity or nil
*/
static func fetch(
by id: String,
in context: NSManagedObjectContext
) -> NotificationContentEntity? {
let request: NSFetchRequest<NotificationContentEntity> = NotificationContentEntity.fetchRequest()
request.predicate = NSPredicate(format: "id == %@", id)
request.fetchLimit = 1
do {
let results = try context.fetch(request)
return results.first
} catch {
print("\(Self.TAG): Error fetching by id: \(error.localizedDescription)")
return nil
}
}
/**
* Fetch all NotificationContent entities
*
* @param context Core Data managed object context
* @return Array of NotificationContent entities
*/
static func fetchAll(
in context: NSManagedObjectContext
) -> [NotificationContentEntity] {
let request: NSFetchRequest<NotificationContentEntity> = NotificationContentEntity.fetchRequest()
do {
return try context.fetch(request)
} catch {
print("\(Self.TAG): Error fetching all: \(error.localizedDescription)")
return []
}
}
/**
* Query by timesafariDid
*
* @param context Core Data managed object context
* @param timesafariDid TimeSafari device ID
* @return Array of NotificationContent entities
*/
static func query(
by timesafariDid: String,
in context: NSManagedObjectContext
) -> [NotificationContentEntity] {
let request: NSFetchRequest<NotificationContentEntity> = NotificationContentEntity.fetchRequest()
request.predicate = NSPredicate(format: "timesafariDid == %@", timesafariDid)
request.sortDescriptors = [NSSortDescriptor(key: "scheduledTime", ascending: true)]
do {
return try context.fetch(request)
} catch {
print("\(Self.TAG): Error querying by timesafariDid: \(error.localizedDescription)")
return []
}
}
/**
* Query by notificationType
*
* @param context Core Data managed object context
* @param notificationType Notification type string
* @return Array of NotificationContent entities
*/
static func queryByNotificationType(
_ notificationType: String,
in context: NSManagedObjectContext
) -> [NotificationContentEntity] {
let request: NSFetchRequest<NotificationContentEntity> = NotificationContentEntity.fetchRequest()
request.predicate = NSPredicate(format: "notificationType == %@", notificationType)
request.sortDescriptors = [NSSortDescriptor(key: "scheduledTime", ascending: true)]
do {
return try context.fetch(request)
} catch {
print("\(Self.TAG): Error querying by notificationType: \(error.localizedDescription)")
return []
}
}
/**
* Query by scheduledTime range
*
* @param context Core Data managed object context
* @param startDate Start date (inclusive)
* @param endDate End date (inclusive)
* @return Array of NotificationContent entities
*/
static func query(
scheduledTimeBetween startDate: Date,
and endDate: Date,
in context: NSManagedObjectContext
) -> [NotificationContentEntity] {
let request: NSFetchRequest<NotificationContentEntity> = NotificationContentEntity.fetchRequest()
request.predicate = NSPredicate(
format: "scheduledTime >= %@ AND scheduledTime <= %@",
startDate as NSDate,
endDate as NSDate
)
request.sortDescriptors = [NSSortDescriptor(key: "scheduledTime", ascending: true)]
do {
return try context.fetch(request)
} catch {
print("\(Self.TAG): Error querying by scheduledTime range: \(error.localizedDescription)")
return []
}
}
/**
* Query by deliveryStatus
*
* @param context Core Data managed object context
* @param deliveryStatus Delivery status string
* @return Array of NotificationContent entities
*/
static func queryByDeliveryStatus(
_ deliveryStatus: String,
in context: NSManagedObjectContext
) -> [NotificationContentEntity] {
let request: NSFetchRequest<NotificationContentEntity> = NotificationContentEntity.fetchRequest()
request.predicate = NSPredicate(format: "deliveryStatus == %@", deliveryStatus)
request.sortDescriptors = [NSSortDescriptor(key: "scheduledTime", ascending: true)]
do {
return try context.fetch(request)
} catch {
print("\(Self.TAG): Error querying by deliveryStatus: \(error.localizedDescription)")
return []
}
}
/**
* Query notifications ready for delivery (scheduledTime <= currentTime)
*
* @param context Core Data managed object context
* @param currentTime Current time for comparison
* @return Array of NotificationContent entities
*/
static func queryReadyForDelivery(
currentTime: Date,
in context: NSManagedObjectContext
) -> [NotificationContentEntity] {
let request: NSFetchRequest<NotificationContentEntity> = NotificationContentEntity.fetchRequest()
request.predicate = NSPredicate(format: "scheduledTime <= %@", currentTime as NSDate)
request.sortDescriptors = [NSSortDescriptor(key: "scheduledTime", ascending: true)]
do {
return try context.fetch(request)
} catch {
print("\(Self.TAG): Error querying ready for delivery: \(error.localizedDescription)")
return []
}
}
// MARK: - Update Methods
/**
* Update this entity's updatedAt timestamp
*/
func touch() {
self.updatedAt = Date()
}
/**
* Update delivery status and increment attempts
*
* @param status New delivery status
*/
func updateDeliveryStatus(_ status: String) {
self.deliveryStatus = status
self.deliveryAttempts += 1
self.lastDeliveryAttempt = Date()
self.touch()
}
/**
* Record user interaction
*/
func recordUserInteraction() {
self.userInteractionCount += 1
self.lastUserInteraction = Date()
self.touch()
}
// MARK: - Delete Methods
/**
* Delete NotificationContent by ID
*
* @param context Core Data managed object context
* @param id Notification ID
* @return true if deleted, false otherwise
*/
static func delete(
by id: String,
in context: NSManagedObjectContext
) -> Bool {
guard let entity = fetch(by: id, in: context) else {
return false
}
context.delete(entity)
do {
try context.save()
print("\(Self.TAG): Deleted NotificationContent with id: \(id)")
return true
} catch {
print("\(Self.TAG): Error deleting: \(error.localizedDescription)")
context.rollback()
return false
}
}
/**
* Delete all NotificationContent entities
*
* @param context Core Data managed object context
* @return Number of entities deleted
*/
static func deleteAll(
in context: NSManagedObjectContext
) -> Int {
let request: NSFetchRequest<NSFetchRequestResult> = NotificationContentEntity.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
do {
let result = try context.execute(deleteRequest) as? NSBatchDeleteResult
try context.save()
let count = result?.result as? Int ?? 0
print("\(Self.TAG): Deleted \(count) NotificationContent entities")
return count
} catch {
print("\(Self.TAG): Error deleting all: \(error.localizedDescription)")
context.rollback()
return 0
}
}
}