451 lines
11 KiB
Go
451 lines
11 KiB
Go
package route
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"forge.cadoles.com/Cadoles/fake-sms/internal/command"
|
|
"forge.cadoles.com/Cadoles/fake-sms/internal/config"
|
|
"forge.cadoles.com/Cadoles/fake-sms/internal/model/powow"
|
|
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
|
"github.com/davecgh/go-spew/spew"
|
|
php "github.com/kovetskiy/go-php-serialize"
|
|
"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
|
|
ErrorCodeTransactionSMSSendMissingSMSID ErrorCode = 1
|
|
ErrorCodeTransactionSMSSendMissingMobilePhoneNumber ErrorCode = 2
|
|
ErrorCodeTransactionSMSSendInvalidSMSID ErrorCode = 3
|
|
ErrorCodeTransactionSMSSendInvalidMobilePhoneNumber ErrorCode = 4
|
|
ErrorCodeTransactionSMSSendInvalidCustomData ErrorCode = 5
|
|
ErrorCodeTransactionSMSSendInvalidTimeToSend ErrorCode = 6
|
|
ErrorCodeTransactionSMSSendAccountSubscribersLimitExceeded ErrorCode = 7
|
|
ErrorCodeTransactionSMSSendMobilePhoneNumberCannotBeSaved ErrorCode = 7
|
|
ErrorCodeTransactionSMSSendTransactionalIDCannotBeCreated ErrorCode = 9
|
|
ErrorCodeTransactionSMSSendSMSSentLimitExceeded ErrorCode = 10
|
|
|
|
ErrorCodeTransactionSMSUpdateMissingSMSID ErrorCode = 1
|
|
ErrorCodeTransactionSMSUpdateInvalidSMSID ErrorCode = 3
|
|
ErrorCodeTransactionSMSUpdateInvalidFromName ErrorCode = 4
|
|
ErrorCodeTransactionSMSUpdateInvalidLanguage ErrorCode = 7
|
|
)
|
|
|
|
type Command string
|
|
|
|
const (
|
|
CommandTransactionalSMSSend = "TransactionalSms.Send"
|
|
CommandTransactionalSMSCreate = "TransactionalSms.Create"
|
|
CommandTransactionalSMSUpdate = "TransactionalSms.Update"
|
|
)
|
|
|
|
type PowowRequest struct {
|
|
APIKey string `json:"ApiKey"`
|
|
Command Command
|
|
ResponseFormat string
|
|
Payload map[string]interface{}
|
|
}
|
|
|
|
type PowowResponse struct {
|
|
Success bool
|
|
ErrorCode map[int]ErrorCode
|
|
}
|
|
|
|
type PowowResponseSuccess struct {
|
|
Success bool
|
|
ErrorCode ErrorCode
|
|
}
|
|
|
|
func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
ctn := container.Must(ctx)
|
|
conf := config.Must(ctn)
|
|
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
logger.Error(ctx, "could not parse form", logger.E(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
}
|
|
|
|
pr := &PowowRequest{
|
|
Payload: make(map[string]interface{}),
|
|
}
|
|
|
|
pr.APIKey = r.FormValue("APIKey")
|
|
pr.Command = Command(r.FormValue("Command"))
|
|
pr.ResponseFormat = r.FormValue("ResponseFormat")
|
|
|
|
if pr.Command == CommandTransactionalSMSUpdate {
|
|
smsid, err := strconv.ParseFloat(r.FormValue("SmsID"), 64)
|
|
if err != nil {
|
|
logger.Error(ctx, "could not convert SmsID to float", logger.E(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
}
|
|
pr.Payload = map[string]interface{}{
|
|
"SmsID": smsid,
|
|
"Content": r.FormValue("Content"),
|
|
"ShortLink": r.FormValue("ShortLink"),
|
|
"Language": r.FormValue("Language"),
|
|
"FromName": r.FormValue("FromName"),
|
|
"SmsName": r.FormValue("SmsName"),
|
|
}
|
|
}
|
|
|
|
if pr.Command == CommandTransactionalSMSSend {
|
|
customData, err := php.Decode(r.FormValue("CustomData"))
|
|
if err != nil {
|
|
logger.Error(ctx, "could not cunserialized custom data", logger.E(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
}
|
|
var smsid float64
|
|
if r.FormValue("SmsID") != "" {
|
|
smsid, err = strconv.ParseFloat(r.FormValue("SmsID"), 64)
|
|
if err != nil {
|
|
logger.Error(ctx, "could not convert SmsID to float", logger.E(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
pr.Payload = map[string]interface{}{
|
|
"SmsID": smsid,
|
|
"MobilePhoneNumber": strings.TrimSpace(r.FormValue("MobilePhoneNumber")),
|
|
"TimeToSend": r.FormValue("TimeToSend"),
|
|
"CustomData": customData,
|
|
}
|
|
}
|
|
|
|
// Authenticate user
|
|
if conf.Powow.APIKey != pr.APIKey {
|
|
res := &PowowResponse{
|
|
Success: false,
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeAuthenticationFailure},
|
|
}
|
|
|
|
sendPowowResponse(w, res)
|
|
|
|
return
|
|
}
|
|
|
|
// If phone number must return an error
|
|
spew.Dump(conf.Powow.InvalidPhoneNumber)
|
|
spew.Dump(pr.Payload["MobilePhoneNumber"])
|
|
if conf.Powow.InvalidPhoneNumber == pr.Payload["MobilePhoneNumber"] {
|
|
res := &PowowResponse{
|
|
Success: false,
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendInvalidMobilePhoneNumber},
|
|
}
|
|
|
|
sendPowowResponse(w, res)
|
|
|
|
return
|
|
}
|
|
|
|
// Handle Powow command
|
|
switch pr.Command {
|
|
case CommandTransactionalSMSSend:
|
|
handleTransactionalSMSSend(ctx, ctn, w, pr)
|
|
|
|
return
|
|
case CommandTransactionalSMSCreate:
|
|
handleTransactionalSMSCreate(ctx, ctn, w, pr)
|
|
|
|
return
|
|
case CommandTransactionalSMSUpdate:
|
|
handleTransactionalSMSUpdate(ctx, ctn, w, pr)
|
|
|
|
return
|
|
default:
|
|
res := &PowowResponse{
|
|
Success: false,
|
|
ErrorCode: map[int]ErrorCode{0: 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)
|
|
db := storm.Must(ctn)
|
|
|
|
smsID, exists, valid := getPowowSMSID(req)
|
|
if !exists {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendMissingSMSID},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if !valid {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendInvalidSMSID},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
smsTmpl := &powow.SMSTemplate{}
|
|
|
|
if err := db.One("ID", smsID, smsTmpl); err != nil {
|
|
if errors.Is(err, storm.ErrNotFound) {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendInvalidSMSID},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
panic(errors.Wrap(err, "could not retrieve sms template"))
|
|
}
|
|
|
|
customData := make(map[string]interface{})
|
|
|
|
rawCustomData, exists := req.Payload["CustomData"]
|
|
if exists {
|
|
cData, ok := rawCustomData.(map[interface{}]interface{})
|
|
if !ok {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendInvalidCustomData},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
for k, v := range cData {
|
|
customData[k.(string)] = v
|
|
}
|
|
req.Payload["CustomData"] = customData
|
|
}
|
|
|
|
body, err := createSMSBody(smsTmpl.Content, customData)
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "could not generate sms body"))
|
|
}
|
|
|
|
req.Payload["_Template"] = smsTmpl
|
|
|
|
storeSMS := &command.StoreSMSRequest{
|
|
From: smsTmpl.FromName,
|
|
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 {
|
|
PowowResponseSuccess
|
|
TransactionalID int
|
|
}{
|
|
PowowResponseSuccess: PowowResponseSuccess{
|
|
Success: true,
|
|
ErrorCode: 0,
|
|
},
|
|
TransactionalID: 0,
|
|
}
|
|
|
|
sendPowowResponse(w, res)
|
|
}
|
|
|
|
func createSMSBody(template string, customData map[string]interface{}) (string, error) {
|
|
content := template
|
|
spew.Dump(customData)
|
|
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
|
|
}
|
|
|
|
// Mock https://powow4.iroquois.fr/user/docs/api/#create-transactional-sms
|
|
func handleTransactionalSMSCreate(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) {
|
|
db := storm.Must(ctn)
|
|
|
|
smsTemplate := &powow.SMSTemplate{}
|
|
|
|
if err := db.Save(smsTemplate); err != nil {
|
|
panic(errors.Wrap(err, "could not save sms template"))
|
|
}
|
|
|
|
res := &struct {
|
|
PowowResponseSuccess
|
|
SmsID int
|
|
}{
|
|
PowowResponseSuccess: PowowResponseSuccess{
|
|
Success: true,
|
|
ErrorCode: 0,
|
|
},
|
|
SmsID: smsTemplate.ID,
|
|
}
|
|
|
|
sendPowowResponse(w, res)
|
|
}
|
|
|
|
// Mock https://powow4.iroquois.fr/user/docs/api/#update-transactional-sms
|
|
func handleTransactionalSMSUpdate(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) {
|
|
smsID, exists, valid := getPowowSMSID(req)
|
|
if !exists {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSUpdateMissingSMSID},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if !valid {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSUpdateInvalidSMSID},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
db := storm.Must(ctn)
|
|
|
|
smsTmpl := &powow.SMSTemplate{}
|
|
|
|
if err := db.One("ID", smsID, smsTmpl); err != nil {
|
|
if errors.Is(err, storm.ErrNotFound) {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSUpdateInvalidSMSID},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
panic(errors.Wrap(err, "could not retrieve sms template"))
|
|
}
|
|
|
|
rawContent, exists := req.Payload["Content"]
|
|
if exists {
|
|
content, ok := rawContent.(string)
|
|
if ok {
|
|
smsTmpl.Content = content
|
|
}
|
|
}
|
|
|
|
rawLanguage, exists := req.Payload["Language"]
|
|
if exists {
|
|
language, ok := rawLanguage.(string)
|
|
if ok {
|
|
if !contains(language, "en", "fr", "it", "es", "de", "pt", "pl", "zh") {
|
|
sendPowowResponse(w, &PowowResponse{
|
|
ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSUpdateInvalidLanguage},
|
|
Success: false,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
smsTmpl.Language = language
|
|
}
|
|
}
|
|
|
|
rawFromName, exists := req.Payload["FromName"]
|
|
if exists {
|
|
fromName, ok := rawFromName.(string)
|
|
if ok {
|
|
smsTmpl.FromName = fromName
|
|
}
|
|
}
|
|
|
|
rawShortLink, exists := req.Payload["ShortLink"]
|
|
if exists {
|
|
shortLink, ok := rawShortLink.(float64)
|
|
if ok {
|
|
if shortLink == 1.0 {
|
|
smsTmpl.ShortLink = 1
|
|
} else {
|
|
smsTmpl.ShortLink = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
rawSMSName, exists := req.Payload["SmsName"]
|
|
if exists {
|
|
smsName, ok := rawSMSName.(string)
|
|
if ok {
|
|
smsTmpl.SmsName = smsName
|
|
}
|
|
}
|
|
|
|
rawShortLink, exists = req.Payload["ShortLink"]
|
|
if exists {
|
|
shortLink, ok := rawShortLink.(float64)
|
|
if ok {
|
|
if shortLink == 1.0 {
|
|
smsTmpl.ShortLink = 1
|
|
} else {
|
|
smsTmpl.ShortLink = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := db.Save(smsTmpl); err != nil {
|
|
panic(errors.Wrap(err, "could not save sms template"))
|
|
}
|
|
|
|
sendPowowResponse(w, &PowowResponseSuccess{
|
|
ErrorCode: 0,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
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))
|
|
}
|
|
}
|
|
|
|
func getPowowSMSID(req *PowowRequest) (smsID int, exists bool, valid bool) {
|
|
rawSMSID, exists := req.Payload["SmsID"]
|
|
if !exists {
|
|
return -1, false, false
|
|
}
|
|
|
|
smsIDFloat, ok := rawSMSID.(float64)
|
|
if !ok {
|
|
return -1, true, false
|
|
}
|
|
|
|
return int(smsIDFloat), true, true
|
|
}
|