160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
package forgejo
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Client struct {
|
|
baseURL string
|
|
token string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func NewClient(baseURL, token string) *Client {
|
|
return &Client{
|
|
baseURL: strings.TrimRight(baseURL, "/"),
|
|
token: token,
|
|
httpClient: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *Client) BaseURL() string {
|
|
return c.baseURL
|
|
}
|
|
|
|
// Issue represents a Forgejo issue.
|
|
type Issue struct {
|
|
ID int64 `json:"id"`
|
|
Number int `json:"number"`
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
State string `json:"state"`
|
|
HTMLURL string `json:"html_url"`
|
|
}
|
|
|
|
// CreateIssueRequest is the body for creating a Forgejo issue.
|
|
type CreateIssueRequest struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Labels []int64 `json:"labels,omitempty"`
|
|
}
|
|
|
|
// CommitStatus represents a Forgejo commit status.
|
|
type CommitStatus struct {
|
|
State string `json:"state"`
|
|
TargetURL string `json:"target_url,omitempty"`
|
|
Description string `json:"description"`
|
|
Context string `json:"context"`
|
|
}
|
|
|
|
// CreateIssue creates a new issue on a Forgejo repository.
|
|
func (c *Client) CreateIssue(ctx context.Context, owner, repo string, req CreateIssueRequest) (*Issue, error) {
|
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner, repo)
|
|
var issue Issue
|
|
if err := c.post(ctx, path, req, &issue); err != nil {
|
|
return nil, fmt.Errorf("creating issue: %w", err)
|
|
}
|
|
return &issue, nil
|
|
}
|
|
|
|
// UpdateIssueState changes the state of an issue (open/closed).
|
|
func (c *Client) UpdateIssueState(ctx context.Context, owner, repo string, number int, state string) error {
|
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", owner, repo, number)
|
|
body := map[string]string{"state": state}
|
|
return c.patch(ctx, path, body)
|
|
}
|
|
|
|
// CreateCommitStatus posts a commit status (success/failure/pending).
|
|
func (c *Client) CreateCommitStatus(ctx context.Context, owner, repo, sha string, status CommitStatus) error {
|
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", owner, repo, sha)
|
|
return c.post(ctx, path, status, nil)
|
|
}
|
|
|
|
// CommentOnIssue adds a comment to an issue.
|
|
func (c *Client) CommentOnIssue(ctx context.Context, owner, repo string, number int, body string) error {
|
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, number)
|
|
return c.post(ctx, path, map[string]string{"body": body}, nil)
|
|
}
|
|
|
|
// CreateWebhook registers a webhook on a repository.
|
|
func (c *Client) CreateWebhook(ctx context.Context, owner, repo, targetURL, secret string) error {
|
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/hooks", owner, repo)
|
|
body := map[string]any{
|
|
"type": "forgejo",
|
|
"active": true,
|
|
"config": map[string]string{
|
|
"url": targetURL,
|
|
"content_type": "json",
|
|
"secret": secret,
|
|
},
|
|
"events": []string{"push", "issues", "pull_request"},
|
|
}
|
|
return c.post(ctx, path, body, nil)
|
|
}
|
|
|
|
func (c *Client) do(ctx context.Context, method, path string, body any) (*http.Response, error) {
|
|
var bodyReader io.Reader
|
|
if body != nil {
|
|
data, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bodyReader = bytes.NewReader(data)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bodyReader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
if c.token != "" {
|
|
req.Header.Set("Authorization", "token "+c.token)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode >= 400 {
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
_ = resp.Body.Close()
|
|
return nil, fmt.Errorf("forgejo API %s %s: %d %s", method, path, resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *Client) post(ctx context.Context, path string, body any, result any) error {
|
|
resp, err := c.do(ctx, http.MethodPost, path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if result != nil {
|
|
return json.NewDecoder(resp.Body).Decode(result)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) patch(ctx context.Context, path string, body any) error {
|
|
resp, err := c.do(ctx, http.MethodPatch, path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_ = resp.Body.Close()
|
|
return nil
|
|
}
|