From 5e734aad548b1037245f8ddca573235eb9ebcb77 Mon Sep 17 00:00:00 2001 From: esimonov Date: Thu, 6 Feb 2020 10:26:18 +0200 Subject: [PATCH] Support RouteMobile SMS service --- .../sms/routemobile/sms_service.go | 68 +++++++++++++++++++ model/server_settings.go | 11 ++- model/server_settings_validation.go | 15 +++- server-config.yaml | 8 ++- server/server.go | 3 + 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 external_services/sms/routemobile/sms_service.go diff --git a/external_services/sms/routemobile/sms_service.go b/external_services/sms/routemobile/sms_service.go new file mode 100644 index 00000000..e26789b0 --- /dev/null +++ b/external_services/sms/routemobile/sms_service.go @@ -0,0 +1,68 @@ +package routemobile + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/madappgang/identifo/model" +) + +const baseURLuae = "https://sms.rmlconnect.net%s" + +// SMSService sends SMS via RouteMobile service. +type SMSService struct { + username string + password string + source string + baseURL string + httpClient *http.Client +} + +// NewSMSService creates, inits and returns RouteMobile-backed SMS service. +func NewSMSService(settings model.SMSServiceSettings) (*SMSService, error) { + s := &SMSService{ + username: settings.Username, + password: settings.Password, + source: settings.Source, + httpClient: &http.Client{ + Timeout: 60 * time.Second, + }, + } + switch { + case settings.Region == model.RouteMobileRegionUAE: + s.baseURL = baseURLuae + default: + return nil, fmt.Errorf("Unknown RouteMobile region %s", settings.Region) + } + return s, nil +} + +// SendSMS sends SMS messages using RouteMobile service. +func (ss *SMSService) SendSMS(recipient, message string) error { + queryParams := fmt.Sprintf("username=%s&password=%s&type=0&dlr=0&destination=%s&source=%s&message=%s", ss.username, ss.password, strings.TrimPrefix(recipient, "+"), url.QueryEscape(ss.source), url.QueryEscape(message)) + url := fmt.Sprintf(ss.baseURL, fmt.Sprintf("/bulksms/bulksms?%s", queryParams)) + + resp, err := ss.httpClient.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + respString := string(respBytes) + + if resp.StatusCode >= 400 { + return fmt.Errorf("%s. %d", respString, resp.StatusCode) + } + if !strings.HasPrefix(respString, "1701") { + return fmt.Errorf("Error from RouteMobile API: '%s'. Please refer to the RouteMobile documentation", respString) + } + return nil +} diff --git a/model/server_settings.go b/model/server_settings.go index d660abb6..a1009657 100644 --- a/model/server_settings.go +++ b/model/server_settings.go @@ -180,13 +180,18 @@ type EmailServiceSettings struct { // SMSServiceSettings holds together settings for SMS service. type SMSServiceSettings struct { Type SMSServiceType `yaml:"type,omitempty" json:"type,omitempty"` - // Twilio related config + // Twilio related config. AccountSid string `yaml:"accountSid,omitempty" json:"account_sid,omitempty"` AuthToken string `yaml:"authToken,omitempty" json:"auth_token,omitempty"` ServiceSid string `yaml:"serviceSid,omitempty" json:"service_sid,omitempty"` - // Nexmo related config + // Nexmo related config. APIKey string `yaml:"apiKey,omitempty" json:"api_key,omitempty"` APISecret string `yaml:"apiSecret,omitempty" json:"api_secret,omitempty"` + // RouteMobile related config. + Username string `yaml:"username,omitempty" json:"username,omitempty"` + Password string `yaml:"password,omitempty" json:"password,omitempty"` + Source string `yaml:"source,omitempty" json:"source,omitempty"` + Region string `yaml:"region,omitempty" json:"region,omitempty"` } // SMSServiceType - service for sending sms messages. @@ -197,6 +202,8 @@ const ( SMSServiceTwilio SMSServiceType = "twilio" // SMSServiceNexmo is a Nexmo SMS service. SMSServiceNexmo SMSServiceType = "nexmo" + // SMSServiceRouteMobile is a RouteMobile SMS service. + SMSServiceRouteMobile SMSServiceType = "routemobile" // SMSServiceMock is an SMS service mock. SMSServiceMock SMSServiceType = "mock" ) diff --git a/model/server_settings_validation.go b/model/server_settings_validation.go index 4cef5fb8..0b97f2f6 100644 --- a/model/server_settings_validation.go +++ b/model/server_settings_validation.go @@ -271,6 +271,9 @@ func (ess *ExternalServicesSettings) Validate() error { return nil } +// RouteMobileRegionUAE is a regional UAE RouteMobileR platform. +const RouteMobileRegionUAE = "uae" + // Validate validates SMS service settings. func (sss *SMSServiceSettings) Validate() error { subject := "SMSServiceSettings" @@ -285,15 +288,23 @@ func (sss *SMSServiceSettings) Validate() error { case SMSServiceMock: return nil case SMSServiceNexmo: - if len(sss.APIKey)*len(sss.APISecret) == 0 { + if len(sss.APIKey) == 0 || len(sss.APISecret) == 0 { return fmt.Errorf("%s. Error creating Nexmo SMS service, missing at least one of the parameters:"+ "\n apiKey : %v\n apiSecret : %v\n", subject, sss.APIKey, sss.APISecret) } case SMSServiceTwilio: - if len(sss.AccountSid)*len(sss.AuthToken)*len(sss.ServiceSid) == 0 { + if len(sss.AccountSid) == 0 || len(sss.AuthToken) == 0 || len(sss.ServiceSid) == 0 { return fmt.Errorf("%s. Error creating Twilio SMS service, missing at least one of the parameters:"+ "\n sidKey : %v\n tokenKey : %v\n ServiceSidKey : %v\n", subject, sss.AccountSid, sss.AuthToken, sss.ServiceSid) } + case SMSServiceRouteMobile: + if len(sss.Username) == 0 || len(sss.Password) == 0 || len(sss.Source) == 0 { + return fmt.Errorf("%s. Error creating RouteMobile SMS service, missing at least one of the parameters:"+ + "\n username : %v\n password : %v\n", subject, sss.Username, sss.Password) + } + if sss.Region != RouteMobileRegionUAE { + return fmt.Errorf("%s. Error creating RouteMobile SMS service, region %s is not supported", subject, sss.Region) + } default: return fmt.Errorf("%s. Unknown type", subject) } diff --git a/server-config.yaml b/server-config.yaml index 9d96ef63..a302752d 100644 --- a/server-config.yaml +++ b/server-config.yaml @@ -114,9 +114,13 @@ externalServices: sender: # Sender of the emails. If "MAILGUN_SENDER" or "AWS_SES_SENDER" env variable is set, it overrides (depending on the email service type) the value specified here. region: # AWS SES-related setting. If "AWS_SES_REGION" env variable is set, it overrides the value specified here. smsService: # SMS service settings. - type: mock # Supported values are: "twilio", "mock". + type: mock # Supported values are: "twilio", "nexmo", "routemobile", "mock". accountSid: # Twilio-related setting. authToken: # Twilio-related setting. serviceSid: # Twilio-related setting. apiKey: # Nexmo-related setting. - apiSecret: # Nexmo-related setting. \ No newline at end of file + apiSecret: # Nexmo-related setting. + username: # RouteMobile-related setting. + password: # RouteMobile-related setting. + source: # RouteMobile-related setting. + region: # RouteMobile-related setting. Supported values are: uae. \ No newline at end of file diff --git a/server/server.go b/server/server.go index e459403f..a4e91ebd 100644 --- a/server/server.go +++ b/server/server.go @@ -15,6 +15,7 @@ import ( "github.com/madappgang/identifo/external_services/mail/ses" smsMock "github.com/madappgang/identifo/external_services/sms/mock" "github.com/madappgang/identifo/external_services/sms/nexmo" + "github.com/madappgang/identifo/external_services/sms/routemobile" "github.com/madappgang/identifo/external_services/sms/twilio" ijwt "github.com/madappgang/identifo/jwt" jwtService "github.com/madappgang/identifo/jwt/service" @@ -271,6 +272,8 @@ func initSMSService(settings model.SMSServiceSettings) (model.SMSService, error) return twilio.NewSMSService(settings) case model.SMSServiceNexmo: return nexmo.NewSMSService(settings) + case model.SMSServiceRouteMobile: + return routemobile.NewSMSService(settings) case model.SMSServiceMock: return smsMock.NewSMSService() }