Federated via graft
3.9 kB
146 lines
1// Package forgejo is a minimal client for the subset of the Forgejo REST
2// API the sync daemon needs: reading/creating issues and pull requests,
3// and resolving a repository's clone URL and default branch.
4package forgejo
5
6import (
7 "bytes"
8 "encoding/json"
9 "fmt"
10 "io"
11 "net/http"
12 "strings"
13 "time"
14)
15
16// Client talks to one Forgejo instance on behalf of one repository.
17type Client struct {
18 baseURL string
19 owner string
20 repo string
21 token string
22 http *http.Client
23}
24
25func New(baseURL, owner, repo, token string) *Client {
26 return &Client{
27 baseURL: strings.TrimRight(baseURL, "/"),
28 owner: owner,
29 repo: repo,
30 token: token,
31 http: &http.Client{Timeout: 30 * time.Second},
32 }
33}
34
35func (c *Client) do(method, path string, body any, out any) error {
36 var reqBody io.Reader
37 if body != nil {
38 b, err := json.Marshal(body)
39 if err != nil {
40 return fmt.Errorf("encode request: %w", err)
41 }
42 reqBody = bytes.NewReader(b)
43 }
44
45 url := c.baseURL + "/api/v1" + path
46 req, err := http.NewRequest(method, url, reqBody)
47 if err != nil {
48 return fmt.Errorf("build request: %w", err)
49 }
50 req.Header.Set("Authorization", "token "+c.token)
51 req.Header.Set("Content-Type", "application/json")
52
53 resp, err := c.http.Do(req)
54 if err != nil {
55 return fmt.Errorf("%s %s: %w", method, path, err)
56 }
57 defer resp.Body.Close()
58
59 if resp.StatusCode >= 300 {
60 respBody, _ := io.ReadAll(resp.Body)
61 return fmt.Errorf("%s %s: HTTP %d: %s", method, path, resp.StatusCode, string(respBody))
62 }
63
64 if out != nil {
65 if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
66 return fmt.Errorf("decode response for %s %s: %w", method, path, err)
67 }
68 }
69 return nil
70}
71
72// Repository is the subset of Forgejo's repository object the daemon uses.
73type Repository struct {
74 CloneURL string `json:"clone_url"`
75 DefaultBranch string `json:"default_branch"`
76}
77
78func (c *Client) GetRepository() (*Repository, error) {
79 var repo Repository
80 path := fmt.Sprintf("/repos/%s/%s", c.owner, c.repo)
81 if err := c.do(http.MethodGet, path, nil, &repo); err != nil {
82 return nil, err
83 }
84 return &repo, nil
85}
86
87// Issue is the subset of Forgejo's issue object the daemon reads and writes.
88type Issue struct {
89 Index int64 `json:"number"`
90 Title string `json:"title"`
91 Body string `json:"body"`
92 State string `json:"state"`
93}
94
95func (c *Client) ListIssues() ([]Issue, error) {
96 var issues []Issue
97 path := fmt.Sprintf("/repos/%s/%s/issues?type=issues&state=all&limit=50", c.owner, c.repo)
98 if err := c.do(http.MethodGet, path, nil, &issues); err != nil {
99 return nil, err
100 }
101 return issues, nil
102}
103
104func (c *Client) CreateIssue(title, body string) (*Issue, error) {
105 var issue Issue
106 path := fmt.Sprintf("/repos/%s/%s/issues", c.owner, c.repo)
107 req := map[string]string{"title": title, "body": body}
108 if err := c.do(http.MethodPost, path, req, &issue); err != nil {
109 return nil, err
110 }
111 return &issue, nil
112}
113
114// PullRequest is the subset of Forgejo's pull request object the daemon
115// reads and writes.
116type PullRequest struct {
117 Index int64 `json:"number"`
118 Title string `json:"title"`
119 Body string `json:"body"`
120 State string `json:"state"`
121 Head Branch `json:"head"`
122 Base Branch `json:"base"`
123}
124
125type Branch struct {
126 Ref string `json:"ref"`
127}
128
129func (c *Client) ListPullRequests() ([]PullRequest, error) {
130 var prs []PullRequest
131 path := fmt.Sprintf("/repos/%s/%s/pulls?state=all&limit=50", c.owner, c.repo)
132 if err := c.do(http.MethodGet, path, nil, &prs); err != nil {
133 return nil, err
134 }
135 return prs, nil
136}
137
138func (c *Client) CreatePullRequest(title, body, head, base string) (*PullRequest, error) {
139 var pr PullRequest
140 path := fmt.Sprintf("/repos/%s/%s/pulls", c.owner, c.repo)
141 req := map[string]string{"title": title, "body": body, "head": head, "base": base}
142 if err := c.do(http.MethodPost, path, req, &pr); err != nil {
143 return nil, err
144 }
145 return &pr, nil
146}