this repo has no description

Now we fetch the mini did doc from slingshot

+193 -14
+73
AtProtoBackup/ATProtocolService.swift
···
··· 1 + // 2 + // ATProtocolService.swift 3 + // AtProtoBackup 4 + // 5 + // Created by Assistant on 8/26/25. 6 + // 7 + 8 + import Foundation 9 + 10 + struct DIDResponse: Codable { 11 + let did: String 12 + } 13 + 14 + class ATProtocolService { 15 + static let shared = ATProtocolService() 16 + 17 + private init() {} 18 + 19 + func lookupDID(for handle: String) async throws -> (did: String, jsonData: Data) { 20 + print("[ATProtocolService] lookupDID called with handle: '\(handle)'") 21 + 22 + guard let url = URL(string: "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=\(handle)") else { 23 + print("[ATProtocolService] Failed to create URL") 24 + throw ATProtocolError.invalidURL 25 + } 26 + 27 + print("[ATProtocolService] Making request to: \(url)") 28 + let (data, response) = try await URLSession.shared.data(from: url) 29 + 30 + print("[ATProtocolService] Received response") 31 + 32 + guard let httpResponse = response as? HTTPURLResponse else { 33 + print("[ATProtocolService] Response is not HTTPURLResponse") 34 + throw ATProtocolError.invalidResponse 35 + } 36 + 37 + print("[ATProtocolService] HTTP Status Code: \(httpResponse.statusCode)") 38 + 39 + guard httpResponse.statusCode == 200 else { 40 + print("[ATProtocolService] Non-200 status code") 41 + throw ATProtocolError.invalidResponse 42 + } 43 + 44 + print("[ATProtocolService] Response data length: \(data.count) bytes") 45 + print("[ATProtocolService] Raw response: \(String(data: data, encoding: .utf8) ?? "Unable to decode as UTF-8")") 46 + 47 + do { 48 + let didResponse = try JSONDecoder().decode(DIDResponse.self, from: data) 49 + print("[ATProtocolService] Successfully decoded - DID: \(didResponse.did)") 50 + return (did: didResponse.did, jsonData: data) 51 + } catch { 52 + print("[ATProtocolService] Decoding error: \(error)") 53 + throw ATProtocolError.decodingError 54 + } 55 + } 56 + } 57 + 58 + enum ATProtocolError: LocalizedError { 59 + case invalidURL 60 + case invalidResponse 61 + case decodingError 62 + 63 + var errorDescription: String? { 64 + switch self { 65 + case .invalidURL: 66 + return "Invalid URL" 67 + case .invalidResponse: 68 + return "Invalid response from server" 69 + case .decodingError: 70 + return "Failed to decode response" 71 + } 72 + } 73 + }
+6 -1
AtProtoBackup/Account.swift
··· 12 @Model 13 final class Account { 14 var did: String 15 16 - init(did: String) { 17 self.did = did 18 } 19 }
··· 12 @Model 13 final class Account { 14 var did: String 15 + var handle: String 16 + var jsonResponse: Data? 17 18 + init(did: String, handle: String, jsonResponse: Data? = nil) { 19 + print("[Account] Creating new Account - DID: \(did), Handle: \(handle)") 20 self.did = did 21 + self.handle = handle 22 + self.jsonResponse = jsonResponse 23 } 24 }
+2
AtProtoBackup/AtProtoBackup.entitlements
··· 6 <true/> 7 <key>com.apple.security.files.user-selected.read-only</key> 8 <true/> 9 </dict> 10 </plist>
··· 6 <true/> 7 <key>com.apple.security.files.user-selected.read-only</key> 8 <true/> 9 + <key>com.apple.security.network.client</key> 10 + <true/> 11 </dict> 12 </plist>
+1 -1
AtProtoBackup/AtProtoBackupApp.swift
··· 14 let schema = Schema([ 15 Account.self, 16 ]) 17 - let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) 18 19 do { 20 return try ModelContainer(for: schema, configurations: [modelConfiguration])
··· 14 let schema = Schema([ 15 Account.self, 16 ]) 17 + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) 18 19 do { 20 return try ModelContainer(for: schema, configurations: [modelConfiguration])
+111 -12
AtProtoBackup/ContentView.swift
··· 13 @Query private var items: [Account] 14 @State private var showingAlert = false 15 @State private var inputText = "" 16 17 var body: some View { 18 NavigationSplitView { 19 List { 20 ForEach(items) { item in 21 NavigationLink { 22 - Text("Item at \(item.did)") 23 } label: { 24 - Text(item.did) 25 } 26 } 27 .onDelete(perform: deleteItems) ··· 46 47 } detail: { 48 Text("Select an item") 49 - }.alert("Test", isPresented: $showingAlert, actions: { 50 - TextField("Type something...", text: $inputText) 51 - Button("OK") { 52 - print("User entered: \(inputText)") 53 - showingAlert = false 54 - withAnimation { 55 - modelContext.insert(Account(did: inputText)) 56 } 57 } 58 - Button("Cancel", role: .cancel) { } 59 }) 60 61 } 62 63 64 private func addItem() { 65 showingAlert = true 66 67 - 68 - 69 } 70 71 private func deleteItems(offsets: IndexSet) {
··· 13 @Query private var items: [Account] 14 @State private var showingAlert = false 15 @State private var inputText = "" 16 + @State private var isLoading = false 17 + @State private var errorMessage: String? 18 19 var body: some View { 20 NavigationSplitView { 21 List { 22 ForEach(items) { item in 23 NavigationLink { 24 + VStack(alignment: .leading, spacing: 16) { 25 + Text("Account Details") 26 + .font(.title2) 27 + .fontWeight(.bold) 28 + 29 + VStack(alignment: .leading, spacing: 8) { 30 + HStack { 31 + Text("Handle:") 32 + .fontWeight(.semibold) 33 + Text(item.handle) 34 + } 35 + 36 + HStack { 37 + Text("DID:") 38 + .fontWeight(.semibold) 39 + Text(item.did) 40 + .lineLimit(1) 41 + .truncationMode(.middle) 42 + } 43 + } 44 + 45 + if let jsonData = item.jsonResponse { 46 + VStack(alignment: .leading, spacing: 8) { 47 + Text("JSON Response:") 48 + .font(.headline) 49 + 50 + ScrollView { 51 + if let jsonObject = try? JSONSerialization.jsonObject(with: jsonData), 52 + let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]), 53 + let prettyString = String(data: prettyData, encoding: .utf8) { 54 + Text(prettyString) 55 + .font(.system(.body, design: .monospaced)) 56 + .textSelection(.enabled) 57 + .padding() 58 + .background(Color.gray.opacity(0.1)) 59 + .cornerRadius(8) 60 + } else { 61 + Text("Unable to decode JSON data") 62 + .foregroundColor(.secondary) 63 + } 64 + } 65 + } 66 + } else { 67 + Text("No JSON response available") 68 + .foregroundColor(.secondary) 69 + } 70 + 71 + Spacer() 72 + } 73 + .padding() 74 + .navigationTitle(item.handle) 75 } label: { 76 + VStack(alignment: .leading) { 77 + Text(item.handle) 78 + .font(.headline) 79 + Text(item.did) 80 + .font(.caption) 81 + .foregroundColor(.secondary) 82 + } 83 } 84 } 85 .onDelete(perform: deleteItems) ··· 104 105 } detail: { 106 Text("Select an item") 107 + }.alert("Add AT Protocol Account", isPresented: $showingAlert, actions: { 108 + TextField("Enter handle (e.g., user.bsky.social)", text: $inputText) 109 + Button("Add") { 110 + print("[ContentView] Add button in alert pressed") 111 + Task { 112 + await lookupAndAddAccount() 113 } 114 } 115 + Button("Cancel", role: .cancel) { 116 + inputText = "" 117 + errorMessage = nil 118 + } 119 + }, message: { 120 + if let errorMessage = errorMessage { 121 + Text(errorMessage) 122 + } else if isLoading { 123 + Text("Looking up DID...") 124 + } else { 125 + Text("Enter your AT Protocol handle") 126 + } 127 }) 128 129 } 130 131 132 private func addItem() { 133 + print("[ContentView] Add button clicked") 134 + inputText = "" 135 + errorMessage = nil 136 showingAlert = true 137 + } 138 + 139 + private func lookupAndAddAccount() async { 140 + print("[ContentView] lookupAndAddAccount called with handle: '\(inputText)'") 141 + isLoading = true 142 + errorMessage = nil 143 144 + do { 145 + print("[ContentView] Starting API lookup...") 146 + let (did, jsonData) = try await ATProtocolService.shared.lookupDID(for: inputText) 147 + print("[ContentView] API lookup successful - DID: \(did)") 148 + 149 + await MainActor.run { 150 + print("[ContentView] Creating and inserting Account object") 151 + withAnimation { 152 + let account = Account(did: did, handle: inputText, jsonResponse: jsonData) 153 + modelContext.insert(account) 154 + print("[ContentView] Account inserted into modelContext") 155 + } 156 + showingAlert = false 157 + inputText = "" 158 + isLoading = false 159 + print("[ContentView] Alert dismissed, operation complete") 160 + } 161 + } catch { 162 + print("[ContentView] Error during lookup: \(error)") 163 + await MainActor.run { 164 + errorMessage = "Error: \(error.localizedDescription)" 165 + isLoading = false 166 + } 167 + } 168 } 169 170 private func deleteItems(offsets: IndexSet) {