Implement comprehensive data access layer for Core Data entities: - Add NotificationContentDAO, NotificationDeliveryDAO, and NotificationConfigDAO with full CRUD operations and query helpers - Add DailyNotificationDataConversions utility for type conversions (Date ↔ Int64, Int ↔ Int32, JSON, optional strings) - Update PersistenceController with entity verification and migration policies - Add comprehensive unit tests for all DAO classes and data conversions - Update Core Data model with NotificationContent, NotificationDelivery, and NotificationConfig entities (relationships and indexes) - Integrate ReactivationManager into DailyNotificationPlugin.load() DAO Features: - Create/Insert methods with dictionary support - Read/Query methods with predicates (by timesafariDid, notificationType, scheduledTime range, deliveryStatus, etc.) - Update methods (touch, updateDeliveryStatus, recordUserInteraction) - Delete methods (by ID, by key, delete all) - Relationship management (NotificationContent ↔ NotificationDelivery) - Cascade delete support Test Coverage: - 328 lines: DailyNotificationDataConversionsTests (time, numeric, string, JSON) - 490 lines: NotificationContentDAOTests (CRUD, queries, updates) - 415 lines: NotificationDeliveryDAOTests (CRUD, relationships, cascade delete) - 412 lines: NotificationConfigDAOTests (CRUD, queries, active filtering) All tests use in-memory Core Data stack for isolation and speed. Completes sections 4.4, 4.5, and 6.0 of iOS implementation checklist.
195 lines
5.2 KiB
Swift
195 lines
5.2 KiB
Swift
/**
|
|
* DailyNotificationDataConversions.swift
|
|
*
|
|
* Data type conversion helpers for Core Data operations
|
|
* Handles conversions between Swift types and Core Data types,
|
|
* especially for time (Date ↔ Long/Int64) and numeric types.
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
* @created 2025-12-08
|
|
*/
|
|
|
|
import Foundation
|
|
import CoreData
|
|
|
|
/**
|
|
* Data conversion utilities for Core Data operations
|
|
*
|
|
* This module provides helper functions for converting between:
|
|
* - Date ↔ Int64 (epoch milliseconds)
|
|
* - Int ↔ Int32
|
|
* - Long ↔ Int64
|
|
* - Optional string handling
|
|
*/
|
|
class DailyNotificationDataConversions {
|
|
|
|
// MARK: - Constants
|
|
|
|
private static let TAG = "DNP-DATA-CONVERSIONS"
|
|
|
|
// MARK: - Time Conversions (Section 6.1)
|
|
|
|
/**
|
|
* Convert epoch milliseconds (Int64) to Date
|
|
*
|
|
* @param epochMillis Milliseconds since epoch (1970-01-01 00:00:00 UTC)
|
|
* @return Date object
|
|
*/
|
|
static func dateFromEpochMillis(_ epochMillis: Int64) -> Date {
|
|
return Date(timeIntervalSince1970: Double(epochMillis) / 1000.0)
|
|
}
|
|
|
|
/**
|
|
* Convert Date to epoch milliseconds (Int64)
|
|
*
|
|
* @param date Date object
|
|
* @return Milliseconds since epoch (1970-01-01 00:00:00 UTC)
|
|
*/
|
|
static func epochMillisFromDate(_ date: Date) -> Int64 {
|
|
return Int64(date.timeIntervalSince1970 * 1000.0)
|
|
}
|
|
|
|
/**
|
|
* Convert optional epoch milliseconds to optional Date
|
|
*
|
|
* @param epochMillis Optional milliseconds since epoch
|
|
* @return Optional Date object
|
|
*/
|
|
static func dateFromEpochMillis(_ epochMillis: Int64?) -> Date? {
|
|
guard let millis = epochMillis else { return nil }
|
|
return dateFromEpochMillis(millis)
|
|
}
|
|
|
|
/**
|
|
* Convert optional Date to optional epoch milliseconds
|
|
*
|
|
* @param date Optional Date object
|
|
* @return Optional milliseconds since epoch
|
|
*/
|
|
static func epochMillisFromDate(_ date: Date?) -> Int64? {
|
|
guard let dateValue = date else { return nil }
|
|
return epochMillisFromDate(dateValue)
|
|
}
|
|
|
|
// MARK: - Numeric Conversions (Section 6.2)
|
|
|
|
/**
|
|
* Convert Int to Int32 (for Core Data Integer 32)
|
|
*
|
|
* @param value Int value
|
|
* @return Int32 value
|
|
*/
|
|
static func int32FromInt(_ value: Int) -> Int32 {
|
|
return Int32(value)
|
|
}
|
|
|
|
/**
|
|
* Convert Int32 to Int
|
|
*
|
|
* @param value Int32 value
|
|
* @return Int value
|
|
*/
|
|
static func intFromInt32(_ value: Int32) -> Int {
|
|
return Int(value)
|
|
}
|
|
|
|
/**
|
|
* Convert Int64 to Int32 (with clamping if needed)
|
|
*
|
|
* @param value Int64 value
|
|
* @return Int32 value (clamped if out of range)
|
|
*/
|
|
static func int32FromInt64(_ value: Int64) -> Int32 {
|
|
if value > Int64(Int32.max) {
|
|
return Int32.max
|
|
} else if value < Int64(Int32.min) {
|
|
return Int32.min
|
|
}
|
|
return Int32(value)
|
|
}
|
|
|
|
/**
|
|
* Convert Int32 to Int64
|
|
*
|
|
* @param value Int32 value
|
|
* @return Int64 value
|
|
*/
|
|
static func int64FromInt32(_ value: Int32) -> Int64 {
|
|
return Int64(value)
|
|
}
|
|
|
|
/**
|
|
* Convert Long (Int64) to Int64 (no-op, but explicit)
|
|
*
|
|
* @param value Int64 value
|
|
* @return Int64 value
|
|
*/
|
|
static func int64FromLong(_ value: Int64) -> Int64 {
|
|
return value
|
|
}
|
|
|
|
/**
|
|
* Convert Boolean to Bool (direct, but explicit)
|
|
*
|
|
* @param value Boolean value
|
|
* @return Bool value
|
|
*/
|
|
static func boolFromBoolean(_ value: Bool) -> Bool {
|
|
return value
|
|
}
|
|
|
|
// MARK: - String Conversions (Section 6.3)
|
|
|
|
/**
|
|
* Safely convert optional String to String
|
|
*
|
|
* @param value Optional String
|
|
* @return String (empty string if nil)
|
|
*/
|
|
static func stringFromOptional(_ value: String?) -> String {
|
|
return value ?? ""
|
|
}
|
|
|
|
/**
|
|
* Safely convert String to optional String
|
|
*
|
|
* @param value String value
|
|
* @return Optional String (nil if empty)
|
|
*/
|
|
static func optionalStringFromString(_ value: String) -> String? {
|
|
return value.isEmpty ? nil : value
|
|
}
|
|
|
|
/**
|
|
* Convert JSON dictionary to JSON string
|
|
*
|
|
* @param dict Dictionary to encode
|
|
* @return JSON string or nil if encoding fails
|
|
*/
|
|
static func jsonStringFromDictionary(_ dict: [String: Any]?) -> String? {
|
|
guard let dict = dict else { return nil }
|
|
guard let data = try? JSONSerialization.data(withJSONObject: dict),
|
|
let jsonString = String(data: data, encoding: .utf8) else {
|
|
return nil
|
|
}
|
|
return jsonString
|
|
}
|
|
|
|
/**
|
|
* Convert JSON string to dictionary
|
|
*
|
|
* @param jsonString JSON string to decode
|
|
* @return Dictionary or nil if decoding fails
|
|
*/
|
|
static func dictionaryFromJsonString(_ jsonString: String?) -> [String: Any]? {
|
|
guard let jsonString = jsonString,
|
|
let data = jsonString.data(using: .utf8),
|
|
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
return nil
|
|
}
|
|
return dict
|
|
}
|
|
}
|
|
|