summaryrefslogtreecommitdiff
path: root/Hutch/Networking/RepositoryDeployKeyService.swift
blob: 39e2da168921e65035391c4c1d8c74c4f2c33f8d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import Foundation

/// A per-repository deploy key (git.sr.ht `SSHKey` under `Repository.deployKeys`).
struct RepositoryDeployKey: Decodable, Sendable, Identifiable, Hashable {
    let rid: String
    let keyType: String
    let fingerprintSHA256: String
    let comment: String?
    let access: AccessMode

    var id: String { rid }
}

private struct DeployKeysQueryResponse: Decodable, Sendable {
    let repository: DeployKeysRepository?
}

private struct DeployKeysRepository: Decodable, Sendable {
    let deployKeys: DeployKeysPage
}

private struct DeployKeysPage: Decodable, Sendable {
    let results: [RepositoryDeployKey]
}

/// `createDeployKey`'s response returns an empty `access` (the stored value is
/// correct — the list query reports it), so we select only `rid` here and let
/// callers reload rather than decode the partial key.
private struct CreateDeployKeyResponse: Decodable, Sendable {}

/// Delete returns the removed key; only success matters here.
private struct DeleteDeployKeyResponse: Decodable, Sendable {}

/// Deploy keys are a git.sr.ht capability (`createDeployKey` / `deleteDeployKey`),
/// owner-only, alongside repository ACLs.
struct RepositoryDeployKeyService: Sendable {
    private let client: SRHTClient

    init(client: SRHTClient) {
        self.client = client
    }

    func fetchDeployKeys(repositoryRid: String) async throws -> [RepositoryDeployKey] {
        let response = try await client.execute(
            service: .git,
            query: Self.deployKeysQuery,
            variables: ["rid": repositoryRid],
            responseType: DeployKeysQueryResponse.self
        )
        return response.repository?.deployKeys.results ?? []
    }

    func createDeployKey(repositoryRid: String, mode: AccessMode, key: String) async throws {
        _ = try await client.execute(
            service: .git,
            query: Self.createDeployKeyMutation,
            variables: ["repo": repositoryRid, "mode": mode.rawValue, "key": key],
            responseType: CreateDeployKeyResponse.self
        )
    }

    func deleteDeployKey(rid: String) async throws {
        _ = try await client.execute(
            service: .git,
            query: Self.deleteDeployKeyMutation,
            variables: ["rid": rid],
            responseType: DeleteDeployKeyResponse.self
        )
    }
}

private extension RepositoryDeployKeyService {
    static let deployKeysQuery = """
    query repositoryDeployKeys($rid: ID!) {
        repository(rid: $rid) {
            deployKeys {
                results {
                    rid
                    keyType
                    fingerprintSHA256
                    comment
                    access
                }
            }
        }
    }
    """

    static let createDeployKeyMutation = """
    mutation createDeployKey($repo: ID!, $mode: AccessMode!, $key: String!) {
        createDeployKey(repo: $repo, mode: $mode, key: $key) { rid }
    }
    """

    static let deleteDeployKeyMutation = """
    mutation deleteDeployKey($rid: ID!) {
        deleteDeployKey(rid: $rid) { rid }
    }
    """
}