196 lines
4.5 KiB
Go
196 lines
4.5 KiB
Go
package forgejo
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
)
|
|
|
|
type Client struct {
|
|
client *resty.Client
|
|
token string
|
|
baseURL string
|
|
}
|
|
|
|
type Secret struct {
|
|
Name string `json:"name"`
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
type MigrateRequest struct {
|
|
CloneAddr string `json:"clone_addr"`
|
|
AuthUsername string `json:"auth_username"`
|
|
AuthPassword string `json:"auth_password"`
|
|
UID int `json:"uid"`
|
|
RepoName string `json:"repo_name"`
|
|
RepoOwner string `json:"repo_owner"`
|
|
Mirror bool `json:"mirror"`
|
|
LFS bool `json:"lfs"`
|
|
Interval string `json:"interval"`
|
|
Description string `json:"description,omitempty"`
|
|
Private bool `json:"private"`
|
|
}
|
|
|
|
type Repository struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
FullName string `json:"full_name"`
|
|
HTMLURL string `json:"html_url"`
|
|
CloneURL string `json:"clone_url"`
|
|
Mirror bool `json:"mirror"`
|
|
}
|
|
|
|
type User struct {
|
|
ID int64 `json:"id"`
|
|
UserName string `json:"login"`
|
|
}
|
|
|
|
type APIError struct {
|
|
Message string `json:"message"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
func NewClient(baseURL, token string) *Client {
|
|
c := resty.New()
|
|
c.SetTimeout(60 * time.Second) // Longer timeout for migration
|
|
c.SetHeader("Authorization", "token "+token)
|
|
c.SetHeader("Content-Type", "application/json")
|
|
c.SetBaseURL(baseURL + "/api/v1")
|
|
|
|
return &Client{
|
|
client: c,
|
|
token: token,
|
|
baseURL: baseURL,
|
|
}
|
|
}
|
|
|
|
func (c *Client) GetUser(username string) (*User, error) {
|
|
var user User
|
|
resp, err := c.client.R().
|
|
SetResult(&user).
|
|
Get("/users/" + username)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get user: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode() != http.StatusOK {
|
|
return nil, fmt.Errorf("failed to get user: status %d - %s", resp.StatusCode(), resp.String())
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
func (c *Client) RepositoryExists(owner, name string) (bool, error) {
|
|
resp, err := c.client.R().
|
|
Head(fmt.Sprintf("/repos/%s/%s", owner, name))
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if resp.StatusCode() == http.StatusOK {
|
|
return true, nil
|
|
} else if resp.StatusCode() == http.StatusNotFound {
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode())
|
|
}
|
|
|
|
func (c *Client) CreateMirror(req MigrateRequest) (*Repository, error) {
|
|
var repo Repository
|
|
var apiErr APIError
|
|
|
|
resp, err := c.client.R().
|
|
SetBody(req).
|
|
SetResult(&repo).
|
|
SetError(&apiErr).
|
|
Post("/repos/migrate")
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("API request failed: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode() != http.StatusCreated && resp.StatusCode() != http.StatusOK {
|
|
if apiErr.Message != "" {
|
|
return nil, fmt.Errorf("Forgejo API error: %s (status %d)", apiErr.Message, resp.StatusCode())
|
|
}
|
|
return nil, fmt.Errorf("Forgejo API returned status %d: %s", resp.StatusCode(), resp.String())
|
|
}
|
|
|
|
return &repo, nil
|
|
}
|
|
|
|
func (c *Client) DeleteRepository(owner, name string) error {
|
|
resp, err := c.client.R().
|
|
Delete(fmt.Sprintf("/repos/%s/%s", owner, name))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode() != http.StatusNoContent && resp.StatusCode() != http.StatusOK {
|
|
return fmt.Errorf("failed to delete repository: status %d", resp.StatusCode())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateSecret creates a repository secret for Actions
|
|
func (c *Client) CreateSecret(owner, repo, name, data string) error {
|
|
// Forgejo/Gitea API for secrets
|
|
url := fmt.Sprintf("/repos/%s/%s/actions/secrets/%s", owner, repo, name)
|
|
|
|
body := map[string]string{
|
|
"data": data,
|
|
}
|
|
|
|
resp, err := c.client.R().
|
|
SetBody(body).
|
|
Put(url)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create secret: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode() != 201 && resp.StatusCode() != 204 {
|
|
return fmt.Errorf("failed to create secret, status: %d", resp.StatusCode())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateFile creates a file in the repository (for workflow files)
|
|
func (c *Client) CreateFile(owner, repo, path, content, message string) error {
|
|
// Content needs to be base64 encoded
|
|
encoded := base64.StdEncoding.EncodeToString([]byte(content))
|
|
|
|
body := map[string]interface{}{
|
|
"content": encoded,
|
|
"message": message,
|
|
}
|
|
|
|
url := fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, path)
|
|
|
|
resp, err := c.client.R().
|
|
SetBody(body).
|
|
Post(url)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 201 = created, 422 = already exists (should handle update in production)
|
|
if resp.StatusCode() != 201 {
|
|
if resp.StatusCode() == 422 {
|
|
return fmt.Errorf("file %s already exists", path)
|
|
}
|
|
return fmt.Errorf("failed to create file, status %d: %s", resp.StatusCode(), resp.String())
|
|
}
|
|
|
|
return nil
|
|
}
|