Skip to content
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

Closed
wants to merge 11 commits into from
141 changes: 141 additions & 0 deletions helpers/whitelabel/branded_links.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package whitelabel
Copy link
Contributor

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.


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
}
48 changes: 48 additions & 0 deletions helpers/whitelabel/ips.go
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
}

22 changes: 22 additions & 0 deletions helpers/whitelabel/types.go
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"`
}
76 changes: 76 additions & 0 deletions sendgrid.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,82 @@ func NewSendClient(key string) *Client {
return &Client{request}
}

// NewClientForEndpoint returns a client that can send requests to a specific endpoint.
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 {
Expand Down