diff --git a/.gitignore b/.gitignore index c7c92de..81fe290 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea config*.yml !config.default.yml -mailwhale \ No newline at end of file +mailwhale +*.db \ No newline at end of file diff --git a/config/config.go b/config/config.go index 45b8933..0997a94 100644 --- a/config/config.go +++ b/config/config.go @@ -22,11 +22,16 @@ type webConfig struct { ListenV4 string `yaml:"listen_v4" default:"127.0.0.1:3000" env:"MW_WEB_LISTEN_V4"` } +type storeConfig struct { + Path string `default:"data.gob.db" env:"MW_STORE_PATH"` +} + type Config struct { Env string `default:"dev" env:"MW_ENV"` Version string Web webConfig Smtp smtpConfig + Store storeConfig } var cfg *Config diff --git a/config/store.go b/config/store.go new file mode 100644 index 0000000..2160dc5 --- /dev/null +++ b/config/store.go @@ -0,0 +1,27 @@ +package config + +import ( + "github.com/emvi/logbuch" + "github.com/timshannon/bolthold" +) + +var store *bolthold.Store + +func InitStore(path string) *bolthold.Store { + if s, err := bolthold.Open(path, 0664, nil); err != nil { + logbuch.Fatal("failed to open store: %v", err) + } else { + store = s + } + return store +} + +func GetStore() *bolthold.Store { + return store +} + +func CloseStore() { + if store != nil { + store.Close() + } +} diff --git a/go.mod b/go.mod index 5a2f632..da0711d 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,8 @@ require ( github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 github.com/emersion/go-smtp v0.14.0 github.com/emvi/logbuch v1.1.2 + github.com/google/uuid v1.2.0 github.com/jinzhu/configor v1.2.1 + github.com/timshannon/bolthold v0.0.0-20200817130212-4a25ab140645 + go.etcd.io/bbolt v1.3.5 // indirect ) diff --git a/go.sum b/go.sum index b75610f..b922763 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,16 @@ github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkX github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/emvi/logbuch v1.1.2 h1:uAHXXqEDboQmEFCNG7ZhCK7h6eMTVshvr6L0UrtC13w= github.com/emvi/logbuch v1.1.2/go.mod h1:J2Wgbr3BuSc1JO+D2MBVh6q3WPVSK5GzktwWz8pvkKw= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko= github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc= +github.com/timshannon/bolthold v0.0.0-20200817130212-4a25ab140645 h1:fbG8rLkRTpKD2rWD/vtQ6ceXZcnDglJssBieaPvieHw= +github.com/timshannon/bolthold v0.0.0-20200817130212-4a25ab140645/go.mod h1:jUigdmrbdCxcIDEFrq82t4X9805XZfwFZoYUap0ET/U= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 05c6550..9459fce 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "github.com/emvi/logbuch" conf "github.com/muety/mailwhale/config" "github.com/muety/mailwhale/service" - "github.com/muety/mailwhale/web/api" + "github.com/muety/mailwhale/web/routes/api" "net/http" "time" ) diff --git a/util/http.go b/util/http.go new file mode 100644 index 0000000..fa907e1 --- /dev/null +++ b/util/http.go @@ -0,0 +1,29 @@ +package util + +import ( + "encoding/json" + "github.com/emvi/logbuch" + "net/http" +) + +func RespondEmpty(w http.ResponseWriter, r *http.Request, status int) { + if status <= 0 { + status = http.StatusOK + } + w.WriteHeader(status) + w.Write([]byte(http.StatusText(status))) +} + +func RespondJson(w http.ResponseWriter, status int, object interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + if err := json.NewEncoder(w).Encode(object); err != nil { + logbuch.Error("error while writing json response: %v", err) + } +} + +func RespondError(w http.ResponseWriter, r *http.Request, status int) { + logbuch.Error("request '%s %s' failed: %v", r.Method, r.URL.Path) + w.WriteHeader(status) + w.Write([]byte(http.StatusText(status))) +} diff --git a/web/api/health.go b/web/routes/api/health.go similarity index 88% rename from web/api/health.go rename to web/routes/api/health.go index 19ace54..9674522 100644 --- a/web/api/health.go +++ b/web/routes/api/health.go @@ -6,6 +6,8 @@ import ( "net/http" ) +const routeHealth = "/api/health" + type HealthHandler struct { config *conf.Config } @@ -17,7 +19,7 @@ func NewHealthHandler() *HealthHandler { } func (h *HealthHandler) Register(mux *http.ServeMux) { - mux.Handle("/api/health", h) + mux.Handle(routeHealth, h) } func (h *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/web/api/mail.go b/web/routes/api/mail.go similarity index 58% rename from web/api/mail.go rename to web/routes/api/mail.go index 4e42c8f..a8df438 100644 --- a/web/api/mail.go +++ b/web/routes/api/mail.go @@ -2,14 +2,16 @@ package api import ( "encoding/json" - "github.com/emvi/logbuch" conf "github.com/muety/mailwhale/config" "github.com/muety/mailwhale/service" "github.com/muety/mailwhale/types" "github.com/muety/mailwhale/types/dto" + "github.com/muety/mailwhale/util" "net/http" ) +const routeMail = "/api/mail" + type MailHandler struct { config *conf.Config sendService *service.SendService @@ -23,29 +25,26 @@ func NewMailHandler(sendService *service.SendService) *MailHandler { } func (h *MailHandler) Register(mux *http.ServeMux) { - mux.Handle("/api/mail", h) + mux.Handle(routeMail, h) } func (h *MailHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { - h.handleGet(w, r) + h.get(w, r) } else if r.Method == http.MethodPost { - h.handlePost(w, r) + h.post(w, r) } } -func (h *MailHandler) handleGet(w http.ResponseWriter, r *http.Request) { +func (h *MailHandler) get(w http.ResponseWriter, r *http.Request) { // TODO: implement - w.WriteHeader(http.StatusMethodNotAllowed) - _, _ = w.Write([]byte(http.StatusText(http.StatusMethodNotAllowed))) + util.RespondError(w, r, http.StatusMethodNotAllowed) } -func (h *MailHandler) handlePost(w http.ResponseWriter, r *http.Request) { +func (h *MailHandler) post(w http.ResponseWriter, r *http.Request) { var payload dto.MailSendRequest if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - logbuch.Error("failed to decode mail request body: %v", err) - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(http.StatusText(http.StatusBadRequest))) + util.RespondError(w, r, http.StatusBadRequest) return } @@ -63,12 +62,9 @@ func (h *MailHandler) handlePost(w http.ResponseWriter, r *http.Request) { } if err := h.sendService.Send(mail); err != nil { - logbuch.Error("failed to send mail: %v", err) - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(http.StatusText(http.StatusInternalServerError))) + util.RespondError(w, r, http.StatusInternalServerError) return } - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(http.StatusText(http.StatusOK))) + util.RespondEmpty(w, r, 0) }