diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index e69de29..6c5fe12 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -0,0 +1,8 @@ +name: Continuous Deployment + +jobs: +on: + pull_request: + branches: [main] + push: + branches: [main] \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..4d55d89 --- /dev/null +++ b/Readme.md @@ -0,0 +1,96 @@ +

+# daraja-go

+STILL IN BETA!

+Unit-testing +GO REPORT +DOCS +

+ + +# africastalking-go + +A Go SDK for Africa's Talking, a platform that provides various communication and payment services in Africa. + +## Features + +- SMS: Send and receive SMS messages to and from any network in Africa +- Voice: Make and receive voice calls, play media, and control the call flow +- USSD: Create interactive menus and sessions for your users +- Airtime: Send airtime to mobile subscribers across Africa +- Payments: Accept and disburse payments via mobile money, card, and bank + + +## Installation + +To install the SDK, you need to have Go 1.17 or higher installed on your machine. You also need to have an account with Africa's Talking and get an API key and a username. + +- Install the SDK using the go get command: + +```bash +go get github.com/MikeMwita/africastalking-go +``` + +- Import the SDK in your code using the import statement: + +```go +import "github.com/MikeMwita/africastalking-go" +``` + +## Usage + +To use the SDK, you need to create a client object and pass it your username and API key. You can then use the client object to access the different services and their methods. For example, you can use something like this: + +```go +package main + +import ( + "fmt" + "log" + + "github.com/YOUR_USERNAME/africastalking-go" +) + +func main() { + // Create a client object with your username and API key + client := africastalking.NewClient("YOUR_USERNAME", "YOUR_API_KEY") + + // Use the client object to access the SMS service + smsService := client.SMS + + // Create an SMS sender object with the required parameters + smsSender := smsService.SmsSender{ + Recipients: []string{"+254712345678"}, + Message: "Hello, this is a test message from Africa's Talking", + Sender: "YOUR_SENDER", + } + + // Call the Send method and get the result + res, err := smsService.Send(smsSender) + if err != nil { + log.Fatal(err) + } + fmt.Println(res) +} +``` + +## Testing + +To test the SDK, you need to have a test database and a test account with Africa's Talking. You also need to set some environment variables for the test configuration. + +- Create a test database and populate it with some test data +- Create a test account with Africa's Talking and get a test API key and a test username +- Set the following environment variables: + +```bash +export AT_TEST_DB_CONNECTION_STRING=YOUR_TEST_DB_CONNECTION_STRING # The connection string for your test database +export AT_TEST_API_KEY=YOUR_TEST_API_KEY # The API key for your test account +export AT_TEST_USERNAME=YOUR_TEST_USERNAME # The username for your test account +``` + +- Run the tests using the go test command and pass it the -cover flag: + +```bash +go test -cover ./... +``` + + diff --git a/assets/africanstalking.png b/assets/africanstalking.png new file mode 100644 index 0000000..2e75716 Binary files /dev/null and b/assets/africanstalking.png differ diff --git a/at b/at deleted file mode 100755 index 5172484..0000000 Binary files a/at and /dev/null differ diff --git a/domain/entities/recipient.go b/domain/entities/recipient.go deleted file mode 100644 index 6db7edf..0000000 --- a/domain/entities/recipient.go +++ /dev/null @@ -1,15 +0,0 @@ -package entities - -// Recipient represents a recipient of an SMS message -type Recipient struct { - Key string `json:"key"` // the unique identifier of the recipient - Cost string `json:"cost"` // the cost of sending the message to the recipient - SMSKey string `json:"smsKey"` // the foreign key to the SMS sender - MessageID string `json:"messageId"` // the message ID from the AfricasTalking API - MessagePart int `json:"messagePart"` // the number of message parts - Number string `json:"number"` // the phone number of the recipient - Status string `json:"status"` // the status of the message delivery - StatusCode int `json:"statusCode"` // the status code from the AfricasTalking API - Label any - QuestionId any -} diff --git a/domain/entities/sms_sender.go b/domain/entities/sms_sender.go deleted file mode 100644 index e6871d4..0000000 --- a/domain/entities/sms_sender.go +++ /dev/null @@ -1,22 +0,0 @@ -package entities - -type SMSSender struct { - Key string `json:"key"` // the unique identifier of the sender - APIKey string `json:"apiKey"` // the API key from the AfricasTalking API - APIUser string `json:"apiUser"` // the username from the AfricasTalking API - Recipients []string `json:"recipients"` // the list of phone numbers to send the message to - Message string `json:"message"` // the message content - Sender string `json:"sender"` // the sender ID - Title any - BulkSMSMode int - RetryDurationInHours int - LinkId string - Keyword string - Enqueue int -} - -type SMSMessageData struct { - Message string - Cost string - Recipients []*Recipient -} diff --git a/domain/interfaces/sms_service.go b/domain/interfaces/sms_service.go deleted file mode 100644 index fceb12b..0000000 --- a/domain/interfaces/sms_service.go +++ /dev/null @@ -1,7 +0,0 @@ -package interfaces - -import "github.com/MikeMwita/at/domain/entities" - -type SMSService interface { - SendSMS(smsSender *entities.SMSSender) (*entities.SMSMessageData, error) -} diff --git a/domain/services/oldsend_sms.go b/domain/services/oldsend_sms.go deleted file mode 100644 index 09a8783..0000000 --- a/domain/services/oldsend_sms.go +++ /dev/null @@ -1,185 +0,0 @@ -package services - -import ( - "encoding/json" - "errors" - "github.com/MikeMwita/at/domain/entities" - "github.com/MikeMwita/at/domain/interfaces" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" -) - -var ( - ErrInvalidInput = errors.New("invalid input") - ErrMessageNotSent = errors.New("message not sent") -) - -// SMSServiceImpl is the implementation of the SMSService interface -type SMSServiceImpl struct { - Username string - APIKey string - Env string -} - -// SendSMS sends an SMS message to the recipients and returns the response data -func (s *SMSServiceImpl) SendSMS(smsSender *entities.SMSSender) (*entities.SMSMessageData, error) { - // Validate the input - if smsSender == nil || smsSender.APIKey == "" || smsSender.APIUser == "" || len(smsSender.Recipients) == 0 || smsSender.Message == "" { - return nil, ErrInvalidInput - } - - // Create the request URL - baseURL := "https://api.africastalking.com/version1/messaging" - if s.Env == "sandbox" { - baseURL = "https://api.sandbox.africastalking.com/version1/messaging" - } - - // Create the request headers - headers := map[string]string{ - "Accept": "application/json", - "Content-Type": "application/x-www-form-urlencoded", - "apiKey": s.APIKey, - } - - // Create the request body - body := url.Values{} - body.Set("username", s.Username) - body.Set("to", strings.Join(smsSender.Recipients, ",")) - body.Set("message", smsSender.Message) - if smsSender.Sender != "" { - body.Set("from", smsSender.Sender) - } - if smsSender.BulkSMSMode != 0 { - body.Set("bulkSMSMode", strconv.Itoa(smsSender.BulkSMSMode)) - } - if smsSender.Enqueue != 0 { - body.Set("enqueue", strconv.Itoa(smsSender.Enqueue)) - } - if smsSender.Keyword != "" { - body.Set("keyword", smsSender.Keyword) - } - if smsSender.LinkId != "" { - body.Set("linkId", smsSender.LinkId) - } - if smsSender.RetryDurationInHours != 0 { - body.Set("retryDurationInHours", strconv.Itoa(smsSender.RetryDurationInHours)) - } - - // Create the HTTP request - req, err := http.NewRequest(http.MethodPost, baseURL, strings.NewReader(body.Encode())) - if err != nil { - return nil, err - } - - // Set the request headers - for k, v := range headers { - req.Header.Set(k, v) - } - - // Create the HTTP client - client := &http.Client{} - - // Send the HTTP request - resp, err := client.Do(req) - if err != nil { - return nil, err - } - - // Close the response body - defer resp.Body.Close() - - // Read the response body - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - // Parse the response data - var responseData map[string]map[string]interface{} - err = json.Unmarshal(data, &responseData) - if err != nil { - return nil, err - } - - // Extract the SMS message data - smsMessageData, ok := responseData["SMSMessageData"] - if !ok { - return nil, ErrMessageNotSent - } - - // Extract the message - message, ok := smsMessageData["Message"].(string) - if !ok { - return nil, ErrMessageNotSent - } - - // Extract the recipients - recipientsData, ok := smsMessageData["Recipients"].([]interface{}) - if !ok { - return nil, ErrMessageNotSent - } - - // Convert the recipients data to entities - recipients := make([]*entities.Recipient, len(recipientsData)) - for i, r := range recipientsData { - recipientData, ok := r.(map[string]interface{}) - if !ok { - return nil, ErrMessageNotSent - } - - // Extract the recipient fields - cost, ok := recipientData["cost"].(string) - if !ok { - return nil, ErrMessageNotSent - } - messageID, ok := recipientData["messageId"].(string) - if !ok { - return nil, ErrMessageNotSent - } - number, ok := recipientData["number"].(string) - if !ok { - return nil, ErrMessageNotSent - } - status, ok := recipientData["status"].(string) - if !ok { - return nil, ErrMessageNotSent - } - statusCode, ok := recipientData["statusCode"].(float64) - if !ok { - return nil, ErrMessageNotSent - } - - // Create the recipient entity - recipient := &entities.Recipient{ - Cost: cost, - MessageID: messageID, - Number: number, - Status: status, - StatusCode: int(statusCode), - } - - // Add the recipient to the list - recipients[i] = recipient - } - - // Create the SMS message data entity - smsData := &entities.SMSMessageData{ - Message: message, - Cost: message[strings.LastIndex(message, " ")+1:], - Recipients: recipients, - } - - // Return the SMS message data - return smsData, nil -} - -// NewSMSService creates a new SMSService instance -func NewSMSService(username, apiKey, env string) interfaces.SMSService { - return &SMSServiceImpl{ - Username: username, - APIKey: apiKey, - Env: env} -} diff --git a/go.mod b/go.mod index bcc7ab2..214dc11 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ -module github.com/MikeMwita/at +module github.com/MikeMwita/africastalking-go -go 1.20 - -require github.com/joho/godotenv v1.5.1 // indirect +go 1.21.4 diff --git a/go.sum b/go.sum deleted file mode 100644 index d61b19e..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/infrastructure/adapters/http_sms_adapter.go b/infrastructure/adapters/http_sms_adapter.go deleted file mode 100644 index cb22f31..0000000 --- a/infrastructure/adapters/http_sms_adapter.go +++ /dev/null @@ -1,56 +0,0 @@ -package adapters - -import ( - "encoding/json" - "github.com/MikeMwita/at/config" - "github.com/MikeMwita/at/domain/entities" - "github.com/MikeMwita/at/domain/interfaces" - "net/http" - "strings" -) - -type HTTPSMSHandler struct { - smsService interfaces.SMSService -} - -func (h *HTTPSMSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Parse request parameters - r.ParseForm() - recipients := strings.Split(r.FormValue("recipient"), ",") - message := r.FormValue("message") - sender := r.FormValue("sender") - - // Validate input parameters - if len(recipients) == 0 || message == "" { - http.Error(w, "Missing required parameters: recipient, message", http.StatusBadRequest) - return - } - - cfg := config.LoadConfig() - // Create the SMSSender object - smsSender := &entities.SMSSender{ - APIUser: cfg.Username, - APIKey: cfg.APIKey, - Recipients: recipients, - Message: message, - Sender: sender, - } - - // Call the SendSMS method of the sms.Service instance - sendResponse, err := h.smsService.SendSMS(smsSender) - - // Handle errors and sendResponse - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Return HTTP response - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(sendResponse) -} - -func NewHTTPSMSHandler(smsService interfaces.SMSService) *HTTPSMSHandler { - return &HTTPSMSHandler{smsService: smsService} -}