260 lines
6.9 KiB
Go
260 lines
6.9 KiB
Go
package notificationservice
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
|
|
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
|
|
"bitbucket.org/nemt/nemt-portal-api/domain"
|
|
"bitbucket.org/nemt/nemt-portal-api/domain/contract"
|
|
"bitbucket.org/nemt/nemt-portal-api/domain/service"
|
|
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
|
)
|
|
|
|
const (
|
|
twilioMessageURL = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json"
|
|
twilioNumbersURL = "https://api.twilio.com/2010-04-01/Accounts/%s/AvailablePhoneNumbers/US/Local.json"
|
|
twiliActualNumbersURL = "https://api.twilio.com/2010-04-01/Accounts/%s/IncomingPhoneNumbers.json"
|
|
)
|
|
|
|
// twilioService holds methods to twillio application service
|
|
type twilioService struct {
|
|
svc *service.Service
|
|
mapEntity *entitymapping.Mapper
|
|
cfg *config.Config
|
|
urlMessage string
|
|
urlNumber string
|
|
urlActualNumber string
|
|
cache contract.CacheManager
|
|
}
|
|
|
|
// newTwilioService returns a twilioService instance
|
|
func newTwilioService(svc *service.Service, mapper *entitymapping.Mapper, cfg *config.Config, cache contract.CacheManager) *twilioService {
|
|
return &twilioService{
|
|
svc: svc,
|
|
mapEntity: mapper,
|
|
cfg: cfg,
|
|
urlMessage: fmt.Sprintf(twilioMessageURL, cfg.Twilio.Account),
|
|
urlNumber: fmt.Sprintf(twilioNumbersURL, cfg.Twilio.Account),
|
|
urlActualNumber: fmt.Sprintf(twiliActualNumbersURL, cfg.Twilio.Account),
|
|
cache: cache,
|
|
}
|
|
}
|
|
|
|
func (s twilioService) GetProxyNumber(to string) (viewmodel.ProxyNumber, error) {
|
|
key := "proxy" + to
|
|
var number viewmodel.ProxyNumber
|
|
err := s.cache.GetStruct(key, &number)
|
|
if err != nil || err == domain.ErrCacheMiss {
|
|
return number, err
|
|
} else {
|
|
return number, nil
|
|
}
|
|
}
|
|
|
|
func (s twilioService) GetAvaliableNumbers(to string) (viewmodel.ListNumbers, error) {
|
|
if to != "" {
|
|
if strings.Contains(to, "+1") {
|
|
to = strings.Replace(to, "+1", "", -1)
|
|
}
|
|
|
|
area := to[0:3]
|
|
|
|
msgData := url.Values{}
|
|
msgData.Set("PhoneNumber", area)
|
|
|
|
client := &http.Client{}
|
|
req, _ := http.NewRequest("GET", s.urlActualNumber+"?"+msgData.Encode(), bytes.NewBuffer([]byte{}))
|
|
|
|
req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
fmt.Println("Error Twilio: ", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
var data viewmodel.ListNumbers
|
|
decoder := json.NewDecoder(resp.Body)
|
|
err := decoder.Decode(&data)
|
|
if err != nil {
|
|
return viewmodel.ListNumbers{}, err
|
|
}
|
|
|
|
return data, nil
|
|
} else {
|
|
return viewmodel.ListNumbers{}, errors.New(resp.Status)
|
|
}
|
|
}
|
|
return viewmodel.ListNumbers{}, nil
|
|
}
|
|
|
|
func (s twilioService) GetAvaliableNewNumbers(to string) (viewmodel.ListNumbers, error) {
|
|
if to != "" {
|
|
if strings.Contains(to, "+1") {
|
|
to = strings.Replace(to, "+1", "", -1)
|
|
}
|
|
|
|
area := to[0:3]
|
|
|
|
msgData := url.Values{}
|
|
msgData.Set("AreaCode", area)
|
|
|
|
client := &http.Client{}
|
|
req, _ := http.NewRequest("GET", s.urlNumber+"?"+msgData.Encode(), bytes.NewBuffer([]byte{}))
|
|
|
|
req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
fmt.Println("Error Twilio: ", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
var data viewmodel.ListNumbers
|
|
decoder := json.NewDecoder(resp.Body)
|
|
err := decoder.Decode(&data)
|
|
if err != nil {
|
|
return viewmodel.ListNumbers{}, err
|
|
}
|
|
|
|
return data, nil
|
|
} else {
|
|
return viewmodel.ListNumbers{}, errors.New(resp.Status)
|
|
}
|
|
}
|
|
return viewmodel.ListNumbers{}, nil
|
|
}
|
|
|
|
func (s twilioService) SaveNewNumber(number viewmodel.Number) (viewmodel.Number, error) {
|
|
phoneNumber := number.PhoneNumber
|
|
if strings.Contains(phoneNumber, "+1") {
|
|
phoneNumber = strings.Replace(phoneNumber, "+1", "", -1)
|
|
}
|
|
area := phoneNumber[0:3]
|
|
|
|
msgData := url.Values{}
|
|
msgData.Set("PhoneNumber", number.PhoneNumber)
|
|
msgData.Set("AreaCode", area)
|
|
msgData.Set("FriendlyName", number.FriendlyName)
|
|
msgData.Set("SmsApplicationSid", s.cfg.Twilio.TwiMLSID)
|
|
msgDataReader := *strings.NewReader(msgData.Encode())
|
|
|
|
client := &http.Client{}
|
|
req, _ := http.NewRequest("POST", s.urlActualNumber, &msgDataReader)
|
|
req.Header.Add("Accept", "application/json")
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
fmt.Println("Error Twilio: ", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
decoder := json.NewDecoder(resp.Body)
|
|
err := decoder.Decode(&number)
|
|
if err != nil {
|
|
return viewmodel.Number{}, err
|
|
}
|
|
|
|
return number, nil
|
|
} else {
|
|
return viewmodel.Number{}, errors.New(resp.Status)
|
|
}
|
|
}
|
|
|
|
func (s twilioService) GetValidNumber(from string, to string) (viewmodel.Number, error) {
|
|
list, err := s.GetAvaliableNumbers(to)
|
|
if err != nil {
|
|
return viewmodel.Number{}, err
|
|
}
|
|
|
|
if len(list.IncomingPhones) > 0 {
|
|
return list.IncomingPhones[0], nil
|
|
}
|
|
|
|
list, err = s.GetAvaliableNewNumbers(to)
|
|
if err != nil {
|
|
return viewmodel.Number{}, err
|
|
}
|
|
|
|
if len(list.AvailableNumbers) > 0 {
|
|
number, err := s.SaveNewNumber(list.AvailableNumbers[0])
|
|
if err != nil {
|
|
fmt.Println("Error on GetValidNumber: ", err.Error())
|
|
return viewmodel.Number{}, err
|
|
}
|
|
|
|
return number, nil
|
|
} else {
|
|
return viewmodel.Number{}, errors.New("No new number available")
|
|
}
|
|
}
|
|
|
|
func (s twilioService) GetNumber(from string, to string) (string, error) {
|
|
number, err := s.GetValidNumber(from, to)
|
|
if err != nil {
|
|
return from, err
|
|
}
|
|
|
|
return number.PhoneNumber, nil
|
|
}
|
|
|
|
func (s twilioService) SendSMS(from string, to string, message string) error {
|
|
if from == "" {
|
|
return errors.New("TwilioService.SendSMS: from parameter is mandatory")
|
|
}
|
|
if to == "" {
|
|
return errors.New("TwilioService.SendSMS: to parameter is mandatory")
|
|
}
|
|
if message == "" {
|
|
return errors.New("TwilioService.SendSMS: message parameter is mandatory")
|
|
}
|
|
|
|
senderNumber, err := s.GetNumber(from, to)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msgData := url.Values{}
|
|
msgData.Set("To", to)
|
|
msgData.Set("From", senderNumber)
|
|
msgData.Set("Body", message)
|
|
|
|
msgDataReader := *strings.NewReader(msgData.Encode())
|
|
|
|
client := &http.Client{}
|
|
req, _ := http.NewRequest("POST", s.urlMessage, &msgDataReader)
|
|
|
|
req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token)
|
|
req.Header.Add("Accept", "application/json")
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
fmt.Println("Error Twilio: ", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
var data map[string]interface{}
|
|
decoder := json.NewDecoder(resp.Body)
|
|
err := decoder.Decode(&data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
fmt.Println("Error on Twilio Response: ", resp.Status)
|
|
return errors.New(resp.Status)
|
|
}
|
|
|
|
return nil
|
|
}
|