Bug fixes
This commit is contained in:
parent
292180b204
commit
bbad8d6948
5 changed files with 195 additions and 166 deletions
|
@ -395,7 +395,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PDSMan/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -431,7 +431,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PDSMan/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
|
@ -387,54 +387,59 @@ class PDSService: ObservableObject {
|
|||
// MARK: - Invite Codes
|
||||
|
||||
func fetchInviteCodes() async {
|
||||
guard isAuthenticated, let baseURL = baseURL, let authHeader = authHeader else { return }
|
||||
|
||||
print("⏳ PDSService: Starting to fetch invite codes")
|
||||
self.isLoading = true
|
||||
defer { self.isLoading = false }
|
||||
|
||||
defer {
|
||||
self.isLoading = false
|
||||
guard let baseURL = baseURL, let authHeader = authHeader else {
|
||||
print("❌ PDSService: Cannot fetch invite codes - missing authentication")
|
||||
return
|
||||
}
|
||||
|
||||
// Construct the URL for the invite codes endpoint
|
||||
guard let inviteCodesURL = URL(string: "\(baseURL)/xrpc/com.atproto.admin.getInviteCodes") else {
|
||||
setError("Invalid invite codes URL")
|
||||
// Set up URL components for the request with any needed query parameters
|
||||
guard var components = URLComponents(string: "\(baseURL)/xrpc/com.atproto.admin.getInviteCodes") else {
|
||||
print("❌ PDSService: Invalid invite codes URL")
|
||||
return
|
||||
}
|
||||
|
||||
// Add query parameters
|
||||
var components = URLComponents(url: inviteCodesURL, resolvingAgainstBaseURL: true)
|
||||
components?.queryItems = [
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "sort", value: "recent"),
|
||||
URLQueryItem(name: "limit", value: "100"),
|
||||
URLQueryItem(name: "includeDisabled", value: "true") // Always include disabled codes
|
||||
URLQueryItem(name: "includeDisabled", value: "true")
|
||||
]
|
||||
|
||||
guard let finalURL = components?.url else {
|
||||
setError("Invalid invite codes URL with parameters")
|
||||
guard let url = components.url else {
|
||||
print("❌ PDSService: Failed to construct URL with query parameters")
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: finalURL)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.addValue(authHeader, forHTTPHeaderField: "Authorization")
|
||||
|
||||
do {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
setError("Invalid response from server")
|
||||
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
|
||||
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0
|
||||
print("❌ PDSService: Invite codes fetch failed with status \(statusCode)")
|
||||
return
|
||||
}
|
||||
|
||||
if httpResponse.statusCode == 200 {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
// Debug: Print raw response
|
||||
if let responseString = String(data: data, encoding: .utf8) {
|
||||
print("👀 PDSService: Raw invite codes response: \(responseString)")
|
||||
}
|
||||
|
||||
let codesResponse = try decoder.decode(InviteCodesResponse.self, from: data)
|
||||
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
|
||||
// Map the response to our model
|
||||
let parsedCodes = codesResponse.codes.map { codeResp -> InviteCode in
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
let createdDate = dateFormatter.date(from: codeResp.createdAt) ?? Date()
|
||||
|
||||
// Convert the uses array
|
||||
|
@ -450,13 +455,16 @@ class PDSService: ObservableObject {
|
|||
)
|
||||
}
|
||||
|
||||
// Update the inviteCodes property
|
||||
DispatchQueue.main.async {
|
||||
self.inviteCodes = parsedCodes
|
||||
} else {
|
||||
let responseString = String(data: data, encoding: .utf8) ?? "Unknown error"
|
||||
setError("Failed to fetch invite codes: \(httpResponse.statusCode) - \(responseString)")
|
||||
self.objectWillChange.send()
|
||||
print("✅ PDSService: Successfully fetched \(parsedCodes.count) invite codes")
|
||||
print("✅ PDSService: Including \(parsedCodes.filter { !$0.disabled }.count) active codes")
|
||||
}
|
||||
|
||||
} catch {
|
||||
setError("Failed to fetch invite codes: \(error.localizedDescription)")
|
||||
print("❌ PDSService: Error fetching invite codes: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -501,7 +509,13 @@ class PDSService: ObservableObject {
|
|||
)
|
||||
|
||||
// Update the local list
|
||||
DispatchQueue.main.async {
|
||||
self.inviteCodes.append(newCode)
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
|
||||
// Also refresh the full list to ensure we have the most up-to-date data
|
||||
await fetchInviteCodes()
|
||||
|
||||
return newCode
|
||||
} else {
|
||||
|
@ -516,10 +530,17 @@ class PDSService: ObservableObject {
|
|||
}
|
||||
|
||||
func disableInviteCode(_ code: String) async -> Bool {
|
||||
guard isAuthenticated, let baseURL = baseURL, let authHeader = authHeader else { return false }
|
||||
print("⏳ PDSService: Attempting to disable invite code: \(code)")
|
||||
self.isLoading = true
|
||||
defer { self.isLoading = false }
|
||||
|
||||
guard let baseURL = baseURL, let authHeader = authHeader else {
|
||||
print("❌ PDSService: Cannot disable code - missing authentication")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let disableURL = URL(string: "\(baseURL)/xrpc/com.atproto.admin.disableInviteCodes") else {
|
||||
setError("Invalid disable invite code URL")
|
||||
print("❌ PDSService: Invalid disable code URL")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -528,7 +549,7 @@ class PDSService: ObservableObject {
|
|||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue(authHeader, forHTTPHeaderField: "Authorization")
|
||||
|
||||
// Create the request body with an array of codes
|
||||
// Create the request body
|
||||
let disableBody = ["codes": [code]]
|
||||
|
||||
do {
|
||||
|
@ -538,21 +559,29 @@ class PDSService: ObservableObject {
|
|||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
setError("Invalid response from server")
|
||||
print("❌ PDSService: Invalid response from server")
|
||||
return false
|
||||
}
|
||||
|
||||
// Debug: Print response details
|
||||
if let responseString = String(data: data, encoding: .utf8) {
|
||||
print("👀 PDSService: Disable code response: \(responseString)")
|
||||
}
|
||||
|
||||
if httpResponse.statusCode == 200 {
|
||||
// Refresh the invite codes
|
||||
print("✅ PDSService: Successfully disabled code: \(code)")
|
||||
|
||||
// Refresh the invite codes list
|
||||
await fetchInviteCodes()
|
||||
|
||||
return true
|
||||
} else {
|
||||
let responseString = String(data: data, encoding: .utf8) ?? "Unknown error"
|
||||
setError("Failed to disable invite code: \(httpResponse.statusCode) - \(responseString)")
|
||||
print("❌ PDSService: Failed to disable code: \(httpResponse.statusCode) - \(responseString)")
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
setError("Failed to disable invite code: \(error.localizedDescription)")
|
||||
print("❌ PDSService: Error disabling code: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -560,53 +589,50 @@ class PDSService: ObservableObject {
|
|||
// MARK: - Users
|
||||
|
||||
func fetchUsers() async {
|
||||
guard isAuthenticated, let baseURL = baseURL, let authHeader = authHeader else { return }
|
||||
|
||||
print("⏳ PDSService: Starting to fetch users")
|
||||
self.isLoading = true
|
||||
defer { self.isLoading = false }
|
||||
|
||||
defer {
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
// Construct the URL for the repos endpoint
|
||||
guard let reposURL = URL(string: "\(baseURL)/xrpc/com.atproto.sync.listRepos") else {
|
||||
setError("Invalid list repos URL")
|
||||
guard let baseURL = baseURL, let authHeader = authHeader else {
|
||||
print("❌ PDSService: Cannot fetch users - missing authentication")
|
||||
return
|
||||
}
|
||||
|
||||
// Add query parameters
|
||||
var components = URLComponents(url: reposURL, resolvingAgainstBaseURL: true)
|
||||
components?.queryItems = [
|
||||
URLQueryItem(name: "limit", value: "100")
|
||||
]
|
||||
|
||||
guard let finalURL = components?.url else {
|
||||
setError("Invalid repos URL with parameters")
|
||||
// First, get a list of all repos (users) on the server
|
||||
guard let repoURL = URL(string: "\(baseURL)/xrpc/com.atproto.sync.listRepos") else {
|
||||
print("❌ PDSService: Invalid list repos URL")
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: finalURL)
|
||||
request.httpMethod = "GET"
|
||||
request.addValue(authHeader, forHTTPHeaderField: "Authorization")
|
||||
var repoRequest = URLRequest(url: repoURL)
|
||||
repoRequest.httpMethod = "GET"
|
||||
repoRequest.addValue(authHeader, forHTTPHeaderField: "Authorization")
|
||||
|
||||
do {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
let (repoData, repoResponse) = try await session.data(for: repoRequest)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
setError("Invalid response from server")
|
||||
guard let httpResponse = repoResponse as? HTTPURLResponse, httpResponse.statusCode == 200 else {
|
||||
let statusCode = (repoResponse as? HTTPURLResponse)?.statusCode ?? 0
|
||||
print("❌ PDSService: Repos fetch failed with status \(statusCode)")
|
||||
return
|
||||
}
|
||||
|
||||
if httpResponse.statusCode == 200 {
|
||||
// Debug: Print raw response
|
||||
if let responseString = String(data: repoData, encoding: .utf8) {
|
||||
print("👀 PDSService: Raw repos response: \(responseString)")
|
||||
}
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
let reposResponse = try decoder.decode(RepoResponse.self, from: data)
|
||||
let reposResult = try decoder.decode(RepoResponse.self, from: repoData)
|
||||
print("📊 PDSService: Found \(reposResult.repos.count) repos")
|
||||
|
||||
// Fetch details for each user
|
||||
// Fetch individual user profiles
|
||||
var fetchedUsers: [PDSUser] = []
|
||||
|
||||
for repo in reposResponse.repos {
|
||||
for repo in reposResult.repos {
|
||||
print("🔍 PDSService: Fetching profile for \(repo.did)")
|
||||
if let user = await fetchUserProfile(did: repo.did, isActive: repo.active) {
|
||||
fetchedUsers.append(user)
|
||||
}
|
||||
|
@ -615,13 +641,15 @@ class PDSService: ObservableObject {
|
|||
// Sort users by join date (newest first)
|
||||
fetchedUsers.sort { $0.joinedAt > $1.joinedAt }
|
||||
|
||||
// Update the users property
|
||||
DispatchQueue.main.async {
|
||||
self.users = fetchedUsers
|
||||
} else {
|
||||
let responseString = String(data: data, encoding: .utf8) ?? "Unknown error"
|
||||
setError("Failed to fetch users: \(httpResponse.statusCode) - \(responseString)")
|
||||
self.objectWillChange.send()
|
||||
print("✅ PDSService: Successfully fetched \(fetchedUsers.count) user profiles")
|
||||
}
|
||||
|
||||
} catch {
|
||||
setError("Failed to fetch users: \(error.localizedDescription)")
|
||||
print("❌ PDSService: Error fetching repos: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,47 @@ class PDSViewModel: ObservableObject {
|
|||
pdsService.inviteCodes
|
||||
}
|
||||
|
||||
// Add listeners for PDSService changes
|
||||
init() {
|
||||
// Subscribe to PDSService objectWillChange events
|
||||
pdsService.objectWillChange.sink { [weak self] _ in
|
||||
// Forward the change notification to our own objectWillChange
|
||||
DispatchQueue.main.async {
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// Storage for cancellables
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// Method to manually refresh UI data
|
||||
func refreshUI() {
|
||||
DispatchQueue.main.async {
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh invite codes with UI update
|
||||
func refreshInviteCodes() async {
|
||||
await pdsService.fetchInviteCodes()
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
// Refresh users with UI update
|
||||
func refreshUsers() async {
|
||||
await pdsService.fetchUsers()
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
// Disable invite code with guaranteed UI update
|
||||
func disableInviteCode(_ code: String) async -> Bool {
|
||||
let result = await pdsService.disableInviteCode(code)
|
||||
refreshUI()
|
||||
return result
|
||||
}
|
||||
|
||||
func login(serverURL: String, username: String, password: String) async {
|
||||
print("PDSViewModel: login called")
|
||||
if let credentials = PDSCredentials(serverURL: serverURL, username: username, password: password) {
|
||||
|
|
|
@ -34,7 +34,9 @@ struct InviteCodesView: View {
|
|||
Text("No invite codes found")
|
||||
.foregroundColor(.secondary)
|
||||
Button("Refresh") {
|
||||
refreshInviteCodes()
|
||||
Task {
|
||||
await viewModel.refreshInviteCodes()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
@ -49,7 +51,8 @@ struct InviteCodesView: View {
|
|||
.contextMenu {
|
||||
Button(role: .destructive) {
|
||||
Task {
|
||||
await viewModel.pdsService.disableInviteCode(code.id)
|
||||
// After disabling the code, refresh the list
|
||||
await viewModel.disableInviteCode(code.id)
|
||||
}
|
||||
} label: {
|
||||
Label("Disable Code", systemImage: "xmark.circle")
|
||||
|
@ -71,6 +74,10 @@ struct InviteCodesView: View {
|
|||
Toggle("Show Disabled Codes (\(disabledCodes.count))", isOn: $showDisabledCodes)
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.onChange(of: showDisabledCodes) { _ in
|
||||
// Refresh UI when toggle changes
|
||||
viewModel.refreshUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +88,7 @@ struct InviteCodesView: View {
|
|||
isCreatingCode = true
|
||||
Task {
|
||||
await viewModel.pdsService.createInviteCode()
|
||||
await viewModel.refreshInviteCodes()
|
||||
isCreatingCode = false
|
||||
}
|
||||
} label: {
|
||||
|
@ -92,43 +100,18 @@ struct InviteCodesView: View {
|
|||
}
|
||||
.disabled(isCreatingCode)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
refreshInviteCodes()
|
||||
} label: {
|
||||
if isRefreshing {
|
||||
ProgressView()
|
||||
} else {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
}
|
||||
}
|
||||
.disabled(isRefreshing)
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
await viewModel.pdsService.fetchInviteCodes()
|
||||
print("⏳ Pull-to-refresh: Fetching invite codes")
|
||||
await viewModel.refreshInviteCodes()
|
||||
print("✅ Pull-to-refresh completed")
|
||||
}
|
||||
}
|
||||
.task {
|
||||
// Only fetch if we don't already have data
|
||||
if viewModel.inviteCodes.isEmpty && !viewModel.isLoading {
|
||||
await viewModel.pdsService.fetchInviteCodes()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// Always attempt to refresh when view appears
|
||||
if !viewModel.isLoading {
|
||||
refreshInviteCodes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshInviteCodes() {
|
||||
isRefreshing = true
|
||||
Task {
|
||||
await viewModel.pdsService.fetchInviteCodes()
|
||||
isRefreshing = false
|
||||
print("⏳ Task: Fetching invite codes")
|
||||
// Always fetch on initial load
|
||||
await viewModel.refreshInviteCodes()
|
||||
print("✅ Task fetch completed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ struct UserListView: View {
|
|||
Text("No users found")
|
||||
.foregroundColor(.secondary)
|
||||
Button("Refresh") {
|
||||
refreshUsers()
|
||||
Task {
|
||||
await viewModel.refreshUsers()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
@ -70,22 +72,10 @@ struct UserListView: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("Users")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
refreshUsers()
|
||||
} label: {
|
||||
if isRefreshing {
|
||||
ProgressView()
|
||||
} else {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
}
|
||||
}
|
||||
.disabled(isRefreshing)
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
await viewModel.pdsService.fetchUsers()
|
||||
print("⏳ Pull-to-refresh: Fetching users")
|
||||
await viewModel.refreshUsers()
|
||||
print("✅ Pull-to-refresh completed")
|
||||
}
|
||||
.sheet(isPresented: $showingEditSheet) {
|
||||
if let user = editingUser {
|
||||
|
@ -105,6 +95,7 @@ struct UserListView: View {
|
|||
Task {
|
||||
if await viewModel.pdsService.editUserHandle(userId: user.id, newHandle: newHandle) {
|
||||
showingEditSheet = false
|
||||
await viewModel.refreshUsers() // Refresh to show updated handle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +114,7 @@ struct UserListView: View {
|
|||
if let user = selectedUser {
|
||||
Task {
|
||||
await viewModel.pdsService.suspendUser(userId: user.id, reason: suspensionReason)
|
||||
await viewModel.pdsService.fetchUsers() // Refresh after suspension
|
||||
await viewModel.refreshUsers() // Refresh after suspension
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +129,7 @@ struct UserListView: View {
|
|||
if let user = selectedUser {
|
||||
Task {
|
||||
await viewModel.pdsService.reactivateUser(userId: user.id)
|
||||
await viewModel.pdsService.fetchUsers() // Refresh after reactivation
|
||||
await viewModel.refreshUsers() // Refresh after reactivation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,24 +140,10 @@ struct UserListView: View {
|
|||
}
|
||||
}
|
||||
.task {
|
||||
// Only fetch if we don't already have data
|
||||
if viewModel.users.isEmpty && !viewModel.isLoading {
|
||||
await viewModel.pdsService.fetchUsers()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// Always attempt to refresh when view appears
|
||||
if !viewModel.isLoading {
|
||||
refreshUsers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshUsers() {
|
||||
isRefreshing = true
|
||||
Task {
|
||||
await viewModel.pdsService.fetchUsers()
|
||||
isRefreshing = false
|
||||
print("⏳ Task: Fetching users")
|
||||
// Always fetch on initial load
|
||||
await viewModel.refreshUsers()
|
||||
print("✅ Task fetch completed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue