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 "time"
12
13 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
14 )
15
16 const defaultBaseURL = "https://mijn.host/api/v2/"
17
18 const authorizationHeader = "API-Key"
19
20 // Client a mijn.host DNS API client.
21 type Client struct {
22 apiKey string
23
24 baseURL *url.URL
25 HTTPClient *http.Client
26 }
27
28 // NewClient creates a new Client.
29 func NewClient(apiKey string) *Client {
30 baseURL, _ := url.Parse(defaultBaseURL)
31
32 return &Client{
33 apiKey: apiKey,
34 baseURL: baseURL,
35 HTTPClient: &http.Client{Timeout: 10 * time.Second},
36 }
37 }
38
39 // ListDomains Retrieve all domains from an account.
40 // https://mijn.host/api/doc/api-3563872
41 func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) {
42 endpoint := c.baseURL.JoinPath("domains")
43
44 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
45 if err != nil {
46 return nil, fmt.Errorf("create request: %w", err)
47 }
48
49 var results Response[DomainData]
50
51 err = c.do(req, &results)
52 if err != nil {
53 return nil, err
54 }
55
56 return results.Data.Domains, nil
57 }
58
59 // GetRecords Retrieve DNS records of specific domain.
60 // https://mijn.host/api/doc/api-3563906
61 func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error) {
62 endpoint := c.baseURL.JoinPath("domains", domain, "dns")
63
64 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
65 if err != nil {
66 return nil, fmt.Errorf("create request: %w", err)
67 }
68
69 var results Response[RecordData]
70
71 err = c.do(req, &results)
72 if err != nil {
73 return nil, err
74 }
75
76 return results.Data.Records, nil
77 }
78
79 // UpdateRecords Update DNS records of specific domain.
80 // https://mijn.host/api/doc/api-3563907
81 func (c *Client) UpdateRecords(ctx context.Context, domain string, records []Record) error {
82 endpoint := c.baseURL.JoinPath("domains", domain, "dns")
83
84 req, err := newJSONRequest(ctx, http.MethodPut, endpoint, RecordData{Records: records})
85 if err != nil {
86 return fmt.Errorf("create request: %w", err)
87 }
88
89 err = c.do(req, nil)
90 if err != nil {
91 return err
92 }
93
94 return nil
95 }
96
97 func (c *Client) do(req *http.Request, result any) error {
98 req.Header.Set(authorizationHeader, c.apiKey)
99
100 resp, err := c.HTTPClient.Do(req)
101 if err != nil {
102 return errutils.NewHTTPDoError(req, err)
103 }
104
105 defer func() { _ = resp.Body.Close() }()
106
107 if resp.StatusCode != http.StatusOK {
108 return parseError(req, resp)
109 }
110
111 if result == nil {
112 return nil
113 }
114
115 raw, err := io.ReadAll(resp.Body)
116 if err != nil {
117 return errutils.NewReadResponseError(req, resp.StatusCode, err)
118 }
119
120 err = json.Unmarshal(raw, result)
121 if err != nil {
122 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
123 }
124
125 return nil
126 }
127
128 func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
129 buf := new(bytes.Buffer)
130
131 if payload != nil {
132 err := json.NewEncoder(buf).Encode(payload)
133 if err != nil {
134 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
135 }
136 }
137
138 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
139 if err != nil {
140 return nil, fmt.Errorf("unable to create request: %w", err)
141 }
142
143 req.Header.Set("Accept", "application/json")
144
145 if payload != nil {
146 req.Header.Set("Content-Type", "application/json")
147 }
148
149 return req, nil
150 }
151
152 func parseError(req *http.Request, resp *http.Response) error {
153 raw, _ := io.ReadAll(resp.Body)
154
155 var errAPI APIError
156
157 err := json.Unmarshal(raw, &errAPI)
158 if err != nil {
159 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
160 }
161
162 return &errAPI
163 }
164