package internal import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "path" "strconv" "time" ) const defaultBaseURL = "https://api.ns1.hosttech.eu/api" // Client a Hosttech client. type Client struct { HTTPClient *http.Client baseURL *url.URL apiKey string } // NewClient creates a new Client. func NewClient(apiKey string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ HTTPClient: &http.Client{Timeout: 10 * time.Second}, baseURL: baseURL, apiKey: apiKey, } } // GetZones Get a list of all zones. // https://api.ns1.hosttech.eu/api/documentation/#/Zones/get_api_user_v1_zones func (c Client) GetZones(query string, limit, offset int) ([]Zone, error) { endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones")) if err != nil { return nil, fmt.Errorf("parse URL: %w", err) } values := endpoint.Query() values.Set("query", query) if limit > 0 { values.Set("limit", strconv.Itoa(limit)) } if offset > 0 { values.Set("offset", strconv.Itoa(offset)) } endpoint.RawQuery = values.Encode() req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } raw, err := c.do(req) if err != nil { return nil, err } var r []Zone err = json.Unmarshal(raw, &r) if err != nil { return nil, fmt.Errorf("unmarshal response data: %s: %w", string(raw), err) } return r, nil } // GetZone Get a single zone. // https://api.ns1.hosttech.eu/api/documentation/#/Zones/get_api_user_v1_zones__zoneId_ func (c Client) GetZone(zoneID string) (*Zone, error) { endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID)) if err != nil { return nil, fmt.Errorf("parse URL: %w", err) } req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } raw, err := c.do(req) if err != nil { return nil, err } var r Zone err = json.Unmarshal(raw, &r) if err != nil { return nil, fmt.Errorf("unmarshal response data: %s: %w", string(raw), err) } return &r, nil } // GetRecords Returns a list of all records for the given zone. // https://api.ns1.hosttech.eu/api/documentation/#/Records/get_api_user_v1_zones__zoneId__records func (c Client) GetRecords(zoneID, recordType string) ([]Record, error) { endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID, "records")) if err != nil { return nil, fmt.Errorf("parse URL: %w", err) } values := endpoint.Query() if recordType != "" { values.Set("type", recordType) } endpoint.RawQuery = values.Encode() req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } raw, err := c.do(req) if err != nil { return nil, err } var r []Record err = json.Unmarshal(raw, &r) if err != nil { return nil, fmt.Errorf("unmarshal response data: %s: %w", string(raw), err) } return r, nil } // AddRecord Adds a new record to the zone and returns the newly created record. // https://api.ns1.hosttech.eu/api/documentation/#/Records/post_api_user_v1_zones__zoneId__records func (c Client) AddRecord(zoneID string, record Record) (*Record, error) { endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID, "records")) if err != nil { return nil, fmt.Errorf("parse URL: %w", err) } body, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("marshal request data: %w", err) } req, err := http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("create request: %w", err) } raw, err := c.do(req) if err != nil { return nil, err } var r Record err = json.Unmarshal(raw, &r) if err != nil { return nil, fmt.Errorf("unmarshal response data: %s: %w", string(raw), err) } return &r, nil } // DeleteRecord Deletes a single record for the given id. // https://api.ns1.hosttech.eu/api/documentation/#/Records/delete_api_user_v1_zones__zoneId__records__recordId_ func (c Client) DeleteRecord(zoneID, recordID string) error { endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "user", "v1", "zones", zoneID, "records", recordID)) if err != nil { return fmt.Errorf("parse URL: %w", err) } req, err := http.NewRequest(http.MethodDelete, endpoint.String(), nil) if err != nil { return fmt.Errorf("create request: %w", err) } _, err = c.do(req) return err } func (c Client) do(req *http.Request) (json.RawMessage, error) { req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) resp, errD := c.HTTPClient.Do(req) if errD != nil { return nil, fmt.Errorf("send request: %w", errD) } defer func() { _ = resp.Body.Close() }() switch resp.StatusCode { case http.StatusOK, http.StatusCreated: all, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } var r apiResponse err = json.Unmarshal(all, &r) if err != nil { return nil, fmt.Errorf("unmarshal response: %w", err) } return r.Data, nil case http.StatusNoContent: return nil, nil default: data, _ := io.ReadAll(resp.Body) e := APIError{StatusCode: resp.StatusCode} err := json.Unmarshal(data, &e) if err != nil { e.Message = string(data) } return nil, e } }