-
Notifications
You must be signed in to change notification settings - Fork 261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Add whitelabeling helpers #306
Changes from all commits
2eb6264
81165b5
b750922
bc6fe3c
05dabda
8d7c0b7
7ae4b84
0c739b7
20855e1
71bc54e
d8c4604
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package whitelabel | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/sendgrid/sendgrid-go" | ||
"strconv" | ||
) | ||
|
||
type brandedLinkRequest struct { | ||
SubdomainInfo | ||
IsDefault bool `json:"default"` | ||
} | ||
|
||
type BrandedLink struct { | ||
SubdomainInfo | ||
User | ||
|
||
Id int64 `json:"id"` | ||
|
||
IsDefault bool `json:"default"` | ||
IsValid bool `json:"valid"` | ||
isLegacy bool `json:"legacy"` | ||
DNS map[string]DNSEntry `json:"dns"` | ||
} | ||
|
||
const linksEndpoint = "/v3/whitelabel/links" | ||
|
||
// CreateBrandedLink adds a new domain for branded links. For more info, see: https://sendgrid.com/docs/API_Reference/api_v3.html | ||
func CreateBrandedLink(key string, sub SubdomainInfo, isDefault bool) (*BrandedLink, error) { | ||
cl := sendgrid.NewClientForEndpoint(key, linksEndpoint) | ||
var err error | ||
var link = BrandedLink{} | ||
|
||
cl.Body, err = json.Marshal(brandedLinkRequest{sub, isDefault}) | ||
if err != nil { | ||
return &link, err | ||
} | ||
|
||
cl.Method = "POST" | ||
resp, err := sendgrid.MakeRequestRetry(cl.Request) | ||
if err != nil { | ||
return &link, err | ||
} | ||
|
||
err = json.Unmarshal([]byte(resp.Body), &link) | ||
return &link, err | ||
} | ||
|
||
// GetBrandedLinks fetches all branded link domains. | ||
func GetBrandedLinks(key string) ([]BrandedLink, error) { | ||
cl := sendgrid.NewClientForEndpoint(key, linksEndpoint) | ||
cl.Method = "GET" | ||
|
||
resp, err := sendgrid.MakeRequestRetry(cl.Request) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var links []BrandedLink | ||
err = json.Unmarshal([]byte(resp.Body), &links) | ||
return links, err | ||
} | ||
|
||
func getSingleBrandedLink(key, identifier, method string) (BrandedLink, error) { | ||
cl := sendgrid.NewClientForEndpoint(key, linksEndpoint+"/"+identifier) | ||
cl.Method = method | ||
var link BrandedLink | ||
|
||
resp, err := sendgrid.MakeRequestRetry(cl.Request) | ||
if err != nil { | ||
return link, err | ||
} | ||
|
||
err = json.Unmarshal([]byte(resp.Body), &link) | ||
return link, err | ||
} | ||
|
||
// GetBrandedLink fetches a branded domain with a specific id. | ||
func GetBrandedLink(key string, id int64) (BrandedLink, error) { | ||
return getSingleBrandedLink(key, strconv.FormatInt(id, 10), "GET") | ||
} | ||
|
||
// GetDefaultBrandedLink fetches the default branded link. | ||
func GetDefaultBrandedLink(key string) (BrandedLink, error) { | ||
return getSingleBrandedLink(key, "default", "GET") | ||
} | ||
|
||
// DeleteBrandedLink deletes a branded link | ||
func DeleteBrandedLink(key string, id int64) (BrandedLink, error) { | ||
return getSingleBrandedLink(key, "default", "DELETE") | ||
} | ||
|
||
type DNSValidationResult struct { | ||
Valid bool `json:"valid"` | ||
Reason string `json:"reason"` | ||
} | ||
|
||
type ValidationResult struct { | ||
Id int64 `json:"id"` | ||
Valid int64 `json:"valid"` | ||
|
||
ValidationResults map[string]DNSValidationResult `json:"validation_results"` | ||
} | ||
|
||
// ValidateBrandedLink validates the DNS records for a branded link. | ||
func ValidateBrandedLink(key string, id int64) (ValidationResult, error) { | ||
cl := sendgrid.NewClientForEndpoint(key, fmt.Sprintf("%v/%v/validate", linksEndpoint, id)) | ||
cl.Method = "POST" | ||
|
||
var vr ValidationResult | ||
resp, err := sendgrid.MakeRequestRetry(cl.Request) | ||
if err != nil { | ||
return vr, err | ||
} | ||
|
||
err = json.Unmarshal([]byte(resp.Body), &vr) | ||
return vr, err | ||
} | ||
|
||
// SetBrandedLinkDefault sets the default of a branded link domain. | ||
func SetBrandedLinkDefault(key string, id int64, isDefault bool) (BrandedLink, error) { | ||
cl := sendgrid.NewClientForEndpoint(key, fmt.Sprintf("%v/%v", linksEndpoint, id)) | ||
cl.Method = "PATCH" | ||
|
||
var link BrandedLink | ||
var err error | ||
|
||
cl.Body, err = json.Marshal(map[string]bool{"default": isDefault}) | ||
if err != nil { | ||
return link, err | ||
} | ||
|
||
resp, err := sendgrid.MakeRequestRetry(cl.Request) | ||
if err != nil { | ||
return link, err | ||
} | ||
|
||
err = json.Unmarshal([]byte(resp.Body), &link) | ||
return link, err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package whitelabel | ||
|
||
import ( | ||
"github.com/sendgrid/sendgrid-go" | ||
"encoding/json" | ||
) | ||
|
||
type ReverseDNSRequest struct { | ||
IP string `json:"ip"` | ||
SubdomainInfo | ||
} | ||
|
||
type ReverseDNSIP struct { | ||
ID int64 `json:"id"` | ||
|
||
ReverseDNSRequest | ||
|
||
Rdns string `json:"rdns"` | ||
Users []User `json:"users"` | ||
|
||
Valid bool `json:"valid"` | ||
Legacy bool `json:"legacy"` | ||
|
||
ARecord DNSEntry `json:"a_record"` | ||
} | ||
|
||
const ipsEndpoint = "/v3/whitelabel/ips" | ||
|
||
// CreateReverseDNS sets up a reverse DNS ip. | ||
func CreateReverseDNS(key, ip string, sub SubdomainInfo) (ReverseDNSIP, error) { | ||
var err error | ||
var rdip ReverseDNSIP | ||
cl := sendgrid.NewClientForEndpoint(key, ipsEndpoint) | ||
cl.Method = "POST" | ||
cl.Body, err = json.Marshal(ReverseDNSRequest{ip, sub}) | ||
if err != nil { | ||
return rdip, err | ||
} | ||
|
||
resp, err := sendgrid.MakeRequestRetry(cl.Request) | ||
if err != nil { | ||
return rdip, err | ||
} | ||
|
||
err = json.Unmarshal([]byte(resp.Body), &rdip) | ||
return rdip, err | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package whitelabel | ||
|
||
type SubdomainInfo struct { | ||
Domain string `json:"domain"` | ||
Subdomain string `json:"subdomain,omitempty"` | ||
} | ||
|
||
func NewSubdomainInfo(domain, subdomain string) SubdomainInfo { | ||
return SubdomainInfo{domain, subdomain} | ||
} | ||
|
||
type DNSEntry struct { | ||
Valid bool `json:"valid"` | ||
Type string `json:"type"` | ||
Host string `json:"host"` | ||
Data string `json:"data"` | ||
} | ||
|
||
type User struct { | ||
Username string `json:"username"` | ||
UserId int64 `json:"user_id"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,82 @@ func NewSendClient(key string) *Client { | |
return &Client{request} | ||
} | ||
|
||
// NewClientForEndpoint returns a client that can send requests to a specific endpoint. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this function, why not just use the default client? |
||
func NewClientForEndpoint(key, endpoint string) *Client { | ||
request := GetRequest(key, endpoint, "") | ||
return &Client{request} | ||
} | ||
|
||
// DefaultClient is used if no custom HTTP client is defined | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should be able to remove the following functions, as they now reside here: https://github.com/sendgrid/sendgrid-go/blob/main/base_interface.go |
||
var DefaultClient = rest.DefaultClient | ||
|
||
// API sets up the request to the SendGrid API, this is main interface. | ||
// This function is deprecated. Please use the MakeRequest or | ||
// MakeRequestAsync functions. | ||
func API(request rest.Request) (*rest.Response, error) { | ||
return DefaultClient.API(request) | ||
} | ||
|
||
// MakeRequest attempts a SendGrid request synchronously. | ||
func MakeRequest(request rest.Request) (*rest.Response, error) { | ||
return DefaultClient.API(request) | ||
} | ||
|
||
// MakeRequestRetry a synchronous request, but retry in the event of a rate | ||
// limited response. | ||
func MakeRequestRetry(request rest.Request) (*rest.Response, error) { | ||
retry := 0 | ||
var response *rest.Response | ||
var err error | ||
|
||
for { | ||
response, err = DefaultClient.API(request) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if response.StatusCode != http.StatusTooManyRequests { | ||
return response, nil | ||
} | ||
|
||
if retry > rateLimitRetry { | ||
return nil, errors.New("Rate limit retry exceeded") | ||
} | ||
retry++ | ||
|
||
resetTime := time.Now().Add(rateLimitSleep * time.Millisecond) | ||
|
||
reset, ok := response.Headers["X-RateLimit-Reset"] | ||
if ok && len(reset) > 0 { | ||
t, err := strconv.Atoi(reset[0]) | ||
if err == nil { | ||
resetTime = time.Unix(int64(t), 0) | ||
} | ||
} | ||
time.Sleep(resetTime.Sub(time.Now())) | ||
} | ||
} | ||
|
||
// MakeRequestAsync attempts a request asynchronously in a new go | ||
// routine. This function returns two channels: responses | ||
// and errors. This function will retry in the case of a | ||
// rate limit. | ||
func MakeRequestAsync(request rest.Request) (chan *rest.Response, chan error) { | ||
r := make(chan *rest.Response) | ||
e := make(chan error) | ||
|
||
go func() { | ||
response, err := MakeRequestRetry(request) | ||
if err != nil { | ||
e <- err | ||
} | ||
if response != nil { | ||
r <- response | ||
} | ||
}() | ||
|
||
return r, e | ||
|
||
// GetRequestSubuser like NewSendClient but with On-Behalf of Subuser | ||
// @return [Client] | ||
func NewSendClientSubuser(key, subuser string) *Client { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have updated the verbiage used to describe a "whitelabel". Please update accordingly. See this PR for an example.