diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bb1e1..f551e0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [v0.6.x] - 2025-08-15 +### Added +- Cloud stack environment delete variables +- SRS list domains +- SRS list contacts +- +### Fixed +- Added handling of null json types to maybe bool, which will evaluate to false, since they are falsy in js +- Updated dependencies +- Fixes in DNS handling - split out Get and Search endpoints - lament the pagination. + + ## [v0.6.0] - 2025-06-17 ### Fixed @@ -9,6 +21,7 @@ All notable changes to this project will be documented in this file. The format - Update pr make file to use golangci-lint GitHub action. - Split url helpers and type helpers in to their own packages. + ## [v0.5.0] - 2025-06-12 ### Added - Added support for all endpoints under `/server/firewall/`. diff --git a/pkg/api/cloud/db/response.go b/pkg/api/cloud/db/response.go index 341d51d..1bc04d1 100644 --- a/pkg/api/cloud/db/response.go +++ b/pkg/api/cloud/db/response.go @@ -26,7 +26,7 @@ type ( Database models.Database `json:"return"` models.APIResponse } - // ListResponse is the returned response from the List endpoint. + // ListResponse is the returned response from the ListDomains endpoint. ListResponse struct { Return struct { Databases []models.Database `json:"data"` diff --git a/pkg/api/cloud/db/user/response.go b/pkg/api/cloud/db/user/response.go index baca1e4..6428bfd 100644 --- a/pkg/api/cloud/db/user/response.go +++ b/pkg/api/cloud/db/user/response.go @@ -5,7 +5,7 @@ import ( ) type ( - // ListResponse is the returned response from the List endpoint. + // ListResponse is the returned response from the ListDomains endpoint. ListResponse struct { Return struct { Users []models.DatabaseUser `json:"data"` diff --git a/pkg/api/cloud/stack/environment/delete.go b/pkg/api/cloud/stack/environment/delete.go new file mode 100644 index 0000000..438828c --- /dev/null +++ b/pkg/api/cloud/stack/environment/delete.go @@ -0,0 +1,45 @@ +package environment + +import ( + "context" + "fmt" + "net/url" + + "github.com/sitehostnz/gosh/pkg/net" +) + +// Update applies updates to the stacks environment. +func (s *Client) Delete(ctx context.Context, request DeleteRequest) (response DeleteResponse, err error) { + uri := "cloud/stack/environment/delete.json" + keys := []string{ + "client_id", + "server", + "project", + "service", + } + + values := url.Values{} + values.Add("client_id", s.client.ClientID) + values.Add("server", request.ServerName) + values.Add("project", request.Project) + values.Add("service", request.Service) + + args := make([]string, len(request.EnvironmentVariables)) + i := 0 + for x, v := range request.EnvironmentVariables { + args[i] = fmt.Sprintf("variables[%d][name]", x) + values.Add(args[i], v.Name) + i++ + } + + req, err := s.client.NewRequest("POST", uri, net.Encode(values, append(keys, args...))) + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/stack/environment/request.go b/pkg/api/cloud/stack/environment/request.go index d9e6455..c3c8cac 100644 --- a/pkg/api/cloud/stack/environment/request.go +++ b/pkg/api/cloud/stack/environment/request.go @@ -17,4 +17,12 @@ type ( Service string `json:"service"` EnvironmentVariables []models.EnvironmentVariable } + + // DeleteRequest is a request to remove variables from a stack environment. + DeleteRequest struct { + ServerName string `json:"server"` + Project string `json:"project"` + Service string `json:"service"` + EnvironmentVariables []models.EnvironmentVariable + } ) diff --git a/pkg/api/cloud/stack/environment/response.go b/pkg/api/cloud/stack/environment/response.go index 9dc2845..80f7c82 100644 --- a/pkg/api/cloud/stack/environment/response.go +++ b/pkg/api/cloud/stack/environment/response.go @@ -3,17 +3,26 @@ package environment import "github.com/sitehostnz/gosh/pkg/models" type ( - // GetResponse is the response that returns a stacks environment variables. + // GetResponse is the response contains the results of a call to get an environment. + // It contains the environment details and variables. GetResponse struct { EnvironmentVariables []models.EnvironmentVariable `json:"return"` models.APIResponse } - // UpdateResponse is the result of updating an environment on a stack. + // UpdateResponse is the result of adding or updating variables in a stack environment. UpdateResponse struct { Return struct { models.Job `json:"job"` } `json:"return"` models.APIResponse } + + // DeleteResponse is the result of deleting variables from a stack environment. + DeleteResponse struct { + Return struct { + models.Job `json:"job"` + } `json:"return"` + models.APIResponse + } ) diff --git a/pkg/api/cloud/stack/environment/update.go b/pkg/api/cloud/stack/environment/update.go index 390ec6e..a95960f 100644 --- a/pkg/api/cloud/stack/environment/update.go +++ b/pkg/api/cloud/stack/environment/update.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/url" + "strings" "github.com/sitehostnz/gosh/pkg/net" ) @@ -31,11 +32,10 @@ func (s *Client) Update(ctx context.Context, request UpdateRequest) (response Up values.Add(args[i], v.Name) i++ - if v.Content != "" { - args[i] = fmt.Sprintf("variables[%d][content]", x) - values.Add(args[i], v.Content) - i++ - } + // we're allowed empty strings here now, as properties need to be explicitly removed + args[i] = fmt.Sprintf("variables[%d][content]", x) + values.Add(args[i], strings.TrimSpace(v.Content)) + i++ } req, err := s.client.NewRequest("POST", uri, net.Encode(values, append(keys, args...))) diff --git a/pkg/api/dns/zoneget.go b/pkg/api/dns/zoneget.go index cd9ee32..6ec230d 100644 --- a/pkg/api/dns/zoneget.go +++ b/pkg/api/dns/zoneget.go @@ -2,16 +2,35 @@ package dns import ( "context" - "net/url" - "github.com/sitehostnz/gosh/pkg/net" + "net/url" ) -// GetZone searches for a domain in the DNS made easy service -// by sending a request with a query containing the domain name. -// If the response contains any matching domain, it returns it as part of the GetZoneResponse. -// If the response is empty, a control to handle this case is missing and should be added. func (s *Client) GetZone(ctx context.Context, request GetZoneRequest) (response GetZoneResponse, err error) { + + resp, err := s.SearchZone(ctx, SearchZoneRequest{ + Query: request.DomainName, + Limit: 1, + }) + + if err != nil { + return response, err + } + + response.APIResponse = resp.APIResponse + + // iterate over the domains to find the one we are looking for. + // Since we've set a limit ths should be one... + if len(resp.Return) > 0 { + response.Return = resp.Return[0] + } + return response, nil +} + +// SearchZone searches for a domain in the DNS service +// by sending a request with a query containing the domain name. +// Matching domains are included in the SearchZoneResponse. +func (s *Client) SearchZone(ctx context.Context, request SearchZoneRequest) (response SearchZoneResponse, err error) { u := "dns/search_domains.json" keys := []string{ @@ -21,18 +40,26 @@ func (s *Client) GetZone(ctx context.Context, request GetZoneRequest) (response values := url.Values{} values.Add("client_id", s.client.ClientID) - values.Add("query[domain]", request.DomainName) + values.Add("query[domain]", request.Query) + + // would like to do this, but it's kinda not... working + //if request.Offset > 0 { + // keys = append(keys, "offset[offset]") + // values.Add("offset[offset]", strconv.Itoa(request.Offset)) + //} + // + //if request.Limit > 0 { + // keys = append(keys, "offset[limit]") + // values.Add("offset[limit]", strconv.Itoa(request.Limit)) + //} req, err := s.client.NewRequest("POST", u, net.Encode(values, keys)) if err != nil { return response, err } - if err := s.client.Do(ctx, req, &response); err != nil { return response, err } - // TODO add control for empty response - return response, nil } diff --git a/pkg/api/dns/zonerequest.go b/pkg/api/dns/zonerequest.go index dc74c21..61397b7 100644 --- a/pkg/api/dns/zonerequest.go +++ b/pkg/api/dns/zonerequest.go @@ -6,6 +6,12 @@ type ( DomainName string `json:"name"` } + SearchZoneRequest struct { + Query string `json:"query"` + Offset int `json:"offset"` + Limit int `json:"limit"` + } + // CreateZoneRequest represents a request to create a DNSZone (domain). CreateZoneRequest struct { DomainName string `json:"name"` diff --git a/pkg/api/dns/zoneresponse.go b/pkg/api/dns/zoneresponse.go index 65c5c04..5ad3c23 100644 --- a/pkg/api/dns/zoneresponse.go +++ b/pkg/api/dns/zoneresponse.go @@ -28,11 +28,18 @@ type ( models.APIResponse } - // GetZoneResponse represents a request to get a DNSZone (domain). - GetZoneResponse struct { + // SearchZoneResponse represents a request to search for a Domain (domain). + SearchZoneResponse struct { Return []struct { Name string `json:"name"` } `json:"return"` models.APIResponse } + + GetZoneResponse struct { + Return struct { + Name string `json:"name"` + } `json:"return"` + models.APIResponse + } ) diff --git a/pkg/api/srs/doc.go b/pkg/api/srs/doc.go new file mode 100644 index 0000000..5310019 --- /dev/null +++ b/pkg/api/srs/doc.go @@ -0,0 +1 @@ +package srs diff --git a/pkg/api/srs/list_contacts.go b/pkg/api/srs/list_contacts.go new file mode 100644 index 0000000..319fe94 --- /dev/null +++ b/pkg/api/srs/list_contacts.go @@ -0,0 +1,20 @@ +package srs + +import ( + "context" +) + +// ListContacts the domain name contacts +func (s *Client) ListContacts(ctx context.Context) (response ListContactsResponse, err error) { + u := "srs/list_contacts.json" + req, err := s.client.NewRequest("GET", u, "") + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/srs/list_domains.go b/pkg/api/srs/list_domains.go new file mode 100644 index 0000000..8ba46ca --- /dev/null +++ b/pkg/api/srs/list_domains.go @@ -0,0 +1,20 @@ +package srs + +import ( + "context" +) + +// ListDomains the registered domain names +func (s *Client) ListDomains(ctx context.Context) (response ListDomainsResponse, err error) { + u := "srs/list_domains.json" + req, err := s.client.NewRequest("GET", u, "") + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/srs/models.go b/pkg/api/srs/models.go new file mode 100644 index 0000000..f6056cb --- /dev/null +++ b/pkg/api/srs/models.go @@ -0,0 +1,17 @@ +package srs + +import ( + "github.com/sitehostnz/gosh/pkg/api" +) + +// Client is a Service to work with API Jobs. +type Client struct { + client *api.Client +} + +// New is used to instantiate the Client struct. +func New(c *api.Client) *Client { + return &Client{ + client: c, + } +} diff --git a/pkg/api/srs/request.go b/pkg/api/srs/request.go new file mode 100644 index 0000000..81d1db0 --- /dev/null +++ b/pkg/api/srs/request.go @@ -0,0 +1,7 @@ +package srs + +//type ( +// ListRequest struct { +// // Domain string `json:"domain"` +// } +//) diff --git a/pkg/api/srs/response.go b/pkg/api/srs/response.go new file mode 100644 index 0000000..32c6383 --- /dev/null +++ b/pkg/api/srs/response.go @@ -0,0 +1,19 @@ +package srs + +import "github.com/sitehostnz/gosh/pkg/models" + +type ( + ListDomainsResponse struct { + Return struct { + models.Pagination + Domains []models.Domain `json:"data"` + } `json:"return"` + models.APIResponse + } + + ListContactsResponse struct { + DomainContacts []models.DomainContact `json:"return"` + + models.APIResponse + } +) diff --git a/pkg/models/srs.go b/pkg/models/srs.go new file mode 100644 index 0000000..fe0494f --- /dev/null +++ b/pkg/models/srs.go @@ -0,0 +1,50 @@ +package models + +import "github.com/sitehostnz/gosh/pkg/shtypes" + +type ( + Domain struct { + ID shtypes.MaybeBigInt `json:"domain_id"` + Domain string `json:"domain"` + + State string `json:"state"` + Locked shtypes.MaybeBool `json:"locked"` + Private shtypes.MaybeBool `json:"private"` + Pending shtypes.MaybeBool `json:"pending"` + Premium shtypes.MaybeBool `json:"premium"` + + DateRegistered string `json:"dateregistered"` + DateModified string `json:"datemodified"` + DateBilledUntil string `json:"datebilleduntil"` + DateCancelled string `json:"datecancelled"` + DateLocked string `json:"datelocked"` + + AutorenewTerm shtypes.MaybeBigInt `json:"autorenew_term"` + AutorenewDaysRemaining shtypes.MaybeBigInt `json:"autorenew_days_remaining"` + + RegistrantName string `json:"registrant_name"` + + RegID shtypes.MaybeBigInt `json:"reg_id"` + RegName string `json:"reg_name"` + AdmID shtypes.MaybeBigInt `json:"adm_id"` + AdmName string `json:"adm_name"` + TecID shtypes.MaybeBigInt `json:"tec_id"` + TecName string `json:"tec_name"` + + ClientID shtypes.MaybeBigInt `json:"client_id"` + ClientName string `json:"client_name"` + + Api string `json:"api"` + } + + DomainContact struct { + ID shtypes.MaybeBigInt `json:"contact_id"` + Name string `json:"name"` + RegistrantName string `json:"registrant_name"` + Email string `json:"email"` + PhoneCountry string `json:"phone_cntry"` + PhoneArea string `json:"phone_area"` + PhoneLocal string `json:"phone_local"` + DomainCount shtypes.MaybeBigInt `json:"domain_count"` + } +) diff --git a/pkg/shtypes/MaybeBool.go b/pkg/shtypes/MaybeBool.go index 4819f53..07126a9 100644 --- a/pkg/shtypes/MaybeBool.go +++ b/pkg/shtypes/MaybeBool.go @@ -10,7 +10,13 @@ type MaybeBool bool // UnmarshalJSON is a helper interface for dealing with things that may or may not be a string representing a bool, or number representing a bool. func (fi *MaybeBool) UnmarshalJSON(b []byte) error { - maybeBool, err := strconv.ParseBool(strings.Trim(string(b), "\"")) + v := strings.Trim(string(b), "\"") + + // does null / empty equal false? possibly... + if v == "null" || v == "" { + v = "false" + } + maybeBool, err := strconv.ParseBool(v) if err != nil { return err }