gitbay/Admin/AdminViewModels.swift

v1.9.0
gitbay-ios/gitbay/Admin/AdminViewModels.swift history · blame · raw

106 lines · 3054 bytes

  1import Foundation
  2import Observation
  3
  4/// Whether the signed-in account is an instance admin, from `whoami`. A
  5/// failed read counts as not: the account menu then leaves Admin out.
  6enum AdminCheck {
  7
  8    nonisolated private struct Who: Decodable, Sendable {
  9        let admin: Bool?
 10    }
 11
 12    static func isAdmin(_ client: GitbayClient) async -> Bool {
 13        ((try? await client.read(["whoami"], as: Who.self))?.admin) ?? false
 14    }
 15}
 16
 17/// The admin screen: `dashboard`'s server build and worker queues, the
 18/// read the web's /admin page makes.
 19@Observable
 20@MainActor
 21final class AdminOverviewViewModel {
 22
 23    nonisolated struct Overview: Sendable, Hashable {
 24        let commit: String?
 25        let sections: [QueueSection]
 26    }
 27
 28    private(set) var state: LoadState<Overview> = .loading
 29
 30    private let client: GitbayClient
 31
 32    init(client: GitbayClient) {
 33        self.client = client
 34    }
 35
 36    func load() async {
 37        do {
 38            let dashboard = try await client.read(["dashboard"], as: AdminDashboard.self)
 39            guard let queues = dashboard.queues else {
 40                state = .empty("Only instance admins can see the worker queues.")
 41                return
 42            }
 43            state = .loaded(Overview(commit: dashboard.server?.commit, sections: queues.sections()))
 44        } catch {
 45            state = .from(error)
 46        }
 47    }
 48}
 49
 50/// `admin user list`, narrowed by the web's five filters, and the four
 51/// writes /admin/users makes per row.
 52@Observable
 53@MainActor
 54final class AdminUsersViewModel {
 55
 56    let list: PagedListModel<AdminUser>
 57    var filter = AdminUserFilter.all {
 58        didSet {
 59            guard filter != oldValue else { return }
 60            list.argv = ["admin", "user", "list"] + filter.flags()
 61            reloadTask?.cancel()
 62            reloadTask = Task { await list.reload() }
 63        }
 64    }
 65    private var reloadTask: Task<Void, Never>?
 66    private(set) var actionError: String?
 67    private(set) var working = false
 68
 69    private let client: GitbayClient
 70
 71    init(client: GitbayClient) {
 72        self.client = client
 73        list = PagedListModel(
 74            client: client,
 75            argv: ["admin", "user", "list"],
 76            emptyMessage: "No account matches"
 77        )
 78    }
 79
 80    func load() async {
 81        reloadTask?.cancel()
 82        await list.reload()
 83    }
 84
 85    /// The list's `.task`: reads only while nothing is shown yet.
 86    func loadIfNeeded() async {
 87        guard list.needsLoad else { return }
 88        await load()
 89    }
 90
 91    func perform(_ action: AdminAction, on username: String) async {
 92        working = true
 93        actionError = nil
 94        defer { working = false }
 95        do {
 96            try await client.run(action.argv(username))
 97        } catch let error as GitbayError {
 98            actionError = error.userFacingMessage
 99        } catch {
100            actionError = GitbayError.transport(error).userFacingMessage
101        }
102        reloadTask?.cancel()
103        reloadTask = Task { await list.reload() }
104        await reloadTask?.value
105    }
106}