Skip to content

Commit

Permalink
chore: Code structure reorganization
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeMwita committed Jul 5, 2024
1 parent ed9adad commit 0622f79
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 112 deletions.
28 changes: 28 additions & 0 deletions pkg/sms/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package sms

import "net/http"

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

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

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

func NewClient(client Doer, apiKey, apiUser string) *Client {
return &Client{
apiURL: DefaultAPIURL,
apiKey: apiKey,
apiUser: apiUser,
client: client,
}
}
166 changes: 54 additions & 112 deletions pkg/sms/sms_sender.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sms

import (
"context"
"encoding/json"
"fmt"
"math/rand"
Expand All @@ -12,149 +13,90 @@ import (
"github.com/google/uuid"
)

type SmsSender struct {
ApiKey string `json:"api_key"`
ApiUser string `json:"api_user"`
Recipients []string `json:"recipients"`
Message string `json:"message"`
Sender string `json:"sender"`
SmsKey string `json:"sms_key"`
}

type Recipient struct {
Key string `json:"key"`
Cost string `json:"cost"`
SmsKey string `json:"sms_key"`
MessageId string `json:"message_id"`
MessagePart int `json:"message_part"`
Number string `json:"number"`
Status string `json:"status"`
StatusCode string `json:"status_code"`
}

type SmsMessageData struct {
Message string `json:"message"`
Cost string `json:"cost"`
Recipients []Recipient `json:"recipients"`
}

type ErrorResponse struct {
HasError bool `json:"has_error"`
Message string `json:"message"`
}

type SmsSenderResponse struct {
ErrorResponse ErrorResponse `json:"error_response"`
SmsMessageData SmsMessageData `json:"sms_message_data"`
}

// SendSMS sends an SMS using the Africa's Talking API
func (s *SmsSender) SendSMS() (SmsSenderResponse, error) {
endpoint := "https://api.africastalking.com/version1/messaging"
parsedURL, err := url.Parse(endpoint)
if err != nil {
return SmsSenderResponse{}, err
}

body := map[string][]string{
"username": {s.ApiUser},
"to": s.Recipients,
"message": {s.Message},
"from": {s.Sender},
}

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

req, err := http.NewRequest(http.MethodPost, parsedURL.String(), strings.NewReader(form.Encode()))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.Client.apiURL, strings.NewReader(form.Encode()))
if err != nil {
return SmsSenderResponse{}, err
}

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

client := &http.Client{}
res, err := client.Do(req)
res, err := s.Client.client.Do(req)
if err != nil {
return SmsSenderResponse{}, err
}
defer res.Body.Close()

if res.StatusCode == http.StatusCreated {
recipients := make([]Recipient, 0)

var data map[string]interface{}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
return SmsSenderResponse{}, err
}
if res.StatusCode != http.StatusCreated {
return SmsSenderResponse{
ErrorResponse: ErrorResponse{
HasError: true,
Message: "Message not sent",
},
}, fmt.Errorf("status code: %d", res.StatusCode)
}

smsMessageData := data["SMSMessageData"].(map[string]interface{})
message := smsMessageData["Message"].(string)
var data map[string]interface{}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
return SmsSenderResponse{}, err
}

cost := ""
for _, word := range strings.Split(message, " ") {
cost = word
}
recipientsData := smsMessageData["Recipients"].([]interface{})

for _, recipient := range recipientsData {
recipientData := recipient.(map[string]interface{})

rct := Recipient{
Key: uuid.New().String(),
Cost: recipientData["cost"].(string),
SmsKey: s.SmsKey,
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)
}
smsMessageData := data["SMSMessageData"].(map[string]interface{})
message := smsMessageData["Message"].(string)
cost := ""
for _, word := range strings.Split(message, " ") {
cost = word
}

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

for _, recipient := range recipientsData {
recipientData := recipient.(map[string]interface{})

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

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

smsSenderResponse := SmsSenderResponse{
return SmsSenderResponse{
ErrorResponse: ErrorResponse{
HasError: true,
Message: "Message not sent",
HasError: false,
},
}

return smsSenderResponse, fmt.Errorf("status code: %d", res.StatusCode)
SmsMessageData: SmsMessageData{
Message: message,
Cost: cost,
Recipients: recipients,
},
}, nil
}

func (s *SmsSender) RetrySendSMS(maxRetries int) (SmsSenderResponse, error) {
func (s *SmsSender) RetrySendSMS(ctx context.Context, maxRetries int) (SmsSenderResponse, error) {
for retry := 0; retry < maxRetries; retry++ {
response, err := s.SendSMS()
response, err := s.SendSMS(ctx)
if err == nil {
return response, nil
}

delay := time.Duration(1<<uint(retry)) * time.Second

// Add jitter (randomness) to the delay
jitter := time.Duration(rand.Intn(int(delay))) * time.Millisecond
waitTime := delay + jitter

Expand Down
35 changes: 35 additions & 0 deletions pkg/sms/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package sms

type SmsSender struct {
Client *Client
Recipients []string `json:"recipients"`
Message string `json:"message"`
Sender string `json:"sender"`
SmsKey string `json:"sms_key"`
}

type Recipient struct {
Key string `json:"key"`
Cost string `json:"cost"`
SmsKey string `json:"sms_key"`
MessageId string `json:"message_id"`
MessagePart int `json:"message_part"`
Number string `json:"number"`
Status string `json:"status"`
StatusCode string `json:"status_code"`
}

type SmsMessageData struct {
Message string `json:"message"`
Cost string `json:"cost"`
Recipients []Recipient `json:"recipients"`
}
type ErrorResponse struct {
HasError bool `json:"has_error"`
Message string `json:"message"`
}

type SmsSenderResponse struct {
ErrorResponse ErrorResponse `json:"error_response"`
SmsMessageData SmsMessageData `json:"sms_message_data"`
}

0 comments on commit 0622f79

Please sign in to comment.