Implements Powow TransactionalSMS.Create and TransactionalSMS.Update RPC

methods

See CNOUS/mse#1005
This commit is contained in:
wpetit 2021-03-01 15:18:10 +01:00
parent eb9f19eaa0
commit c419d6b79f
7 changed files with 267 additions and 63 deletions

View File

@ -40,23 +40,8 @@ powow:
# Clé d'API à utiliser par les clients Powow utilisant le mock # Clé d'API à utiliser par les clients Powow utilisant le mock
apiKey: powow apiKey: powow
# Modèles de SMS transactionnels # La création/mise à jour de modèles de SMS s'effectue via les méthodes TransactionalSMS.Create et TransactionalSMS.Update.
# Voir https://powow4.iroquois.fr/user/docs/api/#create-transactional-sms # Voir le fichier ./misc/powow.http pour un exemple de requête.
# et https://powow4.iroquois.fr/user/docs/api/#update-transactional-sms
#
# L'identifiant (SmsID) de chaque modèle est son index dans le tableau.
sms:
- name: Powow SMS
from: FakeSMS
# Modèle de contenu pour le SMS avec patrons d'insertion
# Voir https://powow4.iroquois.fr/user/docs/api/#send-transactional-sms, "About the CustomData parameter"
content: |
Bonjour %Subscriber:Firstname%,
Lorem ipsum dolor sit amet...
# Cet attribut n'est pas utilisé dans le cadre du mock
shortLink: false
``` ```
### Variables d'environnement ### Variables d'environnement

View File

@ -8,6 +8,8 @@ import (
"forge.cadoles.com/Cadoles/fake-sms/internal/command" "forge.cadoles.com/Cadoles/fake-sms/internal/command"
"forge.cadoles.com/Cadoles/fake-sms/internal/config" "forge.cadoles.com/Cadoles/fake-sms/internal/config"
"forge.cadoles.com/Cadoles/fake-sms/internal/model"
"forge.cadoles.com/Cadoles/fake-sms/internal/model/powow"
"forge.cadoles.com/Cadoles/fake-sms/internal/query" "forge.cadoles.com/Cadoles/fake-sms/internal/query"
"forge.cadoles.com/Cadoles/fake-sms/internal/storm" "forge.cadoles.com/Cadoles/fake-sms/internal/storm"
"gitlab.com/wpetit/goweb/cqrs" "gitlab.com/wpetit/goweb/cqrs"
@ -40,6 +42,10 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
ctn.Provide(storm.ServiceName, storm.ServiceProvider( ctn.Provide(storm.ServiceName, storm.ServiceProvider(
storm.WithPath(conf.Data.Path), storm.WithPath(conf.Data.Path),
storm.WithObjects(
&model.SMS{},
&powow.SMSTemplate{},
),
)) ))
ctn.Provide(cqrs.ServiceName, cqrs.ServiceProvider()) ctn.Provide(cqrs.ServiceName, cqrs.ServiceProvider())

View File

@ -28,8 +28,7 @@ type DataConfig struct {
} }
type PowowConfig struct { type PowowConfig struct {
APIKey string `yaml:"apiKey" env:"FAKESMS_POWOW_API_KEY"` APIKey string `yaml:"apiKey" env:"FAKESMS_POWOW_API_KEY"`
SMS []PowowSMS `yaml:"sms"`
} }
type PowowSMS struct { type PowowSMS struct {
@ -80,17 +79,6 @@ func NewDefault() *Config {
}, },
Powow: PowowConfig{ Powow: PowowConfig{
APIKey: "powow", APIKey: "powow",
SMS: []PowowSMS{
{
Name: "Powow SMS",
From: "FakeSMS",
ShortLink: false,
Content: `Bonjour %Subscriber:Firstname%,
Lorem ipsum dolor sit amet...
`,
},
},
}, },
} }
} }

View File

@ -0,0 +1,10 @@
package powow
type SMSTemplate struct {
ID int `storm:"id,increment"`
SmsName string
FromName string
Content string
ShortLink int
Language string
}

View File

