Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.envrc
zero-notification-service
.history/
api/openapi.yaml
Expand Down
4 changes: 4 additions & 0 deletions .openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ internal/server/api_health.go
internal/server/api_health_service.go
internal/server/api_notification.go
internal/server/api_notification_service.go
internal/server/api_sms.go
internal/server/api_sms_service.go
internal/server/helpers.go
internal/server/impl.go
internal/server/model_email_recipient.go
Expand All @@ -20,6 +22,8 @@ internal/server/model_send_mail_request.go
internal/server/model_send_mail_response.go
internal/server/model_send_slack_message_request.go
internal/server/model_send_slack_message_response.go
internal/server/model_send_sms_request.go
internal/server/model_send_sms_response.go
internal/server/model_slack_message.go
internal/server/model_slack_recipient.go
internal/server/routers.go
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ A number of environment variables are available to configure the service at runt
| GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS | The number of seconds the application will continue servicing in-flight requests before the application stops after it receives an interrupt signal | 10 |
| STRUCTURED_LOGGING | If enabled, logs will be in JSON format, and only above INFO level | false |
| ALLOW_EMAIL_TO_DOMAINS | A comma separated list of domains. Only addresses in this list can have email sent to them. If empty, disable this "sandboxing" functionality. | |
| TWILIO_ACCOUNT_ID | The Account ID for Twilio | |
| TWILIO_AUTH_TOKEN | The Account Auth Token for Twilio | |
| TWILIO_PHONE_NUMBER | The Assigned Twilio Phone Number | |


### Releasing a new version on GitHub and Brew
Expand Down
44 changes: 44 additions & 0 deletions api/notification-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,33 @@ paths:
schema:
$ref: '#/components/schemas/Error'

/sms/send:
post:
summary: Send an SMS
operationId: sendSMS
tags:
- sms
requestBody:
description: Parameters of the message to send
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SendSMSRequest'
responses:
200:
description: OK
content:
application/json:
schema:
type: string
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'

/notification/slack/send:
post:
summary: Send a Slack message
Expand Down Expand Up @@ -278,6 +305,23 @@ components:
description: Schedule these mesages to go out at the time specified by this UNIX timestamp
format: int64

SendSMSRequest:
type: object
required:
- recipientPhoneNumber
- message
properties:
recipientPhoneNumber:
type: string
message:
type: string

SendSMSResponse:
type: object
properties:
message:
type: string

