diff --git a/README.md b/README.md index c545146..80d4325 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,8 @@ powow: # Clé d'API à utiliser par les clients Powow utilisant le mock apiKey: powow - # Modèles de SMS transactionnels - # Voir https://powow4.iroquois.fr/user/docs/api/#create-transactional-sms - # 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 - + # La création/mise à jour de modèles de SMS s'effectue via les méthodes TransactionalSMS.Create et TransactionalSMS.Update. + # Voir le fichier ./misc/powow.http pour un exemple de requête. ``` ### Variables d'environnement diff --git a/cmd/fake-sms/container.go b/cmd/fake-sms/container.go index a6850a6..570c437 100644 --- a/cmd/fake-sms/container.go +++ b/cmd/fake-sms/container.go @@ -8,6 +8,8 @@ import ( "forge.cadoles.com/Cadoles/fake-sms/internal/command" "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/storm" "gitlab.com/wpetit/goweb/cqrs" @@ -40,6 +42,10 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) { ctn.Provide(storm.ServiceName, storm.ServiceProvider( storm.WithPath(conf.Data.Path), + storm.WithObjects( + &model.SMS{}, + &powow.SMSTemplate{}, + ), )) ctn.Provide(cqrs.ServiceName, cqrs.ServiceProvider()) diff --git a/internal/config/config.go b/internal/config/config.go index 79b4fc7..f2fb458 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,8 +28,7 @@ type DataConfig struct { } type PowowConfig struct { - APIKey string `yaml:"apiKey" env:"FAKESMS_POWOW_API_KEY"` - SMS []PowowSMS `yaml:"sms"` + APIKey string `yaml:"apiKey" env:"FAKESMS_POWOW_API_KEY"` } type PowowSMS struct { @@ -80,17 +79,6 @@ func NewDefault() *Config { }, Powow: PowowConfig{ APIKey: "powow", - SMS: []PowowSMS{ - { - Name: "Powow SMS", - From: "FakeSMS", - ShortLink: false, - Content: `Bonjour %Subscriber:Firstname%, - -Lorem ipsum dolor sit amet... -`, - }, - }, }, } } diff --git a/internal/model/powow/sms_template.go b/internal/model/powow/sms_template.go new file mode 100644 index 0000000..50e75d6 --- /dev/null +++ b/internal/model/powow/sms_template.go @@ -0,0 +1,10 @@ +package powow + +type SMSTemplate struct { + ID int `storm:"id,increment"` + SmsName string + FromName string + Content string + ShortLink int + Language string +} diff --git a/internal/route/powow.go b/internal/route/powow.go index 65ded0a..b4118df 100644 --- a/internal/route/powow.go +++ b/internal/route/powow.go @@ -10,6 +10,8 @@ import ( "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/pkg/errors" "gitlab.com/wpetit/goweb/cqrs" "gitlab.com/wpetit/goweb/logger" @@ -20,25 +22,32 @@ import ( 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 + 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" + CommandTransactionalSMSSend = "TransactionalSms.Send" + CommandTransactionalSMSCreate = "TransactionalSms.Create" + CommandTransactionalSMSUpdate = "TransactionalSms.Update" ) type PowowRequest struct { @@ -103,6 +112,14 @@ func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) { 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{ @@ -119,45 +136,52 @@ func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) { // 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) + db := storm.Must(ctn) - rawSMSID, exists := req.Payload["SmsID"] + smsID, exists, valid := getPowowSMSID(req) if !exists { sendPowowResponse(w, &PowowResponse{ - ErrorCode: ErrorCodeMissingSMSID, + ErrorCode: ErrorCodeTransactionSMSSendMissingSMSID, Success: false, }) return } - smsID, ok := rawSMSID.(float64) - if !ok { + if !valid { sendPowowResponse(w, &PowowResponse{ - ErrorCode: ErrorCodeInvalidSMSID, + ErrorCode: ErrorCodeTransactionSMSSendInvalidSMSID, Success: false, }) return } - if smsID < 0 || int(smsID) > len(conf.Powow.SMS)-1 { - sendPowowResponse(w, &PowowResponse{ - ErrorCode: ErrorCodeInvalidSMSID, - Success: false, - }) + smsTmpl := &powow.SMSTemplate{} - 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{}) + var ok bool + rawCustomData, exists := req.Payload["CustomData"] if exists { customData, ok = rawCustomData.(map[string]interface{}) if !ok { sendPowowResponse(w, &PowowResponse{ - ErrorCode: ErrorCodeInvalidCustomData, + ErrorCode: ErrorCodeTransactionSMSSendInvalidCustomData, 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(sms.Content, customData) + body, err := createSMSBody(smsTmpl.Content, customData) if err != nil { panic(errors.Wrap(err, "could not generate sms body")) } - req.Payload["_Template"] = sms + req.Payload["_Template"] = smsTmpl storeSMS := &command.StoreSMSRequest{ - From: sms.From, + From: smsTmpl.FromName, Body: body, Recipient: req.Payload["MobilePhoneNumber"].(string), Metadata: req.Payload, @@ -217,6 +239,143 @@ func createSMSBody(template string, customData map[string]interface{}) (string, 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{}) { w.Header().Add("Content-Type", "application/json") @@ -227,3 +386,17 @@ func sendPowowResponse(w http.ResponseWriter, res interface{}) { 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 +} diff --git a/internal/route/util.go b/internal/route/util.go new file mode 100644 index 0000000..1dbeb70 --- /dev/null +++ b/internal/route/util.go @@ -0,0 +1,11 @@ +package route + +func contains(search string, items ...string) bool { + for _, item := range items { + if item == search { + return true + } + } + + return false +} diff --git a/misc/powow.http b/misc/powow.http index cdd95b1..752b510 100644 --- a/misc/powow.http +++ b/misc/powow.http @@ -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 Content-Type: application/json @@ -5,7 +36,7 @@ Content-Type: application/json "APIKey": "powow", "Command": "TransactionalSms.Send", "ResponseFormat": "JSON", - "SmsID": 0, + "SmsID": {{SmsID}}, "MobilePhoneNumber": "+33699999999", "TimeToSend": "2017-01-01 10:00:00", "CustomData": {