@ -10,6 +10,8 @@ import (
"forge.cadoles.com/Cadoles/fake-sms/internal/command" "forge.cadoles.com/Cadoles/fake-sms/internal/command"
"forge.cadoles.com/Cadoles/fake-sms/internal/config" "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/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/cqrs" "gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
@ -20,25 +22,32 @@ import (
type ErrorCode int type ErrorCode int
const ( const (
ErrorCodeInvalidCommand ErrorCode = 99997 ErrorCodeInvalidCommand ErrorCode = 99997
ErrorCodeAuthenticationFailure ErrorCode = 99998 ErrorCodeAuthenticationFailure ErrorCode = 99998
ErrorCodeNotEnoughPrivileges ErrorCode = 99999 ErrorCodeNotEnoughPrivileges ErrorCode = 99999
ErrorCodeMissingSMSID ErrorCode = 1 ErrorCodeTransactionSMSSendMissingSMSID ErrorCode = 1
ErrorCodeMissingMobilePhoneNumber ErrorCode = 2 ErrorCodeTransactionSMSSendMissingMobilePhoneNumber ErrorCode = 2
ErrorCodeInvalidSMSID ErrorCode = 3 ErrorCodeTransactionSMSSendInvalidSMSID ErrorCode = 3
ErrorCodeInvalidMobilePhoneNumber ErrorCode = 4 ErrorCodeTransactionSMSSendInvalidMobilePhoneNumber ErrorCode = 4
ErrorCodeInvalidCustomData ErrorCode = 5 ErrorCodeTransactionSMSSendInvalidCustomData ErrorCode = 5
ErrorCodeInvalidTimeToSend ErrorCode = 6 ErrorCodeTransactionSMSSendInvalidTimeToSend ErrorCode = 6
ErrorCodeAccountSubscribersLimitExceeded ErrorCode = 7 ErrorCodeTransactionSMSSendAccountSubscribersLimitExceeded ErrorCode = 7
ErrorCodeMobilePhoneNumberCannotBeSaved ErrorCode = 7 ErrorCodeTransactionSMSSendMobilePhoneNumberCannotBeSaved ErrorCode = 7
ErrorCodeTransactionalIDCannotBeCreated ErrorCode = 9 ErrorCodeTransactionSMSSendTransactionalIDCannotBeCreated ErrorCode = 9
ErrorCodeSMSSentLimitExceeded ErrorCode = 10 ErrorCodeTransactionSMSSendSMSSentLimitExceeded ErrorCode = 10
ErrorCodeTransactionSMSUpdateMissingSMSID ErrorCode = 1
ErrorCodeTransactionSMSUpdateInvalidSMSID ErrorCode = 3
ErrorCodeTransactionSMSUpdateInvalidFromName ErrorCode = 4
ErrorCodeTransactionSMSUpdateInvalidLanguage ErrorCode = 7
) )
type Command string type Command string
const ( const (
CommandTransactionalSMSSend = "TransactionalSms.Send" CommandTransactionalSMSSend = "TransactionalSms.Send"
CommandTransactionalSMSCreate = "TransactionalSms.Create"
CommandTransactionalSMSUpdate = "TransactionalSms.Update"
) )
type PowowRequest struct { type PowowRequest struct {
@ -103,6 +112,14 @@ func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) {
case CommandTransactionalSMSSend: case CommandTransactionalSMSSend:
handleTransactionalSMSSend(ctx, ctn, w, pr) handleTransactionalSMSSend(ctx, ctn, w, pr)
return
case CommandTransactionalSMSCreate:
handleTransactionalSMSCreate(ctx, ctn, w, pr)
return
case CommandTransactionalSMSUpdate:
handleTransactionalSMSUpdate(ctx, ctn, w, pr)
return return
default: default:
res := &PowowResponse{ res := &PowowResponse{
@ -119,45 +136,52 @@ func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) {
// Mock https://powow4.iroquois.fr/user/docs/api/#send-transactional-sms // Mock https://powow4.iroquois.fr/user/docs/api/#send-transactional-sms
func handleTransactionalSMSSend(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) { func handleTransactionalSMSSend(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) {
bus := cqrs.Must(ctn) bus := cqrs.Must(ctn)
conf := config.Must(ctn) db := storm.Must(ctn)
rawSMSID, exists := req.Payload["SmsID"] smsID, exists, valid := getPowowSMSID(req)
if !exists { if !exists {
sendPowowResponse(w, &PowowResponse{ sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeMissingSMSID, ErrorCode: ErrorCodeTransactionSMSSendMissingSMSID,
Success: false, Success: false,
}) })
return return
} }
smsID, ok := rawSMSID.(float64) if !valid {
if !ok {
sendPowowResponse(w, &PowowResponse{ sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeInvalidSMSID, ErrorCode: ErrorCodeTransactionSMSSendInvalidSMSID,
Success: false, Success: false,
}) })
return return
} }
if smsID < 0 || int(smsID) > len(conf.Powow.SMS)-1 { smsTmpl := &powow.SMSTemplate{}
sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeInvalidSMSID,
Success: false,
})
return if err := db.One("ID", smsID, smsTmpl); err != nil {
if errors.Is(err, storm.ErrNotFound) {
sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeTransactionSMSSendInvalidSMSID,
Success: false,
})
return
}
panic(errors.Wrap(err, "could not retrieve sms template"))
} }
customData := make(map[string]interface{}) customData := make(map[string]interface{})
var ok bool
rawCustomData, exists := req.Payload["CustomData"] rawCustomData, exists := req.Payload["CustomData"]
if exists { if exists {
customData, ok = rawCustomData.(map[string]interface{}) customData, ok = rawCustomData.(map[string]interface{})
if !ok { if !ok {
sendPowowResponse(w, &PowowResponse{ sendPowowResponse(w, &PowowResponse{
ErrorCode: ErrorCodeInvalidCustomData, ErrorCode: ErrorCodeTransactionSMSSendInvalidCustomData,
Success: false, Success: false,
}) })
@ -165,17 +189,15 @@ func handleTransactionalSMSSend(ctx context.Context, ctn *service.Container, w h
} }
} }
sms := conf.Powow.SMS[int(smsID)] body, err := createSMSBody(smsTmpl.Content, customData)
body, err := createSMSBody(sms.Content, customData)
if err != nil { if err != nil {
panic(errors.Wrap(err, "could not generate sms body")) panic(errors.Wrap(err, "could not generate sms body"))
} }
req.Payload["_Template"] = sms req.Payload["_Template"] = smsTmpl
storeSMS := &command.StoreSMSRequest{ storeSMS := &command.StoreSMSRequest{
From: sms.From, From: smsTmpl.FromName,
Body: body, Body: body,
Recipient: req.Payload["MobilePhoneNumber"].(string), Recipient: req.Payload["MobilePhoneNumber"].(string),
Metadata: req.Payload, Metadata: req.Payload,
@ -217,6 +239,143 @@ func createSMSBody(template string, customData map[string]interface{}) (string,
return content, nil 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 {
PowowResponse
SmsID int
}{
PowowResponse: PowowResponse{
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: ErrorCodeTransactionSMSUpdateMissingSMSID,
Success: false,
})
return
}
if !valid {
sendPowowResponse(w, &PowowResponse{
ErrorCode: 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: 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: 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, &PowowResponse{
ErrorCode: 0,
Success: true,
})
}
func sendPowowResponse(w http.ResponseWriter, res interface{}) { func sendPowowResponse(w http.ResponseWriter, res interface{}) {
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")
@ -227,3 +386,17 @@ func sendPowowResponse(w http.ResponseWriter, res interface{}) {
panic(errors.WithStack(err)) 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
}

11
internal/route/util.go Normal file
View File

@ -0,0 +1,11 @@
package route
func contains(search string, items ...string) bool {
for _, item := range items {
if item == search {
return true
}
}
return false
}

View File

@ -1,3 +1,34 @@
### Create transactional SMS template
# @name createSms
POST http://localhost:3000/api/v1/mock/powow
Content-Type: application/json
{
"APIKey": "powow",
"Command": "TransactionalSms.Create"
}
### Update transactional SMS template
@SmsID = {{createSms.response.body.$.SmsID}}
POST http://localhost:3000/api/v1/mock/powow
Content-Type: application/json
{
"APIKey": "powow",
"Command": "TransactionalSms.Update",
"SmsID": {{SmsID}},
"SmsName": "Defaut SMS",
"FromName": "FakeSMS",
"Content": "Bonjour %Subscriber:Firstname%,\nLorem ipsum dolor sit amet...",
"ShortLink": 0,
"Language": "fr"
}
### Send transactional SMS
POST http://localhost:3000/api/v1/mock/powow POST http://localhost:3000/api/v1/mock/powow
Content-Type: application/json Content-Type: application/json
@ -5,7 +36,7 @@ Content-Type: application/json
"APIKey": "powow", "APIKey": "powow",
"Command": "TransactionalSms.Send", "Command": "TransactionalSms.Send",
"ResponseFormat": "JSON", "ResponseFormat": "JSON",
"SmsID": 0, "SmsID": {{SmsID}},
"MobilePhoneNumber": "+33699999999", "MobilePhoneNumber": "+33699999999",
"TimeToSend": "2017-01-01 10:00:00", "TimeToSend": "2017-01-01 10:00:00",
"CustomData": { "CustomData": {