gogitlabber/cmd/gogitlabber/gitea.go

242 lines
5.9 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/scornet256/go-logger"
)
// giteaClient struct
type GiteaClient struct {
httpClient *http.Client
baseURL string
token string
}
// gitea repo information
type GiteaRepository struct {
Name string `json:"name"`
FullName string `json:"full_name"`
}
// gitea api options
type GiteaAPIOptions struct {
Visibility string
IncludeArchived string
Sort string
Limit int
Page int
}
// gitea api clien
func NewGiteaClient(baseURL, token string) *GiteaClient {
return &GiteaClient{
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
baseURL: baseURL,
token: token,
}
}
// fetch gitea repos
func FetchRepositoriesGitea() ([]Repository, error) {
client := NewGiteaClient(config.GitHost, config.GitToken)
options := GiteaAPIOptions{
Visibility: "all",
IncludeArchived: config.IncludeArchived,
Sort: "alpha",
Limit: 100,
Page: 1,
}
repositories, err := client.fetchAllRepositories(context.Background(), options)
if err != nil {
return nil, fmt.Errorf("fetching repositories: %w", err)
}
if len(repositories) == 0 {
return repositories, fmt.Errorf("no repositories found")
}
// update progress bar
if err := updateProgressBar(len(repositories)); err != nil {
logger.Print("WARNING: failed to update progress bar: "+err.Error(), nil)
}
logger.Print(fmt.Sprintf("Successfully fetched %d repositories", len(repositories)), nil)
return repositories, nil
}
// fetch all repos with pagination
func (c *GiteaClient) fetchAllRepositories(ctx context.Context, options GiteaAPIOptions) ([]Repository, error) {
var allRepositories []Repository
for {
giteaRepos, hasMore, err := c.fetchRepositoryPage(ctx, options)
if err != nil {
return nil, fmt.Errorf("fetching page %d: %w", options.Page, err)
}
// convert gitea repositories to repo type
repositories := convertGiteaRepositories(giteaRepos)
allRepositories = append(allRepositories, repositories...)
if !hasMore {
break
}
options.Page++
}
return allRepositories, nil
}
// fetch single page of repo
func (c *GiteaClient) fetchRepositoryPage(ctx context.Context, options GiteaAPIOptions) ([]GiteaRepository, bool, error) {
apiURL, err := c.buildAPIURL(options)
if err != nil {
return nil, false, fmt.Errorf("building API URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
if err != nil {
return nil, false, fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("token %s", c.token))
req.Header.Set("Accept", "application/json")
logger.Print("Making API request to: "+apiURL, nil)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, false, fmt.Errorf("making request: %w", err)
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
logger.Print("WARNING: failed to close response body: "+closeErr.Error(), nil)
}
}()
if resp.StatusCode != http.StatusOK {
return nil, false, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, resp.Status)
}
var giteaRepos []GiteaRepository
if err := json.NewDecoder(resp.Body).Decode(&giteaRepos); err != nil {
return nil, false, fmt.Errorf("decoding JSON response: %w", err)
}
// check for more pages
hasMore := len(giteaRepos) == options.Limit
return giteaRepos, hasMore, nil
}
// build final api url
func (c *GiteaClient) buildAPIURL(options GiteaAPIOptions) (string, error) {
baseURL := fmt.Sprintf("https://%s/api/v1/user/repos", c.baseURL)
u, err := url.Parse(baseURL)
if err != nil {
return "", fmt.Errorf("parsing base URL: %w", err)
}
query := u.Query()
query.Set("visibility", options.Visibility)
query.Set("sort", options.Sort)
query.Set("limit", strconv.Itoa(options.Limit))
query.Set("page", strconv.Itoa(options.Page))
// handle archived
switch options.IncludeArchived {
case "excluded":
query.Set("archived", "false")
case "only":
query.Set("archived", "true")
}
u.RawQuery = query.Encode()
return u.String(), nil
}
// convert gitea repos to repo type
func convertGiteaRepositories(giteaRepos []GiteaRepository) []Repository {
repositories := make([]Repository, len(giteaRepos))
for i, giteaRepo := range giteaRepos {
repositories[i] = Repository{
Name: giteaRepo.Name,
PathWithNamespace: giteaRepo.FullName,
}
}
return repositories
}
// update progressbar
func updateProgressBar(repoCount int) error {
if config.Debug {
return nil // Skip progress bar in debug mode
}
logger.Print("Resetting progress bar", nil)
if err := bar.Set(0); err != nil {
return fmt.Errorf("resetting progress bar: %w", err)
}
logger.Print("Setting progress bar maximum", nil)
bar.ChangeMax(repoCount)
return nil
}
// connection validation
func (c *GiteaClient) ValidateConnection(ctx context.Context) error {
apiURL := fmt.Sprintf("https://%s/api/v1/user", c.baseURL)
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
if err != nil {
return fmt.Errorf("creating validation request: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("token %s", c.token))
req.Header.Set("Accept", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("making validation request: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Print("failed to close response body: %v", err)
}
}()
if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf("invalid or expired token")
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("API validation failed with status %d", resp.StatusCode)
}
return nil
}
// simply count git repos only
func (c *GiteaClient) GetRepositoryCount(ctx context.Context, options GiteaAPIOptions) (int, error) {
repos, err := c.fetchAllRepositories(ctx, options)
if err != nil {
return 0, fmt.Errorf("counting repositories: %w", err)
}
return len(repos), nil
}