feat(sync): add CloudKit sync for dynamic sports

- Add CKSport model to parse CloudKit Sport records
- Add fetchSportsForSync() to CloudKitService for delta fetching
- Add syncSports() and mergeSport() to CanonicalSyncService
- Update DataProvider with dynamicSports support and allSports computed property
- Update MockAppDataProvider with matching dynamic sports support
- Add comprehensive documentation for adding new sports

The app can now sync sport definitions from CloudKit, enabling new sports
to be added without app updates. Sports are fetched, merged into SwiftData,
and exposed via AppDataProvider.allSports alongside built-in Sport enum cases.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-13 18:27:56 -06:00
parent dc278085de
commit f180e5bfed
10 changed files with 2080 additions and 161 deletions

View File

@@ -315,6 +315,57 @@ struct CKLeagueStructure {
}
}
// MARK: - CKSport
struct CKSport {
static let idKey = "sportId"
static let abbreviationKey = "abbreviation"
static let displayNameKey = "displayName"
static let iconNameKey = "iconName"
static let colorHexKey = "colorHex"
static let seasonStartMonthKey = "seasonStartMonth"
static let seasonEndMonthKey = "seasonEndMonth"
static let isActiveKey = "isActive"
static let schemaVersionKey = "schemaVersion"
static let lastModifiedKey = "lastModified"
let record: CKRecord
init(record: CKRecord) {
self.record = record
}
/// Convert to CanonicalSport for local storage
func toCanonical() -> CanonicalSport? {
guard let id = record[CKSport.idKey] as? String,
let abbreviation = record[CKSport.abbreviationKey] as? String,
let displayName = record[CKSport.displayNameKey] as? String,
let iconName = record[CKSport.iconNameKey] as? String,
let colorHex = record[CKSport.colorHexKey] as? String,
let seasonStartMonth = record[CKSport.seasonStartMonthKey] as? Int,
let seasonEndMonth = record[CKSport.seasonEndMonthKey] as? Int
else { return nil }
let isActive = (record[CKSport.isActiveKey] as? Int ?? 1) == 1
let schemaVersion = record[CKSport.schemaVersionKey] as? Int ?? SchemaVersion.current
let lastModified = record[CKSport.lastModifiedKey] as? Date ?? record.modificationDate ?? Date()
return CanonicalSport(
id: id,
abbreviation: abbreviation,
displayName: displayName,
iconName: iconName,
colorHex: colorHex,
seasonStartMonth: seasonStartMonth,
seasonEndMonth: seasonEndMonth,
isActive: isActive,
lastModified: lastModified,
schemaVersion: schemaVersion,
source: .cloudKit
)
}
}
// MARK: - CKStadiumAlias
struct CKStadiumAlias {