gitbay/Repos/RepoSettingsViewModel.swift
102 lines · 3233 bytes
1import Foundation
2import Observation
3
4/// The repo settings screen: `repo settings show` for the admin knobs,
5/// `repo show` for description/topics/visibility, and one write command
6/// per control. Non-admins get the server's refusal verbatim.
7@Observable
8@MainActor
9final class RepoSettingsViewModel {
10
11 nonisolated struct Loaded: Sendable, Hashable {
12 let settings: RepoSettings
13 let detail: RepoDetail
14 }
15
16 private(set) var state: LoadState<Loaded> = .loading
17 private(set) var actionError: String?
18 private(set) var working = false
19
20 private let client: GitbayClient
21 let repoPath: String
22
23 init(client: GitbayClient, repoPath: String) {
24 self.client = client
25 self.repoPath = repoPath
26 }
27
28 func load() async {
29 do {
30 async let settings = client.read(
31 ["repo", "settings", "show", repoPath], as: RepoSettings.self)
32 async let detail = client.read(["repo", "show", repoPath], as: RepoDetail.self)
33 state = .loaded(Loaded(settings: try await settings, detail: try await detail))
34 } catch {
35 state = .from(error)
36 }
37 }
38
39 // MARK: - Writes, one command per knob
40
41 func setDescription(_ text: String) async {
42 await perform(["repo", "settings", "description", repoPath, text])
43 }
44
45 func setWebsite(_ url: String) async {
46 await perform(["repo", "settings", "website", repoPath, url])
47 }
48
49 func setVisibility(_ visibility: String) async {
50 await perform(["repo", "settings", "visibility", repoPath, visibility])
51 }
52
53 func setGitDaemon(_ on: Bool) async {
54 await perform(["repo", "settings", "git-daemon", repoPath, on ? "on" : "off"])
55 }
56
57 func protectBranch(_ branch: String) async {
58 await perform(["repo", "settings", "protect", repoPath, branch])
59 }
60
61 func unprotectBranch(_ branch: String) async {
62 await perform(["repo", "settings", "unprotect", repoPath, branch])
63 }
64
65 func setRequireApprovals(_ count: Int) async {
66 await perform(["repo", "settings", "require-approvals", repoPath, String(count)])
67 }
68
69 func setRequireResolved(_ on: Bool) async {
70 await perform(["repo", "settings", "require-resolved", repoPath, on ? "on" : "off"])
71 }
72
73 func setRequireChecks(_ on: Bool) async {
74 await perform(["repo", "settings", "require-checks", repoPath, on ? "on" : "off"])
75 }
76
77 func setRequireSigned(_ on: Bool) async {
78 await perform(["repo", "settings", "require-signed", repoPath, on ? "on" : "off"])
79 }
80
81 func addTopic(_ topic: String) async {
82 await perform(["repo", "topics", "add", repoPath, topic])
83 }
84
85 func removeTopic(_ topic: String) async {
86 await perform(["repo", "topics", "remove", repoPath, topic])
87 }
88
89 private func perform(_ argv: [String]) async {
90 working = true
91 actionError = nil
92 defer { working = false }
93 do {
94 try await client.run(argv)
95 await load()
96 } catch let error as GitbayError {
97 actionError = error.userFacingMessage
98 } catch {
99 actionError = GitbayError.transport(error).userFacingMessage
100 }
101 }
102}