package gitea import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" ) type Client struct { baseURL string token string botUser string http *http.Client } func New(baseURL, token, botUser string) *Client { return &Client{ baseURL: baseURL, token: token, botUser: botUser, http: &http.Client{}, } } type Repo struct { ID int64 `json:"id"` Name string `json:"name"` FullName string `json:"full_name"` Description string `json:"description"` CloneURL string `json:"clone_url"` HTMLURL string `json:"html_url"` Empty bool `json:"empty"` } type Branch struct { Name string `json:"name"` } func (c *Client) CreateRepo(ctx context.Context, name, description string) (*Repo, error) { body, _ := json.Marshal(map[string]any{ "name": name, "description": description, "auto_init": true, "default_branch": "main", "private": false, }) req, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/api/v1/user/repos", bytes.NewReader(body)) if err != nil { return nil, err } c.setHeaders(req) resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("create repo: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("create repo: status %d: %s", resp.StatusCode, b) } var repo Repo if err := json.NewDecoder(resp.Body).Decode(&repo); err != nil { return nil, fmt.Errorf("decode repo: %w", err) } return &repo, nil } func (c *Client) ListRepos(ctx context.Context) ([]Repo, error) { req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/api/v1/user/repos?limit=50", nil) if err != nil { return nil, err } c.setHeaders(req) resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("list repos: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("list repos: status %d: %s", resp.StatusCode, b) } var repos []Repo if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil { return nil, fmt.Errorf("decode repos: %w", err) } return repos, nil } func (c *Client) ListBranches(ctx context.Context, owner, repo string) ([]Branch, error) { url := fmt.Sprintf("%s/api/v1/repos/%s/%s/branches", c.baseURL, owner, repo) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err } c.setHeaders(req) resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("list branches: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("list branches: status %d: %s", resp.StatusCode, b) } var branches []Branch if err := json.NewDecoder(resp.Body).Decode(&branches); err != nil { return nil, fmt.Errorf("decode branches: %w", err) } return branches, nil } // AuthCloneURL returns a clone URL with embedded bot credentials. func (c *Client) AuthCloneURL(owner, repo string) string { // Strip protocol from base URL base := c.baseURL if len(base) > 8 && base[:8] == "https://" { base = base[8:] } else if len(base) > 7 && base[:7] == "http://" { base = base[7:] } return fmt.Sprintf("https://%s:%s@%s/%s/%s.git", c.botUser, c.token, base, owner, repo) } func (c *Client) setHeaders(req *http.Request) { req.Header.Set("Authorization", "token "+c.token) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") }