SlackRecipient:
type: object
required:
Expand Down
4 changes: 2 additions & 2 deletions charts/zero-notifcation-service/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.0.7
version: 0.0.8

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 0.0.10
appVersion: 0.0.11
2 changes: 2 additions & 0 deletions charts/zero-notifcation-service/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ type: Opaque
data:
SENDGRID_API_KEY: {{ .Values.application.sendgridApiKey | b64enc | quote }}
SLACK_API_KEY: {{ .Values.application.slackApiKey | b64enc | quote }}
TWILIO_ACCOUNT_ID: {{ .Values.application.twilioAccountID | b64enc | quote }}
TWILIO_AUTH_TOKEN: {{ .Values.application.twilioAuthToken | b64enc | quote }}
3 changes: 3 additions & 0 deletions charts/zero-notifcation-service/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ affinity: {}
application:
sendgridApiKey:
slackApiKey:
twilioAccountID:
twilioAuthToken:
twilioPhoneNumber:
gracefulShutdownTimeout: 10
structuredLogging: true
allowEmailToDomains:
5 changes: 4 additions & 1 deletion cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ func main() {
EmailApiService := service.NewEmailApiService(config)
EmailApiController := server.NewEmailApiController(EmailApiService)

SmsApiService := service.NewSmsApiService(config)
SmsApiController := server.NewSmsApiController(SmsApiService)

HealthApiService := service.NewHealthApiService(config)
HealthApiController := server.NewHealthApiController(HealthApiService)

NotificationApiService := service.NewNotificationApiService(config)
NotificationApiController := server.NewNotificationApiController(NotificationApiService)

router := server.Logger(server.NewRouter(EmailApiController, HealthApiController, NotificationApiController), "")
router := server.NewRouter(EmailApiController, SmsApiController, HealthApiController, NotificationApiController)

serverAddress := fmt.Sprintf("0.0.0.0:%d", config.Port)
server := &http.Server{Addr: serverAddress, Handler: router}
Expand Down
18 changes: 18 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ type Config struct {
Port int
SendgridAPIKey string
SlackAPIKey string
TwilioAccountID string
TwilioAuthToken string
TwilioPhoneNumber string
GracefulShutdownTimeout time.Duration
StructuredLogging bool
DebugDumpRequests bool
Expand All @@ -25,6 +28,9 @@ const (
Port
SendgridAPIKey
SlackAPIKey
TwilioAccountID
TwilioAuthToken
TwilioPhoneNumber
GracefulShutdownTimeout
StructuredLogging
DebugDumpRequests
Expand All @@ -49,6 +55,15 @@ func loadConfig() *Config {
viper.SetDefault(SlackAPIKey, "")
viper.BindEnv(SlackAPIKey, "SLACK_API_KEY")

viper.SetDefault(TwilioAccountID, "")
viper.BindEnv(TwilioAccountID, "TWILIO_ACCOUNT_ID")

viper.SetDefault(TwilioAuthToken, "")
viper.BindEnv(TwilioAuthToken, "TWILIO_AUTH_TOKEN")

viper.SetDefault(TwilioPhoneNumber, "")
viper.BindEnv(TwilioPhoneNumber, "TWILIO_PHONE_NUMBER")

viper.SetDefault(GracefulShutdownTimeout, "10")
viper.BindEnv(GracefulShutdownTimeout, "GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS")

Expand All @@ -74,6 +89,9 @@ func loadConfig() *Config {
Port: viper.GetInt(Port),
SendgridAPIKey: viper.GetString(SendgridAPIKey),
SlackAPIKey: viper.GetString(SlackAPIKey),
TwilioAccountID: viper.GetString(TwilioAccountID),
TwilioAuthToken: viper.GetString(TwilioAuthToken),
TwilioPhoneNumber: viper.GetString(TwilioPhoneNumber),
GracefulShutdownTimeout: viper.GetDuration(GracefulShutdownTimeout),
StructuredLogging: viper.GetBool(StructuredLogging),
DebugDumpRequests: viper.GetBool(DebugDumpRequests),
Expand Down
2 changes: 1 addition & 1 deletion internal/service/api_email_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (s *EmailApiService) SendBulk(ctx context.Context, sendBulkMailRequest serv
}
responseCode := http.StatusOK
if len(successful) == 0 {
responseCode = http.StatusInternalServerError
responseCode = http.StatusBadRequest
}
return server.Response(responseCode, server.SendBulkMailResponse{Successful: successful, Failed: failed}), nil
}
66 changes: 66 additions & 0 deletions internal/service/api_sms_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package service

import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"

"github.com/commitdev/zero-notification-service/internal/config"
"github.com/commitdev/zero-notification-service/internal/server"
"go.uber.org/zap"
)

// EmailApiService is a service that implents the logic for the EmailApiServicer
// This service should implement the business logic for every endpoint for the EmailApi API.
// Include any external packages or services that will be required by this service.
type SmsApiService struct {
config *config.Config
}

// NewSmsApiService creates a default api service
func NewSmsApiService(c *config.Config) server.SmsApiServicer {
return &SmsApiService{c}
}

// SendSMS - Send an email
func (s *SmsApiService) SendSMS(ctx context.Context, sendSMSRequest server.SendSmsRequest) (server.ImplResponse, error) {
// Set initial variables
accountSid := s.config.TwilioAccountID
authToken := s.config.TwilioAuthToken
urlStr := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", accountSid)

// Build out the data for our message
v := url.Values{}
v.Set("To", sendSMSRequest.RecipientPhoneNumber)
v.Set("From", s.config.TwilioPhoneNumber)
v.Set("Body", sendSMSRequest.Message)
rb := *strings.NewReader(v.Encode())

// Create Client
client := &http.Client{}

req, _ := http.NewRequest("POST", urlStr, &rb)
req.SetBasicAuth(accountSid, authToken)
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

// Make request
resp, err := client.Do(req)

// Convert request.body response to string
body, _ := ioutil.ReadAll(resp.Body)
bodyString := string(body)

if err != nil {
zap.S().Errorf("Error sending SMS: %v", err)
return server.Response(http.StatusInternalServerError, nil), fmt.Errorf("unable to send SMS: %v", err)
}
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
zap.S().Errorf("Failure from Twilio when sending SMS: %v\n", bodyString)
return server.Response(http.StatusBadRequest, server.SendSmsResponse{Message: fmt.Sprintf("Error sending SMS: %d\n, Message from SMS Provider: %v\n", resp.StatusCode, bodyString)}), nil
}
return server.Response(http.StatusOK, server.SendSmsResponse{Message: "SMS Sent"}), nil
}