Add PR checks command, iostreams/text packages for colored table output, top-level run/workflow aliases matching gh CLI structure. Enhance actions, issues, PRs, releases, repos, labels, milestones, and wiki commands with improved flags, JSON output, and error handling.
240 lines
6.2 KiB
Go
240 lines
6.2 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"code.gitea.io/sdk/gitea"
|
|
"forgejo.zerova.net/sid/fgj-sid/internal/config"
|
|
)
|
|
|
|
var sharedHTTPClient = &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
|
|
type Client struct {
|
|
*gitea.Client
|
|
hostname string
|
|
token string
|
|
}
|
|
|
|
func NewClient(hostname, token string) (*Client, error) {
|
|
if hostname == "" {
|
|
hostname = "codeberg.org"
|
|
}
|
|
|
|
client, err := gitea.NewClient("https://"+hostname, gitea.SetToken(token))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Client{
|
|
Client: client,
|
|
hostname: hostname,
|
|
token: token,
|
|
}, nil
|
|
}
|
|
|
|
func NewClientFromConfig(cfg *config.Config, hostname string, detectedHost string) (*Client, error) {
|
|
host, err := cfg.GetHost(hostname, detectedHost)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewClient(host.Hostname, host.Token)
|
|
}
|
|
|
|
func (c *Client) Hostname() string {
|
|
return c.hostname
|
|
}
|
|
|
|
// GetJSON performs a GET request to the specified path and decodes the JSON response
|
|
func (c *Client) GetJSON(path string, result any) error {
|
|
baseURL := "https://" + c.hostname
|
|
url := baseURL + path
|
|
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
// Set authentication header
|
|
if c.token != "" {
|
|
req.Header.Set("Authorization", "token "+c.token)
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
resp, err := sharedHTTPClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to perform request: %w", err)
|
|
}
|
|
defer func() {
|
|
if closeErr := resp.Body.Close(); closeErr != nil && err == nil {
|
|
err = fmt.Errorf("failed to close response body: %w", closeErr)
|
|
}
|
|
}()
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
body, readErr := io.ReadAll(resp.Body)
|
|
if readErr != nil {
|
|
return fmt.Errorf("failed to read error response body: %w", readErr)
|
|
}
|
|
return &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Body: string(body),
|
|
Message: fmt.Sprintf("API request failed with status %d: %s", resp.StatusCode, string(body)),
|
|
}
|
|
}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PostJSON performs a POST request to the specified path with JSON body
|
|
func (c *Client) PostJSON(path string, body any, result any) error {
|
|
_, err := c.DoJSON(http.MethodPost, path, body, result)
|
|
return err
|
|
}
|
|
|
|
// DoJSON performs an HTTP request with a JSON body and decodes the JSON response.
|
|
// Returns the HTTP status code and any error encountered.
|
|
func (c *Client) DoJSON(method string, path string, body any, result any) (int, error) {
|
|
baseURL := "https://" + c.hostname
|
|
url := baseURL + path
|
|
|
|
var bodyReader io.Reader
|
|
if body != nil {
|
|
bodyBytes, err := json.Marshal(body)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to marshal request body: %w", err)
|
|
}
|
|
bodyReader = bytes.NewReader(bodyBytes)
|
|
}
|
|
|
|
req, err := http.NewRequest(method, url, bodyReader)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
// Set authentication header
|
|
if c.token != "" {
|
|
req.Header.Set("Authorization", "token "+c.token)
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
if body != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
|
|
resp, err := sharedHTTPClient.Do(req)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to perform request: %w", err)
|
|
}
|
|
defer func() {
|
|
if closeErr := resp.Body.Close(); closeErr != nil && err == nil {
|
|
err = fmt.Errorf("failed to close response body: %w", closeErr)
|
|
}
|
|
}()
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
bodyBytes, readErr := io.ReadAll(resp.Body)
|
|
if readErr != nil {
|
|
return resp.StatusCode, fmt.Errorf("failed to read error response body: %w", readErr)
|
|
}
|
|
return resp.StatusCode, &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Body: string(bodyBytes),
|
|
Message: fmt.Sprintf("API request failed with status %d: %s", resp.StatusCode, string(bodyBytes)),
|
|
}
|
|
}
|
|
|
|
if result != nil && resp.StatusCode != http.StatusNoContent {
|
|
if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
|
|
return resp.StatusCode, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
}
|
|
|
|
return resp.StatusCode, nil
|
|
}
|
|
|
|
// Token returns the client's authentication token.
|
|
func (c *Client) Token() string {
|
|
return c.token
|
|
}
|
|
|
|
// DownloadFile performs an authenticated GET request and writes the response body to the given writer.
|
|
func (c *Client) DownloadFile(url string, w io.Writer) error {
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
if c.token != "" {
|
|
req.Header.Set("Authorization", "token "+c.token)
|
|
}
|
|
|
|
resp, err := sharedHTTPClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to perform request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("download failed with status %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
if _, err := io.Copy(w, resp.Body); err != nil {
|
|
return fmt.Errorf("failed to write file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetRawLog performs a GET request and returns the raw response body as string
|
|
func (c *Client) GetRawLog(url string) (string, error) {
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
// Set authentication header
|
|
if c.token != "" {
|
|
req.Header.Set("Authorization", "token "+c.token)
|
|
}
|
|
|
|
resp, err := sharedHTTPClient.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to perform request: %w", err)
|
|
}
|
|
defer func() {
|
|
if closeErr := resp.Body.Close(); closeErr != nil && err == nil {
|
|
err = fmt.Errorf("failed to close response body: %w", closeErr)
|
|
}
|
|
}()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, readErr := io.ReadAll(resp.Body)
|
|
if readErr != nil {
|
|
return "", fmt.Errorf("failed to read error response body: %w", readErr)
|
|
}
|
|
return "", &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Body: string(body),
|
|
Message: fmt.Sprintf("API request failed with status %d: %s", resp.StatusCode, string(body)),
|
|
}
|
|
}
|
|
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
return string(bodyBytes), nil
|
|
}
|