Files
wrapped/pkg/gitea/client.go
T
2026-05-01 14:10:22 -06:00

216 lines
4.2 KiB
Go

package gitea
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"sync"
"time"
)
type Client struct {
BaseURL string
Token string
Client *http.Client
}
func NewClient(baseURL, token string) *Client {
baseURL = strings.TrimRight(baseURL, "/")
if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
baseURL = "https://" + baseURL
}
if strings.HasSuffix(baseURL, "/api/v1") {
baseURL = strings.TrimSuffix(baseURL, "/api/v1")
}
if strings.HasSuffix(baseURL, "/api") {
baseURL = strings.TrimSuffix(baseURL, "/api")
}
return &Client{
BaseURL: baseURL,
Token: token,
Client: &http.Client{Timeout: 60 * time.Second},
}
}
type GiteaRepo struct {
ID int64 `json:"id"`
Name string `json:"name"`
FullName string `json:"full_name"`
HTMLURL string `json:"html_url"`
Language string `json:"language"`
Topics []string `json:"topics"`
}
type GiteaCommit struct {
SHA string `json:"sha"`
RepoName string `json:"-"`
Commit struct {
Author struct {
Name string `json:"name"`
Email string `json:"email"`
Date time.Time `json:"date"`
} `json:"author"`
Message string `json:"message"`
} `json:"commit"`
}
type RepoRef struct {
Owner string
Name string
}
func (c *Client) do(method, path string) ([]byte, error) {
url := fmt.Sprintf("%s/api/v1%s", c.BaseURL, path)
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request for %s: %w", url, err)
}
req.Header.Set("Authorization", fmt.Sprintf("token %s", c.Token))
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "wrapped-cli/1.0")
resp, err := c.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("API request failed (%s %s): %w", method, url, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
errMsg := string(body)
if len(errMsg) > 500 {
errMsg = errMsg[:500]
}
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, errMsg)
}
return body, nil
}
func (c *Client) GetUserRepos() ([]GiteaRepo, error) {
var repos []GiteaRepo
page := 1
pageSize := 50
maxPages := 100
for page <= maxPages {
path := fmt.Sprintf("/user/repos?page=%d&limit=%d", page, pageSize)
body, err := c.do("GET", path)
if err != nil {
if len(repos) > 0 {
return repos, nil
}
return nil, err
}
var pageRepos []GiteaRepo
if err := json.Unmarshal(body, &pageRepos); err != nil {
return nil, err
}
if len(pageRepos) == 0 {
break
}
repos = append(repos, pageRepos...)
if len(pageRepos) < pageSize {
break
}
page++
}
return repos, nil
}
func (c *Client) GetRepoCommits(owner, repo string) ([]GiteaCommit, error) {
var commits []GiteaCommit
page := 1
pageSize := 50
maxPages := 1000
for page <= maxPages {
path := fmt.Sprintf("/repos/%s/%s/commits?page=%d&limit=%d", owner, repo, page, pageSize)
body, err := c.do("GET", path)
if err != nil {
if len(commits) > 0 {
return commits, nil
}
return nil, err
}
var pageCommits []GiteaCommit
if err := json.Unmarshal(body, &pageCommits); err != nil {
return nil, err
}
if len(pageCommits) == 0 {
break
}
commits = append(commits, pageCommits...)
if len(pageCommits) < pageSize {
break
}
page++
}
return commits, nil
}
func (c *Client) GetReposCommitsParallel(repos []RepoRef) ([]GiteaCommit, error) {
var allCommits []GiteaCommit
var mu sync.Mutex
var wg sync.WaitGroup
semaphore := make(chan struct{}, 5)
for _, repo := range repos {
wg.Add(1)
go func(owner, repoName string) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
commits, err := c.GetRepoCommits(owner, repoName)
if err != nil {
return
}
for i := range commits {
commits[i].RepoName = repoName
}
mu.Lock()
allCommits = append(allCommits, commits...)
mu.Unlock()
}(repo.Owner, repo.Name)
}
wg.Wait()
return allCommits, nil
}
func (c *Client) TestConnection() error {
_, err := c.do("GET", "/user")
if err != nil {
return err
}
return nil
}