// Package gitea provides a minimal Gitea API client for label management. package gitea import ( "bytes" "encoding/json" "fmt" "net/http" "time" ) // Client is a minimal Gitea API client. type Client struct { BaseURL string Token string http *http.Client } // New creates a new Gitea client. func New(baseURL, token string) *Client { return &Client{ BaseURL: baseURL, Token: token, http: &http.Client{Timeout: 10 * time.Second}, } } // Issue represents a Gitea issue. type Issue struct { Number int `json:"number"` Title string `json:"title"` HTMLURL string `json:"html_url"` Labels []Label `json:"labels"` } // HasLabel reports whether the issue carries a label with the given name. func (i Issue) HasLabel(name string) bool { for _, l := range i.Labels { if l.Name == name { return true } } return false } // LabelID returns the ID of the first label matching name, or 0. func (i Issue) LabelID(name string) int64 { for _, l := range i.Labels { if l.Name == name { return l.ID } } return 0 } // Label represents a Gitea label. type Label struct { ID int64 `json:"id"` Name string `json:"name"` Color string `json:"color"` } func (c *Client) newRequest(method, path string, body []byte) (*http.Request, error) { var req *http.Request var err error if body != nil { req, err = http.NewRequest(method, c.BaseURL+path, bytes.NewReader(body)) } else { req, err = http.NewRequest(method, c.BaseURL+path, nil) } if err != nil { return nil, err } req.Header.Set("Authorization", "token "+c.Token) if body != nil { req.Header.Set("Content-Type", "application/json") } return req, nil } // ListOpenIssues returns open issues for the given owner/repo (up to 50). func (c *Client) ListOpenIssues(owner, repo string) ([]Issue, error) { return c.listIssues(owner, repo, "") } // ListIssuesByLabel returns open issues that carry the named label (up to 50). func (c *Client) ListIssuesByLabel(owner, repo, label string) ([]Issue, error) { return c.listIssues(owner, repo, label) } func (c *Client) listIssues(owner, repo, label string) ([]Issue, error) { path := fmt.Sprintf("/api/v1/repos/%s/%s/issues?type=issues&state=open&limit=50", owner, repo) if label != "" { path += "&labels=" + label } req, err := c.newRequest("GET", path, nil) if err != nil { return nil, err } resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("gitea: list issues: %s", resp.Status) } var issues []Issue if err := json.NewDecoder(resp.Body).Decode(&issues); err != nil { return nil, err } return issues, nil } // GetOrCreateLabel finds a repo label by name, creating it if absent. func (c *Client) GetOrCreateLabel(owner, repo, name, color string) (*Label, error) { path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo) req, err := c.newRequest("GET", path, nil) if err != nil { return nil, err } resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var labels []Label json.NewDecoder(resp.Body).Decode(&labels) //nolint:errcheck for _, l := range labels { if l.Name == name { return &l, nil } } // Create the label. body, _ := json.Marshal(map[string]string{"name": name, "color": color}) req2, err := c.newRequest("POST", path, body) if err != nil { return nil, err } resp2, err := c.http.Do(req2) if err != nil { return nil, err } defer resp2.Body.Close() if resp2.StatusCode >= 300 { return nil, fmt.Errorf("gitea: create label: %s", resp2.Status) } var label Label if err := json.NewDecoder(resp2.Body).Decode(&label); err != nil { return nil, err } return &label, nil } // AddLabel adds a label to an issue by label ID. func (c *Client) AddLabel(owner, repo string, issueNum int, labelID int64) error { path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner, repo, issueNum) body, _ := json.Marshal(map[string][]int64{"labels": {labelID}}) req, err := c.newRequest("POST", path, body) if err != nil { return err } resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 { return fmt.Errorf("gitea: add label: %s", resp.Status) } return nil } // RemoveLabel removes a label from an issue by label ID. func (c *Client) RemoveLabel(owner, repo string, issueNum int, labelID int64) error { path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels/%d", owner, repo, issueNum, labelID) req, err := c.newRequest("DELETE", path, nil) if err != nil { return err } resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 && resp.StatusCode != http.StatusNoContent { return fmt.Errorf("gitea: remove label: %s", resp.Status) } return nil }