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

Code Refactor #11

Merged
merged 4 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/GitLink.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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,
}
}
183 changes: 65 additions & 118 deletions pkg/sms/sms_sender.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package sms

import (
"context"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/url"
"strings"
Expand All @@ -11,149 +13,94 @@ 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
}
// Retry sends an SMS with exponential backoff
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()
if err == nil {
return response, nil
}
response, err := s.SendSMS(ctx)
if err == nil {
return response, nil
}

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

time.Sleep(waitTime)
}
return SmsSenderResponse{}, fmt.Errorf("max retries reached")
}
}
Loading