Initial commit
This commit is contained in:
41
internal/command/clear_outbox.go
Normal file
41
internal/command/clear_outbox.go
Normal file
@ -0,0 +1,41 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/model"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
type ClearOutboxRequest struct{}
|
||||
|
||||
func HandleClearOutbox(ctx context.Context, cmd cqrs.Command) error {
|
||||
_, ok := cmd.Request().(*ClearOutboxRequest)
|
||||
if !ok {
|
||||
return cqrs.ErrUnexpectedRequest
|
||||
}
|
||||
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve service container")
|
||||
}
|
||||
|
||||
db, err := storm.From(ctn)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve storm service")
|
||||
}
|
||||
|
||||
if err := db.Select().Delete(&model.SMS{}); err != nil {
|
||||
if err == storm.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "could not delete messages")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
43
internal/command/delete_sms.go
Normal file
43
internal/command/delete_sms.go
Normal file
@ -0,0 +1,43 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/model"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
type DeleteSMSRequest struct {
|
||||
SMSID int
|
||||
}
|
||||
|
||||
func HandleDeleteSMS(ctx context.Context, cmd cqrs.Command) error {
|
||||
req, ok := cmd.Request().(*DeleteSMSRequest)
|
||||
if !ok {
|
||||
return cqrs.ErrUnexpectedRequest
|
||||
}
|
||||
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve service container")
|
||||
}
|
||||
|
||||
db, err := storm.From(ctn)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve storm service")
|
||||
}
|
||||
|
||||
if err := db.DeleteStruct(&model.SMS{ID: req.SMSID}); err != nil {
|
||||
if err == storm.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "could not delete email")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
48
internal/command/store_sms.go
Normal file
48
internal/command/store_sms.go
Normal file
@ -0,0 +1,48 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/model"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
type StoreSMSRequest struct {
|
||||
Body string
|
||||
Recipient string
|
||||
}
|
||||
|
||||
func HandleStoreSMS(ctx context.Context, cmd cqrs.Command) error {
|
||||
req, ok := cmd.Request().(*StoreSMSRequest)
|
||||
if !ok {
|
||||
return cqrs.ErrUnexpectedRequest
|
||||
}
|
||||
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve service container")
|
||||
}
|
||||
|
||||
db, err := storm.From(ctn)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve storm service")
|
||||
}
|
||||
|
||||
sms := &model.SMS{
|
||||
SentAt: time.Now(),
|
||||
}
|
||||
|
||||
sms.Body = req.Body
|
||||
sms.Recipient = req.Recipient
|
||||
|
||||
if err := db.Save(sms); err != nil {
|
||||
return errors.Wrap(err, "could not save email")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
81
internal/config/config.go
Normal file
81
internal/config/config.go
Normal file
@ -0,0 +1,81 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
Data DataConfig `yaml:"data"`
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
Address string `yaml:"address" env:"FAKESMS_HTTP_ADDRESS"`
|
||||
TemplateDir string `yaml:"templateDir" env:"FAKESMS_HTTP_TEMPLATEDIR"`
|
||||
PublicDir string `yaml:"publicDir" env:"FAKESMS_HTTP_PUBLICDIR"`
|
||||
}
|
||||
|
||||
type DataConfig struct {
|
||||
Path string `yaml:"path" env:"FAKESMS_DATA_PATH"`
|
||||
}
|
||||
|
||||
// NewFromFile retrieves the configuration from the given file
|
||||
func NewFromFile(filepath string) (*Config, error) {
|
||||
config := NewDefault()
|
||||
|
||||
data, err := ioutil.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read file '%s'", filepath)
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return nil, errors.Wrapf(err, "could not unmarshal configuration")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func WithEnvironment(conf *Config) error {
|
||||
if err := env.Parse(conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDumpDefault() *Config {
|
||||
config := NewDefault()
|
||||
return config
|
||||
}
|
||||
|
||||
func NewDefault() *Config {
|
||||
return &Config{
|
||||
HTTP: HTTPConfig{
|
||||
Address: ":8080",
|
||||
TemplateDir: "template",
|
||||
PublicDir: "public",
|
||||
},
|
||||
Data: DataConfig{
|
||||
Path: "fakesms.db",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Dump(config *Config, w io.Writer) error {
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not dump config")
|
||||
}
|
||||
|
||||
if _, err := w.Write(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
9
internal/config/provider.go
Normal file
9
internal/config/provider.go
Normal file
@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
import "gitlab.com/wpetit/goweb/service"
|
||||
|
||||
func ServiceProvider(config *Config) service.Provider {
|
||||
return func(ctn *service.Container) (interface{}, error) {
|
||||
return config, nil
|
||||
}
|
||||
}
|
33
internal/config/service.go
Normal file
33
internal/config/service.go
Normal file
@ -0,0 +1,33 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
const ServiceName service.Name = "config"
|
||||
|
||||
// From retrieves the config service in the given container
|
||||
func From(container *service.Container) (*Config, error) {
|
||||
service, err := container.Service(ServiceName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
|
||||
}
|
||||
|
||||
srv, ok := service.(*Config)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Must retrieves the config service in the given container or panic otherwise
|
||||
func Must(container *service.Container) *Config {
|
||||
srv, err := From(container)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return srv
|
||||
}
|
12
internal/model/sms.go
Normal file
12
internal/model/sms.go
Normal file
@ -0,0 +1,12 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type SMS struct {
|
||||
ID int `storm:"id,increment"`
|
||||
Body string
|
||||
Seen bool `storm:"index"`
|
||||
Recipient string
|
||||
SentAt time.Time
|
||||
Metadata map[string]interface{}
|
||||
}
|
133
internal/query/get_outbox.go
Normal file
133
internal/query/get_outbox.go
Normal file
@ -0,0 +1,133 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/model"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
||||
stormdb "github.com/asdine/storm/v3"
|
||||
"github.com/asdine/storm/v3/q"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
type OutboxSearch struct {
|
||||
Recipient string
|
||||
Body string
|
||||
After time.Time
|
||||
Before time.Time
|
||||
}
|
||||
|
||||
type GetOutboxRequest struct {
|
||||
OrderBy string
|
||||
Limit int
|
||||
Skip int
|
||||
Reverse bool
|
||||
Search *OutboxSearch
|
||||
}
|
||||
|
||||
type OutboxData struct {
|
||||
Messages []*model.SMS
|
||||
}
|
||||
|
||||
func HandleGetOutbox(ctx context.Context, qry cqrs.Query) (interface{}, error) {
|
||||
req, ok := qry.Request().(*GetOutboxRequest)
|
||||
if !ok {
|
||||
return nil, cqrs.ErrUnexpectedRequest
|
||||
}
|
||||
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve service container")
|
||||
}
|
||||
|
||||
db, err := storm.From(ctn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve storm service")
|
||||
}
|
||||
|
||||
messages := make([]*model.SMS, 0)
|
||||
|
||||
var query stormdb.Query
|
||||
|
||||
if req.Search != nil {
|
||||
matchers := make([]q.Matcher, 0)
|
||||
|
||||
if req.Search.Body != "" {
|
||||
matchers = append(matchers, q.Or(
|
||||
q.Re("Body", req.Search.Body),
|
||||
))
|
||||
}
|
||||
|
||||
query = db.Select(matchers...)
|
||||
} else {
|
||||
query = db.Select()
|
||||
}
|
||||
|
||||
if req.OrderBy != "" {
|
||||
query = query.OrderBy(req.OrderBy)
|
||||
} else {
|
||||
query = query.OrderBy("SentAt").Reverse()
|
||||
}
|
||||
|
||||
if req.Reverse {
|
||||
query = query.Reverse()
|
||||
}
|
||||
|
||||
if req.Limit != 0 {
|
||||
query = query.Limit(req.Limit)
|
||||
}
|
||||
|
||||
if req.Skip != 0 {
|
||||
query = query.Limit(req.Skip)
|
||||
}
|
||||
|
||||
if err := query.Find(&messages); err != nil {
|
||||
if err == storm.ErrNotFound {
|
||||
return &OutboxData{messages}, nil
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(err, "could not retrieve emails")
|
||||
}
|
||||
|
||||
if req.Search == nil {
|
||||
return &OutboxData{messages}, nil
|
||||
}
|
||||
|
||||
filtered := make([]*model.SMS, 0, len(messages))
|
||||
|
||||
for _, sms := range messages {
|
||||
match := true
|
||||
|
||||
if req.Search.Recipient != "" {
|
||||
found := false
|
||||
|
||||
if strings.Contains(sms.Recipient, req.Search.Recipient) {
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
|
||||
if !req.Search.After.IsZero() && !sms.SentAt.After(req.Search.After) {
|
||||
match = false
|
||||
}
|
||||
|
||||
if !req.Search.Before.IsZero() && !sms.SentAt.Before(req.Search.Before) {
|
||||
match = false
|
||||
}
|
||||
|
||||
if match {
|
||||
filtered = append(filtered, sms)
|
||||
}
|
||||
}
|
||||
|
||||
return &OutboxData{filtered}, nil
|
||||
}
|
44
internal/query/open_sms.go
Normal file
44
internal/query/open_sms.go
Normal file
@ -0,0 +1,44 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/model"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
type OpenSMSRequest struct {
|
||||
SMSID int
|
||||
}
|
||||
|
||||
type OpenSMSData struct {
|
||||
SMS *model.SMS
|
||||
}
|
||||
|
||||
func HandleOpenSMS(ctx context.Context, qry cqrs.Query) (interface{}, error) {
|
||||
req, ok := qry.Request().(*OpenSMSRequest)
|
||||
if !ok {
|
||||
return nil, cqrs.ErrUnexpectedRequest
|
||||
}
|
||||
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve service container")
|
||||
}
|
||||
|
||||
db, err := storm.From(ctn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve storm service")
|
||||
}
|
||||
|
||||
sms := &model.SMS{}
|
||||
|
||||
if err := db.One("ID", req.SMSID, sms); err != nil {
|
||||
return nil, errors.Wrap(err, "could not find email")
|
||||
}
|
||||
|
||||
return &OpenSMSData{sms}, nil
|
||||
}
|
64
internal/route/api.go
Normal file
64
internal/route/api.go
Normal file
@ -0,0 +1,64 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/query"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
func browseAPIV1SMS(w http.ResponseWriter, r *http.Request) {
|
||||
ctn := container.Must(r.Context())
|
||||
bus := cqrs.Must(ctn)
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
getInbox, err := createOutboxQueryFromRequest(r)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "bad request", logger.E(err))
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
result, err := bus.Query(ctx, getInbox)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve inbox"))
|
||||
}
|
||||
|
||||
inboxData, ok := result.Data().(*query.OutboxData)
|
||||
if !ok {
|
||||
panic(errors.New("unexpected data"))
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, inboxData)
|
||||
}
|
||||
|
||||
func serveAPIV1SMS(w http.ResponseWriter, r *http.Request) {
|
||||
smsID, err := getSMSID(r)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
email, err := openSMS(ctx, smsID)
|
||||
if err != nil {
|
||||
if errors.Is(err, storm.ErrNotFound) {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
panic(errors.Wrap(err, "could not open sms"))
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, email)
|
||||
}
|
101
internal/route/helper.go
Normal file
101
internal/route/helper.go
Normal file
@ -0,0 +1,101 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/query"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/service/template"
|
||||
)
|
||||
|
||||
func extendTemplateData(w http.ResponseWriter, r *http.Request, data template.Data) template.Data {
|
||||
ctn := container.Must(r.Context())
|
||||
data, err := template.Extend(data,
|
||||
template.WithBuildInfo(w, r, ctn),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not extend template data"))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func createOutboxQueryFromRequest(r *http.Request) (*query.GetOutboxRequest, error) {
|
||||
orderBy := r.URL.Query().Get("orderBy")
|
||||
reverse := r.URL.Query().Get("reverse")
|
||||
recipient := r.URL.Query().Get("recipient")
|
||||
body := r.URL.Query().Get("body")
|
||||
|
||||
var err error
|
||||
|
||||
var limit int64 = 0
|
||||
|
||||
rawLimit := r.URL.Query().Get("limit")
|
||||
if rawLimit != "" {
|
||||
limit, err = strconv.ParseInt(rawLimit, 10, 32)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
var skip int64 = 0
|
||||
|
||||
rawSkip := r.URL.Query().Get("skip")
|
||||
if rawSkip != "" {
|
||||
skip, err = strconv.ParseInt(rawSkip, 10, 32)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
var after time.Time
|
||||
|
||||
rawAfter := r.URL.Query().Get("after")
|
||||
if rawAfter != "" {
|
||||
after, err = time.Parse(time.RFC3339, rawAfter)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
var before time.Time
|
||||
|
||||
rawBefore := r.URL.Query().Get("before")
|
||||
if rawBefore != "" {
|
||||
before, err = time.Parse(time.RFC3339, rawBefore)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
search := &query.OutboxSearch{}
|
||||
if recipient != "" {
|
||||
search.Recipient = recipient
|
||||
}
|
||||
|
||||
if body != "" {
|
||||
search.Body = body
|
||||
}
|
||||
|
||||
if rawAfter != "" {
|
||||
search.After = after
|
||||
}
|
||||
|
||||
if rawBefore != "" {
|
||||
search.Before = before
|
||||
}
|
||||
|
||||
inboxRequest := &query.GetOutboxRequest{
|
||||
OrderBy: orderBy,
|
||||
Reverse: reverse == "y",
|
||||
Skip: int(skip),
|
||||
Limit: int(limit),
|
||||
Search: search,
|
||||
}
|
||||
|
||||
return inboxRequest, nil
|
||||
}
|
29
internal/route/mount.go
Normal file
29
internal/route/mount.go
Normal file
@ -0,0 +1,29 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/config"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"gitlab.com/wpetit/goweb/static"
|
||||
)
|
||||
|
||||
func Mount(r *chi.Mux, config *config.Config) error {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get("/", serveOutboxPage)
|
||||
r.Delete("/sms", handleClearOutbox)
|
||||
r.Get("/sms/{id}", serveSMSPage)
|
||||
r.Delete("/sms/{id}", handleSMSDelete)
|
||||
})
|
||||
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
r.Get("/sms", browseAPIV1SMS)
|
||||
r.Get("/sms/{id}", serveAPIV1SMS)
|
||||
})
|
||||
})
|
||||
|
||||
notFoundHandler := r.NotFoundHandler()
|
||||
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", notFoundHandler))
|
||||
|
||||
return nil
|
||||
}
|
62
internal/route/outbox.go
Normal file
62
internal/route/outbox.go
Normal file
@ -0,0 +1,62 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/command"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/query"
|
||||
"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/template"
|
||||
)
|
||||
|
||||
func serveOutboxPage(w http.ResponseWriter, r *http.Request) {
|
||||
ctn := container.Must(r.Context())
|
||||
tmpl := template.Must(ctn)
|
||||
bus := cqrs.Must(ctn)
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
getOutbox, err := createOutboxQueryFromRequest(r)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "bad request", logger.E(err))
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
result, err := bus.Query(ctx, getOutbox)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve outbox"))
|
||||
}
|
||||
|
||||
inboxData, ok := result.Data().(*query.OutboxData)
|
||||
if !ok {
|
||||
panic(errors.New("unexpected data"))
|
||||
}
|
||||
|
||||
data := extendTemplateData(w, r, template.Data{
|
||||
"Messages": inboxData.Messages,
|
||||
})
|
||||
|
||||
if err := tmpl.RenderPage(w, "outbox.html.tmpl", data); err != nil {
|
||||
panic(errors.Wrapf(err, "could not render '%s' page", r.URL.Path))
|
||||
}
|
||||
}
|
||||
|
||||
func handleClearOutbox(w http.ResponseWriter, r *http.Request) {
|
||||
ctn := container.Must(r.Context())
|
||||
|
||||
bus := cqrs.Must(ctn)
|
||||
|
||||
clearInbox := &command.ClearOutboxRequest{}
|
||||
ctx := r.Context()
|
||||
|
||||
if _, err := bus.Exec(ctx, clearInbox); err != nil {
|
||||
panic(errors.Wrap(err, "could not clear outbox"))
|
||||
}
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent)
|
||||
}
|
115
internal/route/sms.go
Normal file
115
internal/route/sms.go
Normal file
@ -0,0 +1,115 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/command"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/model"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/query"
|
||||
"forge.cadoles.com/Cadoles/fake-sms/internal/storm"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/service/template"
|
||||
)
|
||||
|
||||
func serveSMSPage(w http.ResponseWriter, r *http.Request) {
|
||||
smsID, err := getSMSID(r)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
sms, err := openSMS(ctx, smsID)
|
||||
if err != nil {
|
||||
if errors.Is(err, storm.ErrNotFound) {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
panic(errors.Wrap(err, "could not open sms"))
|
||||
}
|
||||
|
||||
ctn := container.Must(ctx)
|
||||
tmpl := template.Must(ctn)
|
||||
|
||||
data := extendTemplateData(w, r, template.Data{
|
||||
"SMS": sms,
|
||||
})
|
||||
|
||||
if err := tmpl.RenderPage(w, "sms.html.tmpl", data); err != nil {
|
||||
panic(errors.Wrapf(err, "could not render '%s' page", r.URL.Path))
|
||||
}
|
||||
}
|
||||
|
||||
func handleSMSDelete(w http.ResponseWriter, r *http.Request) {
|
||||
smsID, err := getSMSID(r)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
ctn := container.Must(ctx)
|
||||
bus := cqrs.Must(ctn)
|
||||
|
||||
deleteSMS := &command.DeleteSMSRequest{
|
||||
SMSID: smsID,
|
||||
}
|
||||
|
||||
if _, err := bus.Exec(ctx, deleteSMS); err != nil {
|
||||
panic(errors.Wrap(err, "could not delete email"))
|
||||
}
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent)
|
||||
}
|
||||
|
||||
func getSMSID(r *http.Request) (int, error) {
|
||||
rawSMSID := chi.URLParam(r, "id")
|
||||
|
||||
smsID, err := strconv.ParseInt(rawSMSID, 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(smsID), nil
|
||||
}
|
||||
|
||||
func openSMS(ctx context.Context, emailID int) (*model.SMS, error) {
|
||||
ctn := container.Must(ctx)
|
||||
bus := cqrs.Must(ctn)
|
||||
req := &query.OpenSMSRequest{
|
||||
SMSID: emailID,
|
||||
}
|
||||
|
||||
result, err := bus.Query(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
openEmailData, ok := result.Data().(*query.OpenSMSData)
|
||||
if !ok {
|
||||
return nil, errors.New("unexpected result data")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
9
internal/storm/error.go
Normal file
9
internal/storm/error.go
Normal file
@ -0,0 +1,9 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = storm.ErrNotFound
|
||||
)
|
51
internal/storm/option.go
Normal file
51
internal/storm/option.go
Normal file
@ -0,0 +1,51 @@
|
||||
package storm
|
||||
|
||||
type Option struct {
|
||||
Path string
|
||||
Objects []interface{}
|
||||
ReIndex bool
|
||||
Init bool
|
||||
}
|
||||
|
||||
type OptionFunc func(*Option)
|
||||
|
||||
func DefaultOption() *Option {
|
||||
return MergeOption(
|
||||
&Option{},
|
||||
WithPath("data.db"),
|
||||
WithInit(true),
|
||||
WithReIndex(true),
|
||||
)
|
||||
}
|
||||
|
||||
func MergeOption(opt *Option, funcs ...OptionFunc) *Option {
|
||||
for _, fn := range funcs {
|
||||
fn(opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
func WithPath(path string) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.Path = path
|
||||
}
|
||||
}
|
||||
|
||||
func WithReIndex(reindex bool) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.ReIndex = reindex
|
||||
}
|
||||
}
|
||||
|
||||
func WithInit(init bool) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.Init = init
|
||||
}
|
||||
}
|
||||
|
||||
func WithObjects(objects ...interface{}) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.Objects = objects
|
||||
}
|
||||
}
|
48
internal/storm/provider.go
Normal file
48
internal/storm/provider.go
Normal file
@ -0,0 +1,48 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
func ServiceProvider(funcs ...OptionFunc) service.Provider {
|
||||
opt := MergeOption(
|
||||
DefaultOption(),
|
||||
funcs...,
|
||||
)
|
||||
|
||||
db, err := storm.Open(opt.Path)
|
||||
|
||||
if err == nil && opt.Objects != nil {
|
||||
err = migrate(db, opt.Objects, opt.Init, opt.ReIndex)
|
||||
}
|
||||
|
||||
return func(ctn *service.Container) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
}
|
||||
|
||||
func migrate(db *storm.DB, objects []interface{}, init, reindex bool) error {
|
||||
for _, o := range objects {
|
||||
if init {
|
||||
if err := db.Init(o); err != nil {
|
||||
return errors.Wrapf(err, "could not init object '%s'", reflect.TypeOf(o).String())
|
||||
}
|
||||
}
|
||||
|
||||
if reindex {
|
||||
if err := db.ReIndex(o); err != nil {
|
||||
return errors.Wrapf(err, "could not reindex object '%s'", reflect.TypeOf(o).String())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
34
internal/storm/service.go
Normal file
34
internal/storm/service.go
Normal file
@ -0,0 +1,34 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
const ServiceName service.Name = "storm"
|
||||
|
||||
// From retrieves the storm service in the given container
|
||||
func From(container *service.Container) (*storm.DB, error) {
|
||||
service, err := container.Service(ServiceName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
|
||||
}
|
||||
|
||||
srv, ok := service.(*storm.DB)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Must retrieves the storm service in the given container or panic otherwise
|
||||
func Must(container *service.Container) *storm.DB {
|
||||
srv, err := From(container)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return srv
|
||||
}
|
Reference in New Issue
Block a user