Initial implementation of Gitea Wrapped TUI
- Gitea API client with repository and commit fetching - Interactive credential input screen with masked token input - Statistics analyzer for commits, languages, and activity patterns - Multi-page report screen with ASCII charts and visualizations - Integration of all components in main app coordinator - Comprehensive README with usage instructions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// do makes an authenticated HTTP request to Gitea API
|
||||
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", "gitea-wrapped/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 // Safety limit to prevent infinite loops
|
||||
|
||||
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 // Safety limit to prevent infinite loops
|
||||
|
||||
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) TestConnection() error {
|
||||
_, err := c.do("GET", "/user")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user