#!/usr/bin/env swift // // import_to_cloudkit.swift // SportsTime // // Imports scraped JSON data into CloudKit public database. // Run from command line: swift import_to_cloudkit.swift --games data/games.json --stadiums data/stadiums.json // import Foundation import CloudKit // MARK: - Data Models (matching scraper output) struct ScrapedGame: Codable { let id: String let sport: String let season: String let date: String let time: String? let home_team: String let away_team: String let home_team_abbrev: String let away_team_abbrev: String let venue: String let source: String let is_playoff: Bool? let broadcast: String? } struct ScrapedStadium: Codable { let id: String let name: String let city: String let state: String let latitude: Double let longitude: Double let capacity: Int let sport: String let team_abbrevs: [String] let source: String let year_opened: Int? } // MARK: - CloudKit Importer class CloudKitImporter { let container: CKContainer let database: CKDatabase init(containerIdentifier: String = "iCloud.com.sportstime.app") { self.container = CKContainer(identifier: containerIdentifier) self.database = container.publicCloudDatabase } // MARK: - Import Stadiums func importStadiums(from stadiums: [ScrapedStadium]) async throws -> Int { var imported = 0 for stadium in stadiums { let record = CKRecord(recordType: "Stadium") record["stadiumId"] = stadium.id record["name"] = stadium.name record["city"] = stadium.city record["state"] = stadium.state record["location"] = CLLocation(latitude: stadium.latitude, longitude: stadium.longitude) record["capacity"] = stadium.capacity record["sport"] = stadium.sport record["teamAbbrevs"] = stadium.team_abbrevs record["source"] = stadium.source if let yearOpened = stadium.year_opened { record["yearOpened"] = yearOpened } do { _ = try await database.save(record) imported += 1 print(" Imported stadium: \(stadium.name)") } catch { print(" Error importing \(stadium.name): \(error)") } } return imported } // MARK: - Import Teams func importTeams(from stadiums: [ScrapedStadium], teamMappings: [String: TeamInfo]) async throws -> [String: CKRecord.ID] { var teamRecordIDs: [String: CKRecord.ID] = [:] for (abbrev, info) in teamMappings { let record = CKRecord(recordType: "Team") record["teamId"] = UUID().uuidString record["name"] = info.name record["abbreviation"] = abbrev record["sport"] = info.sport record["city"] = info.city do { let saved = try await database.save(record) teamRecordIDs[abbrev] = saved.recordID print(" Imported team: \(info.name)") } catch { print(" Error importing team \(info.name): \(error)") } } return teamRecordIDs } // MARK: - Import Games func importGames( from games: [ScrapedGame], teamRecordIDs: [String: CKRecord.ID], stadiumRecordIDs: [String: CKRecord.ID] ) async throws -> Int { var imported = 0 // Batch imports for efficiency let batchSize = 100 var batch: [CKRecord] = [] for game in games { let record = CKRecord(recordType: "Game") record["gameId"] = game.id record["sport"] = game.sport record["season"] = game.season // Parse date let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" if let date = dateFormatter.date(from: game.date) { if let timeStr = game.time { // Combine date and time let timeFormatter = DateFormatter() timeFormatter.dateFormat = "HH:mm" if let time = timeFormatter.date(from: timeStr) { let calendar = Calendar.current let timeComponents = calendar.dateComponents([.hour, .minute], from: time) if let combined = calendar.date(bySettingHour: timeComponents.hour ?? 19, minute: timeComponents.minute ?? 0, second: 0, of: date) { record["dateTime"] = combined } } } else { // Default to 7 PM if no time let calendar = Calendar.current if let defaultTime = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: date) { record["dateTime"] = defaultTime } } } // Team references if let homeTeamID = teamRecordIDs[game.home_team_abbrev] { record["homeTeamRef"] = CKRecord.Reference(recordID: homeTeamID, action: .none) } if let awayTeamID = teamRecordIDs[game.away_team_abbrev] { record["awayTeamRef"] = CKRecord.Reference(recordID: awayTeamID, action: .none) } record["isPlayoff"] = (game.is_playoff ?? false) ? 1 : 0 record["broadcastInfo"] = game.broadcast record["source"] = game.source batch.append(record) // Save batch if batch.count >= batchSize { do { let operation = CKModifyRecordsOperation(recordsToSave: batch, recordIDsToDelete: nil) operation.savePolicy = .changedKeys try await database.modifyRecords(saving: batch, deleting: []) imported += batch.count print(" Imported batch of \(batch.count) games (total: \(imported))") batch.removeAll() } catch { print(" Error importing batch: \(error)") } } } // Save remaining if !batch.isEmpty { do { try await database.modifyRecords(saving: batch, deleting: []) imported += batch.count } catch { print(" Error importing final batch: \(error)") } } return imported } } // MARK: - Team Info struct TeamInfo { let name: String let city: String let sport: String } // MARK: - Main func loadJSON(from path: String) throws -> T { let url = URL(fileURLWithPath: path) let data = try Data(contentsOf: url) return try JSONDecoder().decode(T.self, from: data) } func main() async { let args = CommandLine.arguments guard args.count >= 3 else { print("Usage: swift import_to_cloudkit.swift --games --stadiums ") return } var gamesPath: String? var stadiumsPath: String? for i in 1..