Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

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} 147 148// CreateIssueComment adds a comment to an issue or pull request — Forgejo 149// (like Gitea) treats PR comments as issue comments under the same 150// endpoint, verified against a live PR on f1. 151func (c *Client) CreateIssueComment(index int64, body string) error { 152 path := fmt.Sprintf("/repos/%s/%s/issues/%d/comments", c.owner, c.repo, index) 153 req := map[string]string{"body": body} 154 return c.do(http.MethodPost, path, req, nil) 155}