Add Iroquois/Powow API mocking entrypoint
This commit is contained in:
parent
d9a6c14041
commit
9b90eaf240
|
@ -4,3 +4,4 @@
|
||||||
/bin
|
/bin
|
||||||
/node_modules
|
/node_modules
|
||||||
/.env
|
/.env
|
||||||
|
/.vscode
|
41
README.md
41
README.md
|
@ -29,13 +29,36 @@ Voici la structure du fichier par défaut:
|
||||||
```yaml
|
```yaml
|
||||||
# Configuration HTTP
|
# Configuration HTTP
|
||||||
http:
|
http:
|
||||||
address: :8080
|
address: :3000
|
||||||
templateDir: template
|
templateDir: template
|
||||||
publicDir: public
|
publicDir: public
|
||||||
|
|
||||||
# Configuration du stockage
|
# Configuration du stockage
|
||||||
data:
|
data:
|
||||||
path: fakesms.db
|
path: fakesms.db
|
||||||
|
|
||||||
|
# Confirguration du mock Powow
|
||||||
|
# Voir https://powow4.iroquois.fr/
|
||||||
|
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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Variables d'environnement
|
### Variables d'environnement
|
||||||
|
@ -50,6 +73,20 @@ Les valeurs des variables d'environnement surchargent les valeurs présentes dan
|
||||||
|`FAKESMS_HTTP_TEMPLATEDIR`|`http.templateDir`|
|
|`FAKESMS_HTTP_TEMPLATEDIR`|`http.templateDir`|
|
||||||
|`FAKESMS_HTTP_PUBLICDIR`|`http.publicDir`|
|
|`FAKESMS_HTTP_PUBLICDIR`|`http.publicDir`|
|
||||||
|
|
||||||
|
## Mocks
|
||||||
|
|
||||||
|
### Iroquois/Powow
|
||||||
|
|
||||||
|
Un mock de l'API Powow est disponible via l'URL `http://<fake_sms_host>/api/v1/mock/powow`.
|
||||||
|
|
||||||
|
Les commandes suivantes sont implémentées:
|
||||||
|
|
||||||
|
|Commande|Documentation officiele|Notes|
|
||||||
|
|--------|-----------------------|-----------|
|
||||||
|
|`TransactionalSms.Send`|https://powow4.iroquois.fr/user/docs/api/#send-transactional-sms|La définition des modèles de SMS s'effectue via la configuration.|
|
||||||
|
|
||||||
|
Un exemple d'appel à l'API est disponible dans le fichier [`misc/powow.http`](./misc/powow.http).
|
||||||
|
|
||||||
## Démarrer avec les sources
|
## Démarrer avec les sources
|
||||||
|
|
||||||
### Dépendances
|
### Dépendances
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/template/html"
|
"gitlab.com/wpetit/goweb/template/html"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/fake-sms/internal/command"
|
"forge.cadoles.com/Cadoles/fake-sms/internal/command"
|
||||||
|
@ -22,6 +25,14 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
|
||||||
// Create and expose template service provider
|
// Create and expose template service provider
|
||||||
ctn.Provide(template.ServiceName, html.ServiceProvider(
|
ctn.Provide(template.ServiceName, html.ServiceProvider(
|
||||||
html.NewDirectoryLoader(conf.HTTP.TemplateDir),
|
html.NewDirectoryLoader(conf.HTTP.TemplateDir),
|
||||||
|
html.WithHelper("toPrettyJSON", func(data interface{}) (string, error) {
|
||||||
|
json, err := json.MarshalIndent(data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(json), nil
|
||||||
|
}),
|
||||||
))
|
))
|
||||||
|
|
||||||
// Create and expose config service provider
|
// Create and expose config service provider
|
||||||
|
|
|
@ -16,8 +16,9 @@
|
||||||
<table class="outbox table is-fullwidth is-striped is-hoverable">
|
<table class="outbox table is-fullwidth is-striped is-hoverable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="sms-to">Recipient</th>
|
<th class="sms-from">From</th>
|
||||||
<th class="sms-sentat">Date</th>
|
<th class="sms-recipient">Recipient</th>
|
||||||
|
<th class="sms-sentat">Sent At</th>
|
||||||
<th class="sms-actions"></th>
|
<th class="sms-actions"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -26,10 +27,11 @@
|
||||||
<tr data-controller="inbox-entry"
|
<tr data-controller="inbox-entry"
|
||||||
data-action="click->outbox-entry#onClick"
|
data-action="click->outbox-entry#onClick"
|
||||||
data-inbox-entry-link="./sms/{{ .ID }}">
|
data-inbox-entry-link="./sms/{{ .ID }}">
|
||||||
|
<td class="sms-from">
|
||||||
|
<span class="is-size-7">{{ .From }}</span>
|
||||||
|
</td>
|
||||||
<td class="sms-recipient">
|
<td class="sms-recipient">
|
||||||
{{range .Recipient}}
|
<span class="tag">{{ .Recipient }}</span>
|
||||||
<span class="tag">{{ .Recipient }}</span>
|
|
||||||
{{end}}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="sms-sentat">
|
<td class="sms-sentat">
|
||||||
<span class="is-size-7">{{ .SentAt.Format "02/01/2006 15:04:05"}}</span>
|
<span class="is-size-7">{{ .SentAt.Format "02/01/2006 15:04:05"}}</span>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{{define "title"}}Email - FakeSMTP{{end}}
|
{{define "title"}}SMS - FakeSMTP{{end}}
|
||||||
{{define "header_buttons"}}
|
{{define "header_buttons"}}
|
||||||
<button class="button is-danger"
|
<button class="button is-danger"
|
||||||
data-controller="restful"
|
data-controller="restful"
|
||||||
data-restful-endpoint="./{{ .Email.ID }}"
|
data-restful-endpoint="./{{ .SMS.ID }}"
|
||||||
data-restful-method="DELETE"
|
data-restful-method="DELETE"
|
||||||
data-restful-redirect="../">
|
data-restful-redirect="../">
|
||||||
🗑️ Delete
|
🗑️ Delete
|
||||||
|
@ -16,40 +16,21 @@
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h4 class="title is-size-4">Email</h4>
|
<h4 class="title is-size-4">SMS</h4>
|
||||||
{{template "email_head" .}}
|
{{template "sms_head" .}}
|
||||||
</div>
|
</div>
|
||||||
{{if .Email.Attachments}}
|
|
||||||
<div class="column is-narrow">
|
|
||||||
<h4 class="title is-size-4">Attachments ({{len .Email.Attachments}})</h4>
|
|
||||||
<ul>
|
|
||||||
{{ $email := .Email }}
|
|
||||||
{{range $i, $a := .Email.Attachments}}
|
|
||||||
<li><a href="{{ $email.ID }}/attachments/{{ $i }}" download="{{ $a.Name }}">{{ $a.Name }}</a></li>
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
<div data-controller="tabs">
|
<div data-controller="tabs">
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
<li data-action="click->tabs#openTab" data-target="tabs.tab" data-tabs-name="html" {{if .Email.HTML}}class="is-active"{{end}}><a>HTML</a></li>
|
<li data-action="click->tabs#openTab" data-target="tabs.tab" data-tabs-name="text" class="is-active"><a>Body</a></li>
|
||||||
<li data-action="click->tabs#openTab" data-target="tabs.tab" data-tabs-name="text" {{if not .Email.HTML}}class="is-active"{{end}}><a>Text</a></li>
|
<li data-action="click->tabs#openTab" data-target="tabs.tab" data-tabs-name="metadata"><a>Metadata</a></li>
|
||||||
<li data-action="click->tabs#openTab" data-target="tabs.tab" data-tabs-name="headers"><a>Headers</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<iframe data-target="tabs.tabContent" data-tabs-for="html"
|
<div data-target="tabs.tabContent" data-tabs-for="text" style="width:100%;overflow:hidden;">
|
||||||
frameborder="0"
|
<pre style="white-space:pre-line;">{{ .SMS.Body }}</pre>
|
||||||
data-controller="iframe"
|
|
||||||
data-action="load->iframe#onLoad"
|
|
||||||
style="width:100%;{{if not .Email.HTML}}display:none;{{end}}"
|
|
||||||
src="{{ .Email.ID }}/html">
|
|
||||||
</iframe>
|
|
||||||
<div data-target="tabs.tabContent" data-tabs-for="text" style="{{if .Email.HTML}}display:none;{{end}}width:100%;overflow:hidden;">
|
|
||||||
<pre style="white-space:pre-line;">{{ .Email.Text }}</pre>
|
|
||||||
</div>
|
</div>
|
||||||
<div data-target="tabs.tabContent" data-tabs-for="headers" style="display:none">
|
<div data-target="tabs.tabContent" data-tabs-for="metadata" style="display:none">
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="table is-striped is-hoverable is-fullwidth">
|
<table class="table is-striped is-hoverable is-fullwidth">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -59,13 +40,11 @@
|
||||||
<tr>
|
<tr>
|
||||||
<thead>
|
<thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range $k, $v := .Email.Headers}}
|
{{range $k, $v := .SMS.Metadata}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>{{ $k }}</code></td>
|
<td><code>{{ $k }}</code></td>
|
||||||
<td>
|
<td>
|
||||||
{{range $v}}
|
<code>{{ toPrettyJSON . }}</code>
|
||||||
<code>{{ . }}</code>
|
|
||||||
{{end}}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -81,61 +60,30 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{define "email_head"}}
|
{{define "sms_head"}}
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-1">
|
<div class="column is-1">
|
||||||
<h5 class="is-size-5">From</h5>
|
<h5 class="is-size-5">From</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{{template "email_adresses" .Email.From}}
|
<span class="tag is-large">{{.SMS.From}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-1">
|
<div class="column is-1">
|
||||||
<h5 class="is-size-5">To</h5>
|
<h5 class="is-size-5">Recipient</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{{template "email_adresses" .Email.To}}
|
<span class="tag is-large">{{.SMS.Recipient}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .Email.Cc }}
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-1">
|
<div class="column is-1">
|
||||||
<h5 class="is-size-5">Cc</h5>
|
<h5 class="is-size-5">Sent At</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{{template "email_adresses" .Email.Cc}}
|
<p class="is-size-5">{{ .SMS.SentAt.Format "02/01/2006 15:04:05"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .Email.Cci }}
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-1">
|
|
||||||
<h5 class="is-size-5">Cci</h5>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
{{template "email_adresses" .Email.Cci}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-1">
|
|
||||||
<h5 class="is-size-5">Subject</h5>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<p class="is-size-5">{{.Email.Subject}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{define "email_adresses"}}
|
|
||||||
{{- range .}}
|
|
||||||
<span class="tag">
|
|
||||||
{{- if .Name -}}
|
|
||||||
{{.Name}} <{{.Address}}>
|
|
||||||
{{- else -}}
|
|
||||||
{{.Address}}
|
|
||||||
{{- end -}}
|
|
||||||
</span>
|
|
||||||
{{- end -}}
|
|
||||||
{{end}}
|
|
||||||
{{template "base" .}}
|
{{template "base" .}}
|
|
@ -13,8 +13,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type StoreSMSRequest struct {
|
type StoreSMSRequest struct {
|
||||||
|
From string
|
||||||
Body string
|
Body string
|
||||||
Recipient string
|
Recipient string
|
||||||
|
Metadata map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleStoreSMS(ctx context.Context, cmd cqrs.Command) error {
|
func HandleStoreSMS(ctx context.Context, cmd cqrs.Command) error {
|
||||||
|
@ -39,6 +41,8 @@ func HandleStoreSMS(ctx context.Context, cmd cqrs.Command) error {
|
||||||
|
|
||||||
sms.Body = req.Body
|
sms.Body = req.Body
|
||||||
sms.Recipient = req.Recipient
|
sms.Recipient = req.Recipient
|
||||||
|
sms.Metadata = req.Metadata
|
||||||
|
sms.From = req.From
|
||||||
|
|
||||||
if err := db.Save(sms); err != nil {
|
if err := db.Save(sms); err != nil {
|
||||||
return errors.Wrap(err, "could not save email")
|
return errors.Wrap(err, "could not save email")
|
||||||
|
|
|
@ -11,8 +11,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
Data DataConfig `yaml:"data"`
|
Data DataConfig `yaml:"data"`
|
||||||
|
Powow PowowConfig `ymal:"powow"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
@ -25,6 +26,18 @@ type DataConfig struct {
|
||||||
Path string `yaml:"path" env:"FAKESMS_DATA_PATH"`
|
Path string `yaml:"path" env:"FAKESMS_DATA_PATH"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PowowConfig struct {
|
||||||
|
APIKey string `yaml:"apiKey" env:"FAKESMS_POWOW_API_KEY"`
|
||||||
|
SMS []PowowSMS `yaml:"sms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PowowSMS struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
From string `yaml:"from"`
|
||||||
|
Content string `yaml:"content"`
|
||||||
|
ShortLink bool `yaml:"shortLink"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewFromFile retrieves the configuration from the given file
|
// NewFromFile retrieves the configuration from the given file
|
||||||
func NewFromFile(filepath string) (*Config, error) {
|
func NewFromFile(filepath string) (*Config, error) {
|
||||||
config := NewDefault()
|
config := NewDefault()
|
||||||
|
@ -57,13 +70,27 @@ func NewDumpDefault() *Config {
|
||||||
func NewDefault() *Config {
|
func NewDefault() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
HTTP: HTTPConfig{
|
HTTP: HTTPConfig{
|
||||||
Address: ":8080",
|
Address: ":3000",
|
||||||
TemplateDir: "template",
|
TemplateDir: "template",
|
||||||
PublicDir: "public",
|
PublicDir: "public",
|
||||||
},
|
},
|
||||||
Data: DataConfig{
|
Data: DataConfig{
|
||||||
Path: "fakesms.db",
|
Path: "fakesms.db",
|
||||||
},
|
},
|
||||||
|
Powow: PowowConfig{
|
||||||
|
APIKey: "powow",
|
||||||
|
SMS: []PowowSMS{
|
||||||
|
{
|
||||||
|
Name: "Powow SMS",
|
||||||
|
From: "FakeSMS",
|
||||||
|
ShortLink: false,
|
||||||
|
Content: `Bonjour %Subscriber:Firstname%,
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet...
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ type SMS struct {
|
||||||
ID int `storm:"id,increment"`
|
ID int `storm:"id,increment"`
|
||||||
Body string
|
Body string
|
||||||
Seen bool `storm:"index"`
|
Seen bool `storm:"index"`
|
||||||
|
From string
|
||||||
Recipient string
|
Recipient string
|
||||||
SentAt time.Time
|
SentAt time.Time
|
||||||
Metadata map[string]interface{}
|
Metadata map[string]interface{}
|
||||||
|
|
|
@ -19,7 +19,11 @@ func Mount(r *chi.Mux, config *config.Config) error {
|
||||||
r.Route("/v1", func(r chi.Router) {
|
r.Route("/v1", func(r chi.Router) {
|
||||||
r.Get("/sms", browseAPIV1SMS)
|
r.Get("/sms", browseAPIV1SMS)
|
||||||
r.Get("/sms/{id}", serveAPIV1SMS)
|
r.Get("/sms/{id}", serveAPIV1SMS)
|
||||||
|
|
||||||
|
// Powow Mock
|
||||||
|
r.Post("/mock/powow", handlePowowEntrypoint)
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
notFoundHandler := r.NotFoundHandler()
|
notFoundHandler := r.NotFoundHandler()
|
||||||
|
|
|
@ -0,0 +1,229 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,14 +102,3 @@ func openSMS(ctx context.Context, emailID int) (*model.SMS, error) {
|
||||||
|
|
||||||
return openEmailData.SMS, nil
|
return openEmailData.SMS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAttachmentIndex(r *http.Request) (int, error) {
|
|
||||||
rawAttachmendIndex := chi.URLParam(r, "attachmendIndex")
|
|
||||||
|
|
||||||
attachmendIndex, err := strconv.ParseInt(rawAttachmendIndex, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(attachmendIndex), nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
POST http://localhost:3000/api/v1/mock/powow
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"APIKey": "powow",
|
||||||
|
"Command": "TransactionalSms.Send",
|
||||||
|
"ResponseFormat": "JSON",
|
||||||
|
"SmsID": 0,
|
||||||
|
"MobilePhoneNumber": "+33699999999",
|
||||||
|
"TimeToSend": "2017-01-01 10:00:00",
|
||||||
|
"CustomData": {
|
||||||
|
"Firstname": "Rm9v"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue