package destinations import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strings" "time" ) // Paperclip files one slow, recorded job after a tap-to-approve // (docs/hub-api.md section 6). The Hub files the job once. type Paperclip struct { URL string APIKey string CompanyID string Timeout time.Duration HTTP *http.Client } // NewPaperclip builds the Paperclip client. func NewPaperclip(url, key, companyID string, timeout time.Duration) *Paperclip { if timeout <= 0 { timeout = 5 * time.Second } return &Paperclip{ URL: strings.TrimRight(url, "/"), APIKey: key, CompanyID: companyID, Timeout: timeout, HTTP: &http.Client{Timeout: timeout}, } } // FileJob creates one Paperclip issue. It returns the issue id. func (p *Paperclip) FileJob(ctx context.Context, title, description string) (string, error) { if p == nil || p.URL == "" || p.APIKey == "" || p.CompanyID == "" { return "", fmt.Errorf("paperclip: not configured") } body, err := json.Marshal(map[string]string{ "title": title, "description": description, "priority": "medium", }) if err != nil { return "", err } ctx, cancel := context.WithTimeout(ctx, p.Timeout) defer cancel() url := fmt.Sprintf("%s/api/companies/%s/issues", p.URL, p.CompanyID) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return "", err } req.Header.Set("Authorization", "Bearer "+p.APIKey) req.Header.Set("Content-Type", "application/json") resp, err := p.HTTP.Do(req) if err != nil { return "", fmt.Errorf("paperclip: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return "", fmt.Errorf("paperclip: status %d", resp.StatusCode) } var out struct { Issue struct { ID string `json:"id"` } `json:"issue"` ID string `json:"id"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return "", err } id := out.Issue.ID if id == "" { id = out.ID } if id == "" { return "", fmt.Errorf("paperclip: reply has no issue id") } return id, nil }