@@ -56,7 +56,31 @@ struct MistDemo: AsyncParsableCommand {
5656
5757 @Option ( name: . long, help: " CloudKit environment (development or production) " )
5858 var environment : String = " development "
59-
59+
60+ @Flag ( name: . long, help: " Test lookupZones operation " )
61+ var testLookupZones : Bool = false
62+
63+ @Option ( name: . long, help: " Comma-separated zone names to lookup " )
64+ var zoneNames : String ?
65+
66+ @Flag ( name: . long, help: " Test fetchRecordChanges operation " )
67+ var testFetchChanges : Bool = false
68+
69+ @Option ( name: . long, help: " Sync token from previous fetch " )
70+ var syncToken : String ?
71+
72+ @Flag ( name: . long, help: " Fetch all changes automatically (pagination) " )
73+ var fetchAll : Bool = false
74+
75+ @Flag ( name: . long, help: " Test uploadAssets operation " )
76+ var testUploadAsset : Bool = false
77+
78+ @Option ( name: . long, help: " Path to file to upload " )
79+ var file : String ?
80+
81+ @Option ( name: . long, help: " Create record of this type with uploaded asset " )
82+ var createRecord : String ?
83+
6084 func run( ) async throws {
6185 // Get API token from environment variable if not provided
6286 let resolvedApiToken = apiToken. isEmpty ?
@@ -78,7 +102,13 @@ struct MistDemo: AsyncParsableCommand {
78102 // Use the resolved API token for all operations
79103 let effectiveApiToken = resolvedApiToken
80104
81- if testAllAuth {
105+ if testLookupZones {
106+ try await demonstrateLookupZones ( apiToken: effectiveApiToken)
107+ } else if testFetchChanges {
108+ try await demonstrateFetchChanges ( apiToken: effectiveApiToken)
109+ } else if testUploadAsset {
110+ try await demonstrateUploadAsset ( apiToken: effectiveApiToken)
111+ } else if testAllAuth {
82112 try await testAllAuthenticationMethods ( apiToken: effectiveApiToken)
83113 } else if testApiOnly {
84114 try await testAPIOnlyAuthentication ( apiToken: effectiveApiToken)
@@ -642,4 +672,229 @@ struct MistDemo: AsyncParsableCommand {
642672 print ( " --private-key-file 'path/to/private_key.pem' " )
643673 }
644674 }
675+
676+ // MARK: - New Operation Demonstrations
677+
678+ private func demonstrateLookupZones( apiToken: String ) async throws {
679+ print ( " \n " + String( repeating: " = " , count: 60 ) )
680+ print ( " 🔍 Testing lookupZones() Operation " )
681+ print ( String ( repeating: " = " , count: 60 ) )
682+
683+ let tokenManager = APITokenManager ( apiToken: apiToken)
684+ let service = try CloudKitService (
685+ containerIdentifier: containerIdentifier,
686+ tokenManager: tokenManager,
687+ environment: environment == " production " ? . production : . development,
688+ database: . public
689+ )
690+
691+ let zoneNamesList = self . zoneNames? . split ( separator: " , " ) . map ( String . init) ?? [ " _defaultZone " ]
692+ let zoneIDs = zoneNamesList. map { ZoneID ( zoneName: $0, ownerName: nil ) }
693+
694+ print ( " \n 📋 Looking up \( zoneIDs. count) zone(s): " )
695+ for zoneName in zoneNamesList {
696+ print ( " - \( zoneName) " )
697+ }
698+
699+ do {
700+ let zones = try await service. lookupZones ( zoneIDs: zoneIDs)
701+ print ( " \n ✅ Found \( zones. count) zone(s): " )
702+ for zone in zones {
703+ print ( " - \( zone. zoneName) " )
704+ if let owner = zone. ownerRecordName {
705+ print ( " Owner: \( owner) " )
706+ }
707+ if !zone. capabilities. isEmpty {
708+ print ( " Capabilities: \( zone. capabilities. joined ( separator: " , " ) ) " )
709+ }
710+ }
711+ } catch {
712+ print ( " \n ❌ Error: \( error) " )
713+ }
714+
715+ print ( " \n " + String( repeating: " = " , count: 60 ) )
716+ print ( " ✅ lookupZones test completed! " )
717+ print ( String ( repeating: " = " , count: 60 ) )
718+ }
719+
720+ private func demonstrateFetchChanges( apiToken: String ) async throws {
721+ print ( " \n " + String( repeating: " = " , count: 60 ) )
722+ print ( " 🔄 Testing fetchRecordChanges() Operation " )
723+ print ( String ( repeating: " = " , count: 60 ) )
724+
725+ let tokenManager = APITokenManager ( apiToken: apiToken)
726+ let service = try CloudKitService (
727+ containerIdentifier: containerIdentifier,
728+ tokenManager: tokenManager,
729+ environment: environment == " production " ? . production : . development,
730+ database: . public
731+ )
732+
733+ do {
734+ if fetchAll {
735+ print ( " \n 📦 Fetching all changes (automatic pagination)... " )
736+ if let token = syncToken {
737+ print ( " Using sync token: \( token. prefix ( 20 ) ) ... " )
738+ } else {
739+ print ( " Performing initial fetch (no sync token) " )
740+ }
741+
742+ let ( records, newToken) = try await service. fetchAllRecordChanges (
743+ syncToken: syncToken
744+ )
745+ print ( " \n ✅ Fetched \( records. count) record(s) " )
746+ displayRecords ( records, limit: 5 )
747+ if let token = newToken {
748+ print ( " \n 💾 New sync token: \( token. prefix ( 20 ) ) ... " )
749+ print ( " Save this token to fetch only new changes next time: " )
750+ print ( " mistdemo --test-fetch-changes --sync-token ' \( token) ' " )
751+ }
752+ } else {
753+ print ( " \n 📄 Fetching single page... " )
754+ if let token = syncToken {
755+ print ( " Using sync token: \( token. prefix ( 20 ) ) ... " )
756+ } else {
757+ print ( " Performing initial fetch (no sync token) " )
758+ }
759+
760+ let result = try await service. fetchRecordChanges (
761+ syncToken: syncToken,
762+ resultsLimit: 10
763+ )
764+ print ( " \n ✅ Fetched \( result. records. count) record(s) " )
765+ displayRecords ( result. records, limit: 5 )
766+
767+ if result. moreComing {
768+ print ( " \n ⚠️ More changes available! Use --sync-token with: " )
769+ if let token = result. syncToken {
770+ print ( " mistdemo --test-fetch-changes --sync-token ' \( token) ' " )
771+ }
772+ }
773+
774+ if let token = result. syncToken {
775+ print ( " \n 💾 Sync token: \( token. prefix ( 20 ) ) ... " )
776+ }
777+ }
778+ } catch {
779+ print ( " \n ❌ Error: \( error) " )
780+ }
781+
782+ print ( " \n " + String( repeating: " = " , count: 60 ) )
783+ print ( " ✅ fetchRecordChanges test completed! " )
784+ print ( String ( repeating: " = " , count: 60 ) )
785+ }
786+
787+ private func displayRecords( _ records: [ RecordInfo ] , limit: Int ) {
788+ let displayed = records. prefix ( limit)
789+ for record in displayed {
790+ print ( " 📝 \( record. recordType) - \( record. recordName) " )
791+ if !record. fields. isEmpty {
792+ print ( " Fields: \( record. fields. keys. joined ( separator: " , " ) ) " )
793+ }
794+ }
795+ if records. count > limit {
796+ print ( " ... and \( records. count - limit) more " )
797+ }
798+ }
799+
800+ private func demonstrateUploadAsset( apiToken: String ) async throws {
801+ print ( " \n " + String( repeating: " = " , count: 60 ) )
802+ print ( " 📤 Testing uploadAssets() Operation " )
803+ print ( String ( repeating: " = " , count: 60 ) )
804+
805+ guard let filePath = file else {
806+ print ( " \n ❌ Error: --file required " )
807+ print ( " Usage: mistdemo --test-upload-asset --file path/to/file.png " )
808+ return
809+ }
810+
811+ let fileURL = URL ( fileURLWithPath: filePath)
812+
813+ guard FileManager . default. fileExists ( atPath: filePath) else {
814+ print ( " \n ❌ Error: File not found at path: \( filePath) " )
815+ return
816+ }
817+
818+ let tokenManager = APITokenManager ( apiToken: apiToken)
819+ let service = try CloudKitService (
820+ containerIdentifier: containerIdentifier,
821+ tokenManager: tokenManager,
822+ environment: environment == " production " ? . production : . development,
823+ database: . public
824+ )
825+
826+ do {
827+ let data = try Data ( contentsOf: fileURL)
828+ let sizeInMB = Double ( data. count) / 1024 / 1024
829+ print ( " \n 📁 File: \( fileURL. lastPathComponent) ( \( String ( format: " %.2f " , sizeInMB) ) MB) " )
830+
831+ print ( " ⬆️ Uploading... " )
832+ let result = try await service. uploadAssets ( data: data)
833+
834+ print ( " \n ✅ Upload successful! " )
835+ print ( " 🎫 Received \( result. tokens. count) token(s): " )
836+ for (index, token) in result. tokens. enumerated ( ) {
837+ print ( " Token \( index + 1 ) : " )
838+ if let url = token. url {
839+ print ( " URL: \( url. prefix ( 50 ) ) ... " )
840+ }
841+ if let recordName = token. recordName {
842+ print ( " Record: \( recordName) " )
843+ }
844+ if let fieldName = token. fieldName {
845+ print ( " Field: \( fieldName) " )
846+ }
847+ }
848+
849+ // Optional: Create record with asset
850+ if let recordType = createRecord, let token = result. tokens. first {
851+ print ( " \n 📝 Creating \( recordType) record with asset... " )
852+ try await createRecordWithAsset (
853+ service: service,
854+ recordType: recordType,
855+ filename: fileURL. lastPathComponent,
856+ token: token,
857+ fileSize: data. count
858+ )
859+ }
860+
861+ } catch let error as CloudKitError {
862+ print ( " \n ❌ CloudKit Error: \( error) " )
863+ } catch {
864+ print ( " \n ❌ Error: \( error) " )
865+ }
866+
867+ print ( " \n " + String( repeating: " = " , count: 60 ) )
868+ print ( " ✅ uploadAssets test completed! " )
869+ print ( String ( repeating: " = " , count: 60 ) )
870+ }
871+
872+ private func createRecordWithAsset(
873+ service: CloudKitService ,
874+ recordType: String ,
875+ filename: String ,
876+ token: AssetUploadToken ,
877+ fileSize: Int
878+ ) async throws {
879+ let asset = FieldValue . Asset (
880+ fileChecksum: nil ,
881+ size: Int64 ( fileSize) ,
882+ referenceChecksum: nil ,
883+ wrappingKey: nil ,
884+ receipt: nil ,
885+ downloadURL: token. url
886+ )
887+
888+ let record = try await service. createRecord (
889+ recordType: recordType,
890+ fields: [
891+ " filename " : . string( filename) ,
892+ " file " : . asset( asset)
893+ ]
894+ )
895+
896+ print ( " ✅ Created record: \( record. recordName) " )
897+ print ( " 📝 Type: \( record. recordType) " )
898+ print ( " 🆔 Record ID: \( record. recordName) " )
899+ }
645900}
0 commit comments