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.
301 lines
9.4 KiB
Swift
301 lines
9.4 KiB
Swift
/**
|
|
* HistoryDAO.swift
|
|
*
|
|
* Data Access Object (DAO) for History Core Data entity
|
|
* Provides helper methods for recording recovery and operation history
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
* @created 2025-12-08
|
|
*/
|
|
|
|
import Foundation
|
|
import CoreData
|
|
|
|
/**
|
|
* Extension providing DAO methods for History entity
|
|
*
|
|
* This extension adds helper methods for recording operation history
|
|
* including recovery operations, errors, and metrics.
|
|
*/
|
|
extension History {
|
|
|
|
// MARK: - Constants
|
|
|
|
private static let TAG = "DNP-HISTORY-DAO"
|
|
|
|
// MARK: - Create/Insert Methods
|
|
|
|
/**
|
|
* Create a new History entity in the given context
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param id Unique history identifier (UUID recommended)
|
|
* @param refId Reference ID (e.g., notification ID, schedule ID)
|
|
* @param kind History kind (e.g., "recovery", "fetch", "notify")
|
|
* @param occurredAt When the event occurred
|
|
* @param durationMs Duration in milliseconds
|
|
* @param outcome Outcome string (e.g., "success", "failure", "skipped")
|
|
* @param diagJson Diagnostic JSON string with additional details
|
|
* @return Created History entity
|
|
*/
|
|
static func create(
|
|
in context: NSManagedObjectContext,
|
|
id: String,
|
|
refId: String? = nil,
|
|
kind: String,
|
|
occurredAt: Date,
|
|
durationMs: Int32 = 0,
|
|
outcome: String,
|
|
diagJson: String? = nil
|
|
) -> History {
|
|
let entity = History(context: context)
|
|
|
|
entity.id = id
|
|
entity.refId = refId
|
|
entity.kind = kind
|
|
entity.occurredAt = occurredAt
|
|
entity.durationMs = durationMs
|
|
entity.outcome = outcome
|
|
entity.diagJson = diagJson
|
|
|
|
print("\(Self.TAG): Created History record: kind=\(kind), outcome=\(outcome)")
|
|
return entity
|
|
}
|
|
|
|
/**
|
|
* Create history record from dictionary
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param dict Dictionary with history data
|
|
* @return Created History entity or nil
|
|
*/
|
|
static func create(
|
|
in context: NSManagedObjectContext,
|
|
from dict: [String: Any]
|
|
) -> History? {
|
|
guard let id = dict["id"] as? String,
|
|
let kind = dict["kind"] as? String,
|
|
let outcome = dict["outcome"] as? String else {
|
|
print("\(Self.TAG): Missing required fields")
|
|
return nil
|
|
}
|
|
|
|
// Convert occurredAt from epoch milliseconds or Date
|
|
let occurredAt: Date
|
|
if let timeMillis = dict["occurredAt"] as? Int64 {
|
|
occurredAt = DailyNotificationDataConversions.dateFromEpochMillis(timeMillis)
|
|
} else if let timeDate = dict["occurredAt"] as? Date {
|
|
occurredAt = timeDate
|
|
} else {
|
|
occurredAt = Date()
|
|
}
|
|
|
|
let entity = History(context: context)
|
|
entity.id = id
|
|
entity.refId = dict["refId"] as? String
|
|
entity.kind = kind
|
|
entity.occurredAt = occurredAt
|
|
entity.durationMs = DailyNotificationDataConversions.int32FromInt(
|
|
dict["durationMs"] as? Int ?? 0
|
|
)
|
|
entity.outcome = outcome
|
|
|
|
// Convert diagJson from dictionary if needed
|
|
if let diagDict = dict["diagJson"] as? [String: Any] {
|
|
entity.diagJson = DailyNotificationDataConversions.jsonStringFromDictionary(diagDict)
|
|
} else if let diagString = dict["diagJson"] as? String {
|
|
entity.diagJson = diagString
|
|
}
|
|
|
|
return entity
|
|
}
|
|
|
|
/**
|
|
* Record recovery history with metrics
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param scenario Recovery scenario
|
|
* @param missedCount Number of missed notifications
|
|
* @param rescheduledCount Number of rescheduled notifications
|
|
* @param verifiedCount Number of verified notifications
|
|
* @param errors Number of errors
|
|
* @param startTime When recovery started
|
|
* @param endTime When recovery ended
|
|
* @return Created History entity
|
|
*/
|
|
static func recordRecovery(
|
|
in context: NSManagedObjectContext,
|
|
scenario: String,
|
|
missedCount: Int,
|
|
rescheduledCount: Int,
|
|
verifiedCount: Int,
|
|
errors: Int,
|
|
startTime: Date,
|
|
endTime: Date
|
|
) -> History {
|
|
let durationMs = Int32((endTime.timeIntervalSince(startTime) * 1000).rounded())
|
|
|
|
let diagJson: [String: Any] = [
|
|
"scenario": scenario,
|
|
"missedCount": missedCount,
|
|
"rescheduledCount": rescheduledCount,
|
|
"verifiedCount": verifiedCount,
|
|
"errors": errors,
|
|
"durationMs": durationMs
|
|
]
|
|
|
|
let diagJsonString = DailyNotificationDataConversions.jsonStringFromDictionary(diagJson) ?? "{}"
|
|
|
|
return create(
|
|
in: context,
|
|
id: UUID().uuidString,
|
|
kind: "recovery",
|
|
occurredAt: endTime,
|
|
durationMs: durationMs,
|
|
outcome: errors > 0 ? "partial_success" : "success",
|
|
diagJson: diagJsonString
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Record recovery failure
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param error Error that occurred
|
|
* @param scenario Recovery scenario (if known)
|
|
* @return Created History entity
|
|
*/
|
|
static func recordRecoveryFailure(
|
|
in context: NSManagedObjectContext,
|
|
error: Error,
|
|
scenario: String? = nil
|
|
) -> History {
|
|
var errorInfo: [String: Any] = [
|
|
"error": error.localizedDescription,
|
|
"errorType": String(describing: type(of: error))
|
|
]
|
|
|
|
// Add scenario if provided
|
|
if let scenario = scenario {
|
|
errorInfo["scenario"] = scenario
|
|
}
|
|
|
|
// Add error details if available
|
|
if let nsError = error as NSError? {
|
|
errorInfo["errorCode"] = nsError.code
|
|
errorInfo["errorDomain"] = nsError.domain
|
|
if let userInfo = nsError.userInfo as? [String: Any] {
|
|
errorInfo["userInfo"] = userInfo
|
|
}
|
|
}
|
|
|
|
let diagJsonString = DailyNotificationDataConversions.jsonStringFromDictionary(errorInfo) ?? "{}"
|
|
|
|
return create(
|
|
in: context,
|
|
id: UUID().uuidString,
|
|
kind: "recovery",
|
|
occurredAt: Date(),
|
|
outcome: "failure",
|
|
diagJson: diagJsonString
|
|
)
|
|
}
|
|
|
|
// MARK: - Read/Query Methods
|
|
|
|
/**
|
|
* Fetch History by ID
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param id History ID
|
|
* @return History entity or nil
|
|
*/
|
|
static func fetch(
|
|
by id: String,
|
|
in context: NSManagedObjectContext
|
|
) -> History? {
|
|
let request: NSFetchRequest<History> = History.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
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query by kind
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param kind History kind
|
|
* @return Array of History entities
|
|
*/
|
|
static func query(
|
|
by kind: String,
|
|
in context: NSManagedObjectContext
|
|
) -> [History] {
|
|
let request: NSFetchRequest<History> = History.fetchRequest()
|
|
request.predicate = NSPredicate(format: "kind == %@", kind)
|
|
request.sortDescriptors = [NSSortDescriptor(key: "occurredAt", ascending: false)]
|
|
|
|
do {
|
|
return try context.fetch(request)
|
|
} catch {
|
|
print("\(Self.TAG): Error querying by kind: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query by refId
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param refId Reference ID
|
|
* @return Array of History entities
|
|
*/
|
|
static func queryByRefId(
|
|
_ refId: String,
|
|
in context: NSManagedObjectContext
|
|
) -> [History] {
|
|
let request: NSFetchRequest<History> = History.fetchRequest()
|
|
request.predicate = NSPredicate(format: "refId == %@", refId)
|
|
request.sortDescriptors = [NSSortDescriptor(key: "occurredAt", ascending: false)]
|
|
|
|
do {
|
|
return try context.fetch(request)
|
|
} catch {
|
|
print("\(Self.TAG): Error querying by refId: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query by outcome
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param outcome Outcome string
|
|
* @return Array of History entities
|
|
*/
|
|
static func queryByOutcome(
|
|
_ outcome: String,
|
|
in context: NSManagedObjectContext
|
|
) -> [History] {
|
|
let request: NSFetchRequest<History> = History.fetchRequest()
|
|
request.predicate = NSPredicate(format: "outcome == %@", outcome)
|
|
request.sortDescriptors = [NSSortDescriptor(key: "occurredAt", ascending: false)]
|
|
|
|
do {
|
|
return try context.fetch(request)
|
|
} catch {
|
|
print("\(Self.TAG): Error querying by outcome: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
}
|
|
|