Add StadiumAlias CloudKit sync and offline-first data architecture

- Add CKStadiumAlias model for CloudKit record mapping
- Add fetchStadiumAliases/fetchStadiumAliasChanges to CloudKitService
- Add syncStadiumAliases to CanonicalSyncService for delta sync
- Add subscribeToStadiumAliasUpdates for push notifications
- Update cloudkit_import.py with --stadium-aliases-only option

Data Architecture Updates:
- Remove obsolete provider files (CanonicalDataProvider, CloudKitDataProvider, StubDataProvider)
- AppDataProvider now reads exclusively from SwiftData
- Add background CloudKit sync on app startup (non-blocking)
- Document data architecture in CLAUDE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-08 22:20:07 -06:00
parent 588938d2a1
commit 1ee47df53e
12 changed files with 482 additions and 780 deletions

View File

@@ -17,6 +17,7 @@ enum CKRecordType {
static let sport = "Sport"
static let leagueStructure = "LeagueStructure"
static let teamAlias = "TeamAlias"
static let stadiumAlias = "StadiumAlias"
}
// MARK: - CKTeam
@@ -273,6 +274,55 @@ struct CKLeagueStructure {
}
}
// MARK: - CKStadiumAlias
struct CKStadiumAlias {
static let aliasNameKey = "aliasName"
static let stadiumCanonicalIdKey = "stadiumCanonicalId"
static let validFromKey = "validFrom"
static let validUntilKey = "validUntil"
static let schemaVersionKey = "schemaVersion"
static let lastModifiedKey = "lastModified"
let record: CKRecord
init(record: CKRecord) {
self.record = record
}
init(model: StadiumAlias) {
let record = CKRecord(recordType: CKRecordType.stadiumAlias, recordID: CKRecord.ID(recordName: model.aliasName))
record[CKStadiumAlias.aliasNameKey] = model.aliasName
record[CKStadiumAlias.stadiumCanonicalIdKey] = model.stadiumCanonicalId
record[CKStadiumAlias.validFromKey] = model.validFrom
record[CKStadiumAlias.validUntilKey] = model.validUntil
record[CKStadiumAlias.schemaVersionKey] = model.schemaVersion
record[CKStadiumAlias.lastModifiedKey] = model.lastModified
self.record = record
}
/// Convert to SwiftData model for local storage
func toModel() -> StadiumAlias? {
guard let aliasName = record[CKStadiumAlias.aliasNameKey] as? String,
let stadiumCanonicalId = record[CKStadiumAlias.stadiumCanonicalIdKey] as? String
else { return nil }
let validFrom = record[CKStadiumAlias.validFromKey] as? Date
let validUntil = record[CKStadiumAlias.validUntilKey] as? Date
let schemaVersion = record[CKStadiumAlias.schemaVersionKey] as? Int ?? SchemaVersion.current
let lastModified = record[CKStadiumAlias.lastModifiedKey] as? Date ?? record.modificationDate ?? Date()
return StadiumAlias(
aliasName: aliasName,
stadiumCanonicalId: stadiumCanonicalId,
validFrom: validFrom,
validUntil: validUntil,
schemaVersion: schemaVersion,
lastModified: lastModified
)
}
}
// MARK: - CKTeamAlias
struct CKTeamAlias {