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.
441 lines
15 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|
|
|