fake-sms/internal/route/powow.go

230 lines
5.2 KiB
Go

package route
import (
"context"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"forge.cadoles.com/Cadoles/fake-sms/internal/command"
"forge.cadoles.com/Cadoles/fake-sms/internal/config"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/logger"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service"
)
type ErrorCode int
const (
ErrorCodeInvalidCommand ErrorCode = 99997
ErrorCodeAuthenticationFailure ErrorCode = 99998
ErrorCodeNotEnoughPrivileges ErrorCode = 99999
ErrorCodeMissingSMSID ErrorCode = 1
ErrorCodeMissingMobilePhoneNumber ErrorCode = 2
ErrorCodeInvalidSMSID ErrorCode = 3
ErrorCodeInvalidMobilePhoneNumber ErrorCode = 4
ErrorCodeInvalidCustomData ErrorCode = 5
ErrorCodeInvalidTimeToSend ErrorCode = 6
ErrorCodeAccountSubscribersLimitExceeded ErrorCode = 7
ErrorCodeMobilePhoneNumberCannotBeSaved ErrorCode = 7
ErrorCodeTransactionalIDCannotBeCreated ErrorCode = 9
ErrorCodeSMSSentLimitExceeded ErrorCode = 10
)
type Command string
const (
CommandTransactionalSMSSend = "TransactionalSms.Send"
)
type PowowRequest struct {
APIKey string `json:"ApiKey"`
Command Command
ResponseFormat string
Payload map[string]interface{}
}
type PowowResponse struct {
Success bool
ErrorCode ErrorCode
}
func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctn := container.Must(ctx)
conf := config.Must(ctn)
data, err := ioutil.ReadAll(r.Body)
if err != nil {
logger.Error(ctx, "could not read body", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
defer r.Body.Close()
pr := &PowowRequest{
Payload: make(map[string]interface{}),
}
if err := json.Unmarshal(data, pr); err != nil {
logger.Error(ctx, "could not parse request", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if err := json.Unmarshal(data, &pr.Payload); err != nil {
logger.Error(ctx, "could not parse request payload", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
// Authenticate user
if conf.Powow.APIKey != pr.APIKey {
res := &PowowResponse{
Success: false,
ErrorCode: ErrorCodeAuthenticationFailure,
}
sendPowowResponse(w, res)
return
}
// Handle Powow command
switch pr.Command {
case CommandTransactionalSMSSend:
handleTransactionalSMSSend(ctx, ctn, w, pr)
return
default:
res := &PowowResponse{
Success: false,
ErrorCode: ErrorCodeInvalidCommand,
}
sendPowowResponse(w, res)
return
}
}
// Mock https://powow4.iroquois.fr/user/docs/api/#send-transactional-sms
func handleTransactionalSMSSend(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) {
bus := cqrs.Must(ctn)
conf := config.Must(ctn)
rawSMSID, exists := req.Payload["SmsID"]
if !exists {
sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeMissingSMSID,
Success: false,
})
return
}
smsID, ok := rawSMSID.(float64)
if !ok {
sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeInvalidSMSID,
Success: false,
})
return
}
if smsID < 0 || int(smsID) > len(conf.Powow.SMS)-1 {
sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeInvalidSMSID,
Success: false,
})
return
}
customData := make(map[string]interface{})
rawCustomData, exists := req.Payload["CustomData"]
if exists {
customData, ok = rawCustomData.(map[string]interface{})
if !ok {
sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeInvalidCustomData,
Success: false,
})
return
}
}
sms := conf.Powow.SMS[int(smsID)]
body, err := createSMSBody(sms.Content, customData)
if err != nil {
panic(errors.Wrap(err, "could not generate sms body"))
}
req.Payload["_Template"] = sms
storeSMS := &command.StoreSMSRequest{
From: sms.From,
Body: body,
Recipient: req.Payload["MobilePhoneNumber"].(string),
Metadata: req.Payload,
}
_, err = bus.Exec(ctx, storeSMS)
if err != nil {
panic(errors.Wrap(err, "could not store sms"))
}
res := &struct {
PowowResponse
TransactionalID int
}{
PowowResponse: PowowResponse{
Success: true,
ErrorCode: 0,
},
TransactionalID: 0,
}
sendPowowResponse(w, res)
}
func createSMSBody(template string, customData map[string]interface{}) (string, error) {
content := template
for k, v := range customData {
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return "", errors.WithStack(err)
}
key := "%Subscriber:" + k + "%"
content = strings.ReplaceAll(content, key, string(decoded))
}
return content, nil
}
func sendPowowResponse(w http.ResponseWriter, res interface{}) {
w.Header().Add("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
if err := encoder.Encode(res); err != nil {
panic(errors.WithStack(err))
}
}