client.go raw

   1  package internal
   2  
   3  import (
   4  	"bytes"
   5  	"context"
   6  	"encoding/json"
   7  	"fmt"
   8  	"io"
   9  	"net/http"
  10  	"net/url"
  11  	"strconv"
  12  	"time"
  13  
  14  	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
  15  )
  16  
  17  const defaultBaseURL = "https://www.lima-city.de/usercp"
  18  
  19  type Client struct {
  20  	apiKey     string
  21  	baseURL    *url.URL
  22  	HTTPClient *http.Client
  23  }
  24  
  25  func NewClient(apiKey string) *Client {
  26  	baseURL, _ := url.Parse(defaultBaseURL)
  27  
  28  	return &Client{
  29  		apiKey:     apiKey,
  30  		baseURL:    baseURL,
  31  		HTTPClient: &http.Client{Timeout: 10 * time.Second},
  32  	}
  33  }
  34  
  35  func (c *Client) GetDomains(ctx context.Context) ([]Domain, error) {
  36  	endpoint := c.baseURL.JoinPath("domains.json")
  37  
  38  	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
  39  	if err != nil {
  40  		return nil, err
  41  	}
  42  
  43  	var results DomainsResponse
  44  
  45  	err = c.do(req, &results)
  46  	if err != nil {
  47  		return nil, err
  48  	}
  49  
  50  	return results.Data, nil
  51  }
  52  
  53  func (c *Client) GetRecords(ctx context.Context, domainID int) ([]Record, error) {
  54  	endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records.json")
  55  
  56  	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
  57  	if err != nil {
  58  		return nil, err
  59  	}
  60  
  61  	var results RecordsResponse
  62  
  63  	err = c.do(req, &results)
  64  	if err != nil {
  65  		return nil, err
  66  	}
  67  
  68  	return results.Data, nil
  69  }
  70  
  71  func (c *Client) AddRecord(ctx context.Context, domainID int, record Record) error {
  72  	endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records.json")
  73  
  74  	req, err := newJSONRequest(ctx, http.MethodPost, endpoint, NameserverRecordPayload{Data: record})
  75  	if err != nil {
  76  		return err
  77  	}
  78  
  79  	var results APIResponse
  80  
  81  	err = c.do(req, &results)
  82  	if err != nil {
  83  		return err
  84  	}
  85  
  86  	return nil
  87  }
  88  
  89  func (c *Client) UpdateRecord(ctx context.Context, domainID, recordID int, record Record) error {
  90  	endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records", strconv.Itoa(recordID))
  91  
  92  	req, err := newJSONRequest(ctx, http.MethodPut, endpoint, NameserverRecordPayload{Data: record})
  93  	if err != nil {
  94  		return err
  95  	}
  96  
  97  	var results APIResponse
  98  
  99  	err = c.do(req, &results)
 100  	if err != nil {
 101  		return err
 102  	}
 103  
 104  	return nil
 105  }
 106  
 107  func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error {
 108  	// /domains/{domainId}/records/{recordId} DELETE
 109  	endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records", strconv.Itoa(recordID))
 110  
 111  	req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
 112  	if err != nil {
 113  		return err
 114  	}
 115  
 116  	var results APIResponse
 117  
 118  	err = c.do(req, &results)
 119  	if err != nil {
 120  		return err
 121  	}
 122  
 123  	return nil
 124  }
 125  
 126  func (c *Client) do(req *http.Request, result any) error {
 127  	req.SetBasicAuth("api", c.apiKey)
 128  
 129  	resp, err := c.HTTPClient.Do(req)
 130  	if err != nil {
 131  		return errutils.NewHTTPDoError(req, err)
 132  	}
 133  
 134  	defer func() { _ = resp.Body.Close() }()
 135  
 136  	if resp.StatusCode/100 != 2 {
 137  		return parseError(req, resp)
 138  	}
 139  
 140  	if result == nil {
 141  		return nil
 142  	}
 143  
 144  	raw, err := io.ReadAll(resp.Body)
 145  	if err != nil {
 146  		return errutils.NewReadResponseError(req, resp.StatusCode, err)
 147  	}
 148  
 149  	err = json.Unmarshal(raw, result)
 150  	if err != nil {
 151  		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
 152  	}
 153  
 154  	return nil
 155  }
 156  
 157  func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
 158  	buf := new(bytes.Buffer)
 159  
 160  	if payload != nil {
 161  		err := json.NewEncoder(buf).Encode(payload)
 162  		if err != nil {
 163  			return nil, fmt.Errorf("failed to create request JSON body: %w", err)
 164  		}
 165  	}
 166  
 167  	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
 168  	if err != nil {
 169  		return nil, fmt.Errorf("unable to create request: %w", err)
 170  	}
 171  
 172  	req.Header.Set("Accept", "application/json")
 173  
 174  	if payload != nil {
 175  		req.Header.Set("Content-Type", "application/json")
 176  	}
 177  
 178  	return req, nil
 179  }
 180  
 181  func parseError(req *http.Request, resp *http.Response) error {
 182  	raw, _ := io.ReadAll(resp.Body)
 183  
 184  	var errAPI APIResponse
 185  
 186  	err := json.Unmarshal(raw, &errAPI)
 187  	if err != nil {
 188  		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
 189  	}
 190  
 191  	return fmt.Errorf("[status code: %d] %w", resp.StatusCode, &errAPI)
 192  }
 193