Skip to content

Commit

Permalink
feat: Refactor the base client
Browse files Browse the repository at this point in the history
  • Loading branch information
michael.mwita committed Sep 17, 2024
1 parent e0cd773 commit d79719d
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 162 deletions.
27 changes: 16 additions & 11 deletions pkg/sms/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@ import "net/http"

const (
DefaultAPIURL = "https://api.africastalking.com/version1/messaging"
sandboxAPIURL = "https://api.sandbox.africastalking.com/version1/messaging"
SandboxAPIURL = "https://api.sandbox.africastalking.com/version1/messaging"
)

type Doer interface {
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}

type Client struct {
apiURL string
apiKey string
apiUser string
client Doer
apiURL string
apiKey string
apiUser string
httpClient HTTPClient
}

func NewClient(client Doer, apiKey, apiUser string) *Client {
func NewClient(httpClient HTTPClient, apiKey, apiUser string, useSandbox bool) *Client {
apiURL := DefaultAPIURL
if useSandbox {
apiURL = SandboxAPIURL
}

return &Client{
apiURL: DefaultAPIURL,
apiKey: apiKey,
apiUser: apiUser,
client: client,
apiURL: apiURL,
apiKey: apiKey,
apiUser: apiUser,
httpClient: httpClient,
}
}
105 changes: 64 additions & 41 deletions pkg/sms/sms_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sms
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
Expand All @@ -14,24 +15,25 @@ import (
)

func (s *SmsSender) SendSMS(ctx context.Context) (SmsSenderResponse, error) {
form := url.Values{}
form.Add("username", s.Client.apiUser)
form.Add("to", strings.Join(s.Recipients, ","))
form.Add("message", s.Message)
form.Add("from", s.Sender)
form := url.Values{
"username": {s.Client.apiUser},
"to": {strings.Join(s.Recipients, ",")},
"message": {s.Message},
"from": {s.Sender},
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.Client.apiURL, strings.NewReader(form.Encode()))
if err != nil {
return SmsSenderResponse{}, err
return SmsSenderResponse{}, fmt.Errorf("failed to create request: %w", err)
}

req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("apiKey", s.Client.apiKey)

res, err := s.Client.client.Do(req)
res, err := s.Client.httpClient.Do(req)
if err != nil {
return SmsSenderResponse{}, err
return SmsSenderResponse{}, fmt.Errorf("failed to send SMS: %w", err)
}
defer res.Body.Close()

Expand All @@ -41,52 +43,72 @@ func (s *SmsSender) SendSMS(ctx context.Context) (SmsSenderResponse, error) {
HasError: true,
Message: "Message not sent",
},
}, fmt.Errorf("status code: %d", res.StatusCode)
}, fmt.Errorf("non-201 response status: %d", res.StatusCode)
}

var data map[string]interface{}
err = json.NewDecoder(res.Body).Decode(&data)
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return SmsSenderResponse{}, fmt.Errorf("failed to decode response: %w", err)
}

smsMessageData, ok := data["SMSMessageData"].(map[string]interface{})
if !ok {
return SmsSenderResponse{}, errors.New("invalid response format: missing 'SMSMessageData'")
}

recipients, err := parseRecipients(smsMessageData["Recipients"])
if err != nil {
return SmsSenderResponse{}, err
return SmsSenderResponse{}, fmt.Errorf("failed to parse recipients: %w", err)
}

smsMessageData := data["SMSMessageData"].(map[string]interface{})
message := smsMessageData["Message"].(string)
cost := ""
for _, word := range strings.Split(message, " ") {
cost = word
message, ok := smsMessageData["Message"].(string)
if !ok {
return SmsSenderResponse{}, errors.New("invalid message format")
}

recipientsData := smsMessageData["Recipients"].([]interface{})
recipients := make([]Recipient, 0)
return SmsSenderResponse{
ErrorResponse: ErrorResponse{HasError: false},
SmsMessageData: SmsMessageData{
Message: message,
Cost: extractCostFromMessage(message),
Recipients: recipients,
},
}, nil
}

func parseRecipients(data interface{}) ([]Recipient, error) {
recipientsData, ok := data.([]interface{})
if !ok {
return nil, errors.New("invalid recipient format")
}

var recipients []Recipient
for _, recipient := range recipientsData {
recipientData := recipient.(map[string]interface{})
recipientData, ok := recipient.(map[string]interface{})
if !ok {
return nil, errors.New("invalid recipient data")
}

rct := Recipient{
recipients = append(recipients, Recipient{
Key: uuid.New().String(),
Cost: recipientData["cost"].(string),
SmsKey: s.SmsKey,
SmsKey: recipientData["sms_key"].(string),
MessageId: recipientData["messageId"].(string),
MessagePart: int(recipientData["messageParts"].(float64)),
Number: recipientData["number"].(string),
Status: recipientData["status"].(string),
StatusCode: fmt.Sprintf("%v", recipientData["statusCode"]),
}

recipients = append(recipients, rct)
})
}
return recipients, nil
}

return SmsSenderResponse{
ErrorResponse: ErrorResponse{
HasError: false,
},
SmsMessageData: SmsMessageData{
Message: message,
Cost: cost,
Recipients: recipients,
},
}, nil
func extractCostFromMessage(message string) string {
words := strings.Split(message, " ")
if len(words) > 0 {
return words[0]
}
return ""
}

func (s *SmsSender) RetrySendSMS(ctx context.Context, maxRetries int) (SmsSenderResponse, error) {
Expand All @@ -95,12 +117,13 @@ func (s *SmsSender) RetrySendSMS(ctx context.Context, maxRetries int) (SmsSender
if err == nil {
return response, nil
}

delay := time.Duration(1<<uint(retry)) * time.Second
jitter := time.Duration(rand.Intn(int(delay))) * time.Millisecond
waitTime := delay + jitter

time.Sleep(waitTime)
time.Sleep(backoff(retry))
}
return SmsSenderResponse{}, fmt.Errorf("max retries reached")
return SmsSenderResponse{}, errors.New("max retries reached")
}

func backoff(retry int) time.Duration {
delay := time.Duration(1<<uint(retry)) * time.Second
jitter := time.Duration(rand.Intn(int(delay))) * time.Millisecond
return delay + jitter
}
Loading

0 comments on commit d79719d

Please sign in to comment.