1
0
Fork 0

Bug fixes

This commit is contained in:
Atridad Lahiji 2025-03-19 01:55:58 -06:00
parent 292180b204
commit bbad8d6948
Signed by: atridad
SSH key fingerprint: SHA256:LGomp8Opq0jz+7kbwNcdfTcuaLRb5Nh0k5AchDDb438
5 changed files with 195 additions and 166 deletions

View file

@ -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;

View file

@ -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)")
}
}

View file

@ -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) {

View file

@ -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")
}
}
}

View file

@ -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")
}
}
}