Skip to content

Commit

Permalink
Merge pull request #11 from MikeMwita/code-structure-reorganization
Browse files Browse the repository at this point in the history
Code Refactor
  • Loading branch information
MikeMwita authored Jul 10, 2024
2 parents 018a2e5 + 5802cef commit e0cd773
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 121 deletions.
28 changes: 19 additions & 9 deletions examples/send_sms_example.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
package sms_sender

import (
"fmt"
"context"
"github.com/MikeMwita/africastalking-go/pkg/sms"
"log"
"net/http"
"os"
"time"
)

func main() {
// Example usage
sender := sms.SmsSender{
ApiKey: "your_api_key",
ApiUser: "your_api_user",
apiKey := os.Getenv("API_KEY")
apiUser := os.Getenv("API_USER")

client := sms.NewClient(&http.Client{}, apiKey, apiUser)
sender := &sms.SmsSender{
Client: client,
Recipients: []string{"+1234567890"},
Message: "Hello, world!",
Message: "Test message",
Sender: "YourSenderID",
SmsKey: "unique_sms_key",
}

response, err := sender.SendSMS()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

resp, err := sender.RetrySendSMS(ctx, 3)
if err != nil {
log.Fatal(err)
log.Fatalf("Failed to send SMS: %v", err)
}

fmt.Printf("Response: %+v\n", response)
log.Printf("SMS Response: %+v", resp)
}
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 e0cd773

Please sign in to comment.