11 Commits

17 changed files with 358 additions and 71 deletions

View File

@ -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

View File

@ -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())

View File

@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{block "title" . -}}{{- end}}</title>
{{- block "head_style" . -}}
<link rel="stylesheet" href="{{ .BaseUrl }}/css/main.css" />
<link rel="stylesheet" href="{{ .BaseURL }}/css/main.css" />
{{end}}
{{- block "head_script" . -}}
<script type="text/javascript" src="{{ .BaseUrl }}/main.js"></script>
<script type="text/javascript" src="{{ .BaseURL }}/main.js"></script>
{{end}}
</head>
<body>

View File

@ -2,7 +2,7 @@
<div class="columns is-mobile">
<div class="column is-narrow">
<h1 class="is-size-3 title">
<a href="{{ .BaseUrl }}" rel="Inbox" class="has-text-grey-dark">
<a href="{{ .BaseURL }}/" rel="Inbox" class="has-text-grey-dark">
{{if or .Messages .SMS}}
📳
{{else}}

View File

@ -2,7 +2,7 @@
{{define "header_buttons"}}
<button
data-controller="restful"
data-restful-endpoint="{{ .BaseUrl }}/sms"
data-restful-endpoint="{{ .BaseURL }}/sms"
data-restful-method="DELETE"
class="button is-danger">
🗑️ Clear
@ -23,10 +23,11 @@
</tr>
</thead>
<tbody>
{{ $baseURL := .BaseURL }}
{{range .Messages}}
<tr data-controller="inbox-entry"
data-action="click->outbox-entry#onClick"
data-inbox-entry-link="{{ .BaseUrl }}/sms/{{ .ID }}">
data-inbox-entry-link="{{ $baseURL }}/sms/{{ .ID }}">
<td class="sms-from">
<span class="is-size-7">{{ .From }}</span>
</td>
@ -38,7 +39,7 @@
</td>
<td class="sms-actions">
<div class="buttons is-right">
<a href="{{ .BaseUrl }}/sms/{{ .ID }}" class="button is-small is-link">👁️ See</a>
<a href="{{ $baseURL }}/sms/{{ .ID }}" class="button is-small is-link">👁️ See</a>
</div>
</td>
</tr>

View File

@ -2,9 +2,9 @@
{{define "header_buttons"}}
<button class="button is-danger"
data-controller="restful"
data-restful-endpoint="{{ .BaseUrl }}/{{ .SMS.ID }}"
data-restful-endpoint="{{ .BaseURL }}/sms/{{ .SMS.ID }}"
data-restful-method="DELETE"
data-restful-redirect="{{ .BaseUrl }}">
data-restful-redirect="{{ .BaseURL }}/">
🗑️ Delete
</button>
{{end}}

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

14
debian/control vendored Normal file
View File

@ -0,0 +1,14 @@
Source: fake-sms
Section: unknown
Priority: optional
Maintainer: Cadoles <contact@cadoles.com>
Build-Depends: debhelper (>= 8.0.0), wget, ca-certificates, tar, curl
Standards-Version: 3.9.4
Homepage: http://forge.cadoles.com/Cadoles/fake-sms
Vcs-Git: http://forge.cadoles.com/Cadoles/fake-sms.git
Vcs-Browser: http://forge.cadoles.com/Cadoles/fake-sms
Package: fake-sms
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Serveur d'envoi de SMS factice pour le développement avec interface web

1
debian/fake-sms.dirs vendored Normal file
View File

@ -0,0 +1 @@
var/lib/fake-sms

11
debian/fake-sms.service vendored Normal file
View File

@ -0,0 +1,11 @@
[Unit]
Description=Serveur d'envoi de SMS factice pour le développement avec interface web
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/fake-sms -workdir /usr/share/fake-sms -config /etc/fake-sms/config.yml
Restart=on-failure
[Install]
WantedBy=multi-user.target

54
debian/rules vendored Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
export DH_VERBOSE=1
GO_VERSION := 1.15.6
OS := linux
ARCH := amd64
GOPATH=$(HOME)/go
ifeq (, $(shell which go 2>/dev/null))
override_dh_auto_build: install-go
endif
ifeq (, $(shell which node 2>/dev/null))
override_dh_auto_build: install-nodejs
endif
%:
dh $@ --with systemd
override_dh_auto_build: $(GOPATH)
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" make tooling
npm install
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" go mod vendor
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" ARCH_TARGETS=$(ARCH) make release
$(GOPATH):
mkdir -p $(GOPATH)
install-go:
wget https://dl.google.com/go/go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
tar -C /usr/local -xzf go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
install-nodejs:
curl -sL https://deb.nodesource.com/setup_14.x | bash -
apt-get install -y nodejs
override_dh_auto_install:
mkdir -p debian/fake-sms/usr/share/fake-sms
mkdir -p debian/fake-sms/etc/fake-sms
mkdir -p debian/fake-sms/usr/bin
cp -r release/fake-sms-$(OS)-$(ARCH)/* debian/fake-sms/usr/share/fake-sms/
mv debian/fake-sms/usr/share/fake-sms/bin/fake-sms debian/fake-sms/usr/bin/fake-sms
mv debian/fake-sms/usr/share/fake-sms/config.yml debian/fake-sms/etc/fake-sms/config.yml
install -d debian/fake-sms
override_dh_strip:
override_dh_auto_test:

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (native)

View File

@ -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...
`,
},
},
},
}
}

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/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
}

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
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": {