package gitea import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" "github.com/chorus-services/whoosh/internal/config" "github.com/rs/zerolog/log" ) type Client struct { baseURL string token string httpClient *http.Client } type Issue struct { ID int `json:"id"` Number int `json:"number"` Title string `json:"title"` Body string `json:"body"` State string `json:"state"` URL string `json:"html_url"` HTMLURL string `json:"html_url"` Labels []struct { Name string `json:"name"` Color string `json:"color"` } `json:"labels"` Repository struct { Name string `json:"name"` FullName string `json:"full_name"` } `json:"repository"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type Repository struct { ID int `json:"id"` Name string `json:"name"` FullName string `json:"full_name"` HTMLURL string `json:"html_url"` CloneURL string `json:"clone_url"` SSHURL string `json:"ssh_url"` } type WebhookPayload struct { Action string `json:"action"` Issue *Issue `json:"issue,omitempty"` Repository Repository `json:"repository"` Sender struct { Login string `json:"login"` } `json:"sender"` } type CreateIssueRequest struct { Title string `json:"title"` Body string `json:"body"` Labels []string `json:"labels,omitempty"` Assignee string `json:"assignee,omitempty"` } func NewClient(cfg config.GITEAConfig) *Client { return &Client{ baseURL: cfg.BaseURL, token: cfg.Token, httpClient: &http.Client{ Timeout: 30 * time.Second, }, } } func (c *Client) makeRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) { url := c.baseURL + "/api/v1" + path var reqBody *bytes.Buffer if body != nil { jsonData, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("failed to marshal request body: %w", err) } reqBody = bytes.NewBuffer(jsonData) } var req *http.Request var err error if reqBody != nil { req, err = http.NewRequestWithContext(ctx, method, url, reqBody) } else { req, err = http.NewRequestWithContext(ctx, method, url, nil) } if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Authorization", "token "+c.token) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } return resp, nil } func (c *Client) CreateIssue(ctx context.Context, owner, repo string, issue CreateIssueRequest) (*Issue, error) { path := fmt.Sprintf("/repos/%s/%s/issues", owner, repo) resp, err := c.makeRequest(ctx, "POST", path, issue) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { return nil, fmt.Errorf("failed to create issue: status %d", resp.StatusCode) } var createdIssue Issue if err := json.NewDecoder(resp.Body).Decode(&createdIssue); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } log.Info(). Str("repo", fmt.Sprintf("%s/%s", owner, repo)). Int("issue_number", createdIssue.Number). Str("title", createdIssue.Title). Msg("Created GITEA issue") return &createdIssue, nil } func (c *Client) GetIssue(ctx context.Context, owner, repo string, issueNumber int) (*Issue, error) { path := fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, issueNumber) resp, err := c.makeRequest(ctx, "GET", path, nil) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get issue: status %d", resp.StatusCode) } var issue Issue if err := json.NewDecoder(resp.Body).Decode(&issue); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return &issue, nil } func (c *Client) ListRepositories(ctx context.Context) ([]Repository, error) { path := "/user/repos" resp, err := c.makeRequest(ctx, "GET", path, nil) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to list repositories: status %d", resp.StatusCode) } var repos []Repository if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return repos, nil } func (c *Client) GetRepository(ctx context.Context, owner, repo string) (*Repository, error) { path := fmt.Sprintf("/repos/%s/%s", owner, repo) resp, err := c.makeRequest(ctx, "GET", path, nil) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get repository: status %d", resp.StatusCode) } var repository Repository if err := json.NewDecoder(resp.Body).Decode(&repository); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return &repository, nil }