tangled
alpha
login
or
join now
coreyja.com
/
backup-blue
1
fork
atom
this repo has no description
1
fork
atom
overview
issues
pulls
pipelines
Now we fetch the mini did doc from slingshot
Corey Alexander
6 months ago
01c0320b
67e88bdd
+193
-14
5 changed files
expand all
collapse all
unified
split
AtProtoBackup
ATProtocolService.swift
Account.swift
AtProtoBackup.entitlements
AtProtoBackupApp.swift
ContentView.swift
+73
AtProtoBackup/ATProtocolService.swift
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
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
0
0
15
16
-
init(did: String) {
0
17
self.did = did
0
0
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/>
0
0
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 = ""
0
0
16
17
var body: some View {
18
NavigationSplitView {
19
List {
20
ForEach(items) { item in
21
NavigationLink {
22
-
Text("Item at \(item.did)")
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
23
} label: {
24
-
Text(item.did)
0
0
0
0
0
0
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) { }
0
0
0
0
0
0
0
0
0
0
0
59
})
60
61
}
62
63
64
private func addItem() {
0
0
0
65
showingAlert = true
0
0
0
0
0
0
66
67
-
68
-
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
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()
0
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) {