diff --git a/app/attachment_get.go b/app/attachment.go similarity index 100% rename from app/attachment_get.go rename to app/attachment.go diff --git a/app/message_handle.go b/app/message_handle.go index 2222a143..5c78a0b6 100644 --- a/app/message_handle.go +++ b/app/message_handle.go @@ -7,5 +7,11 @@ type MessageSendRequest struct { } func (a *App) MessageSend(req *MessageSendRequest) error { - return a.endpointSVC.SendBridges(req.Message, a.bridgeSVC.GetBridges(req.Message)) + err := a.endpointSVC.SendBridges(req.Message, a.bridgeSVC.GetBridges(req.Message)) + if err != nil { + a.messageSVC.UpdateStatus(req.Message, domain.StatusFailed) + return err + } + + return a.messageSVC.UpdateStatus(req.Message, domain.StatusSent) } diff --git a/app/message_list.go b/app/message_list.go index b9848078..d3bc2e0d 100644 --- a/app/message_list.go +++ b/app/message_list.go @@ -1,12 +1,52 @@ package app -import "github.com/ItsNotGoodName/smtpbridge/domain" +import ( + "path" + + "github.com/ItsNotGoodName/smtpbridge/dto" +) type MessageListRequest struct { - Limit int - Offset int + Page int + AttachmentPath string } -func (a *App) MessageList(req *MessageListRequest) ([]domain.Message, error) { - return a.messageSVC.List(req.Limit, req.Offset) +func (a *App) MessageList(req *MessageListRequest) ([]dto.Message, error) { + if req.Page < 0 { + req.Page = 0 + } + + msgs, err := a.messageSVC.List(10, req.Page*10) + if err != nil { + return nil, err + } + + var result []dto.Message + for _, msg := range msgs { + var attachments []dto.Attachment + for _, attachment := range msg.Attachments { + attachments = append(attachments, dto.Attachment{ + UUID: attachment.UUID, + Name: attachment.Name, + Path: path.Join(req.AttachmentPath, a.dao.Attachment.GetAttachmentFile(&attachment)), + }) + } + + var to []string + for toAddr := range msg.To { + to = append(to, toAddr) + } + + result = append(result, dto.Message{ + UUID: msg.UUID, + From: msg.From, + To: to, + Status: msg.Status.String(), + Subject: msg.Subject, + Text: msg.Text, + Attachments: attachments, + }) + } + + return result, nil } diff --git a/domain/message.go b/domain/message.go index 35045061..7f7721e3 100644 --- a/domain/message.go +++ b/domain/message.go @@ -25,6 +25,16 @@ const ( StatusFailed ) +var status []string = []string{ + "created", + "sent", + "failed", +} + +func (s Status) String() string { + return status[s] +} + func NewMessage(subject, from string, to map[string]bool, text string) *Message { return &Message{ CreatedAt: time.Now(), diff --git a/dto/dto.go b/dto/dto.go new file mode 100644 index 00000000..9e671589 --- /dev/null +++ b/dto/dto.go @@ -0,0 +1,17 @@ +package dto + +type Message struct { + UUID string `json:"uuid"` + Status string `json:"status"` + From string `json:"from"` + To []string `json:"to"` + Subject string `json:"subject"` + Text string `json:"text"` + Attachments []Attachment `json:"attachments"` +} + +type Attachment struct { + UUID string `json:"uuid"` + Name string `json:"name"` + Path string `json:"path"` +} diff --git a/left/router/handlers.go b/left/router/handlers.go index 7f92ac65..d0a75b0c 100644 --- a/left/router/handlers.go +++ b/left/router/handlers.go @@ -7,12 +7,12 @@ import ( "net/http" "github.com/ItsNotGoodName/smtpbridge/app" - "github.com/ItsNotGoodName/smtpbridge/domain" + "github.com/ItsNotGoodName/smtpbridge/dto" ) -func (s *Router) GetAttachments(prefix string) http.HandlerFunc { +func (s *Router) GetAttachments() http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { - http.StripPrefix(prefix, http.FileServer(http.FS(s.a.AttachmentGetFS()))).ServeHTTP(rw, r) + http.StripPrefix(s.attachmentURI, http.FileServer(http.FS(s.a.AttachmentGetFS()))).ServeHTTP(rw, r) } } @@ -21,7 +21,7 @@ var templateFS embed.FS func (s *Router) GetIndex() http.HandlerFunc { type Data struct { - Messages []domain.Message + Messages []dto.Message } index, err := template.ParseFS(templateFS, "template/index.html") @@ -30,12 +30,17 @@ func (s *Router) GetIndex() http.HandlerFunc { } return func(rw http.ResponseWriter, r *http.Request) { - messages, err := s.a.MessageList(&app.MessageListRequest{Limit: 10, Offset: 0}) + messages, err := s.a.MessageList(&app.MessageListRequest{AttachmentPath: s.attachmentURI, Page: 0}) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return } - index.Execute(rw, Data{Messages: messages}) + log.Println("router.Router.GetIndex:", len(messages), "messages") + err = index.Execute(rw, Data{Messages: messages}) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } } } diff --git a/left/router/route.go b/left/router/route.go index f774ac29..c229a646 100644 --- a/left/router/route.go +++ b/left/router/route.go @@ -1,6 +1,6 @@ package router func (s *Router) route() { - s.r.Get("/attachments/*", s.GetAttachments("/attachments/")) + s.r.Get(s.attachmentURI+"*", s.GetAttachments()) s.r.Get("/", s.GetIndex()) } diff --git a/left/router/router.go b/left/router/router.go index 62d760ae..72134303 100644 --- a/left/router/router.go +++ b/left/router/router.go @@ -11,14 +11,16 @@ import ( ) type Router struct { - r *chi.Mux - a *app.App + r *chi.Mux + a *app.App + attachmentURI string } func New(app *app.App) *Router { s := Router{ - r: chi.NewRouter(), - a: app, + r: chi.NewRouter(), + a: app, + attachmentURI: "/attachments/", } // A good base middleware stack diff --git a/left/router/template/index.html b/left/router/template/index.html index 0b678290..5bcadd04 100644 --- a/left/router/template/index.html +++ b/left/router/template/index.html @@ -16,7 +16,7 @@

{{ $msg.Subject }}

{{ else }}

No Subject

{{ end }} -
Status: {{ $msg.Status }}
+ {{ $msg.Status }}
{{ $msg.Text }}
@@ -26,8 +26,8 @@

No Subject

{{ range $att := $msg.Attachments }} {{ $att.UUID }}.{{ $att.Type }} {{ end }}
diff --git a/right/database/attachment.go b/right/database/attachment.go new file mode 100644 index 00000000..2e1511c3 --- /dev/null +++ b/right/database/attachment.go @@ -0,0 +1,66 @@ +package database + +import ( + "fmt" + "io/fs" + "os" + "path" + + "github.com/ItsNotGoodName/smtpbridge/domain" + "github.com/asdine/storm" + "github.com/asdine/storm/q" +) + +func (db *DB) CreateAttachment(att *domain.Attachment) error { + err := db.db.Save(att) + if err != nil { + return err + } + + return os.WriteFile(db.getAttachmentPath(att), att.Data, 0644) +} + +// getAttachmentPath returns the path to the attachment file on the file system. +func (db *DB) getAttachmentPath(att *domain.Attachment) string { + return path.Join(db.attDir, db.GetAttachmentFile(att)) +} + +func (db *DB) GetAttachmentFile(att *domain.Attachment) string { + return fmt.Sprintf("%s.%s", att.UUID, att.Type) +} + +func (db *DB) GetAttachmentFS() fs.FS { + return db.fs +} + +func (db *DB) GetAttachment(uuid string) (*domain.Attachment, error) { + var att domain.Attachment + err := db.db.One("UUID", uuid, att) + if err != nil { + return nil, err + } + + return &att, nil +} + +func (db *DB) GetAttachmentData(att *domain.Attachment) ([]byte, error) { + data, err := os.ReadFile(db.getAttachmentPath(att)) + if err != nil { + return nil, err + } + + return data, nil +} + +func (db *DB) GetAttachments(msg *domain.Message) ([]domain.Attachment, error) { + var atts []domain.Attachment + err := db.db.Select(q.Eq("MessageUUID", msg.UUID)).Find(&atts) + if err != nil { + if err == storm.ErrNotFound { + return []domain.Attachment{}, nil + } + return nil, err + } + + return atts, nil +} diff --git a/right/database/database.go b/right/database/database.go index 97576b4f..4cdd2061 100644 --- a/right/database/database.go +++ b/right/database/database.go @@ -1,20 +1,17 @@ package database import ( - "fmt" "io/fs" "log" "os" - "path" - "github.com/ItsNotGoodName/smtpbridge/domain" "github.com/asdine/storm" - "github.com/asdine/storm/q" ) type DB struct { db *storm.DB attDir string + fs fs.FS } func NewDB(dbFile, attDir string) *DB { @@ -30,151 +27,7 @@ func NewDB(dbFile, attDir string) *DB { return &DB{ db: db, + fs: os.DirFS(attDir), attDir: attDir, } } - -func (db *DB) CreateMessage(msg *domain.Message) error { - return db.db.Save(msg) -} - -func (db *DB) GetMessage(uuid string) (*domain.Message, error) { - var msg domain.Message - err := db.db.One("UUID", uuid, msg) - if err != nil { - return nil, err - } - - return &msg, nil -} - -func (db *DB) UpdateMessage(msg *domain.Message, updateFN func(msg *domain.Message) (*domain.Message, error)) error { - tx, err := db.db.Begin(true) - if err != nil { - return err - } - defer tx.Rollback() - - var existingMSG domain.Message - if err := tx.One("UUID", msg.UUID, &existingMSG); err != nil { - return err - } - - updatedMSG, err := updateFN(&existingMSG) - if err != nil { - return err - } - - err = tx.Save(updatedMSG) - if err != nil { - return err - } - - return tx.Commit() -} - -func (db *DB) GetMessages(limit, offset int) ([]domain.Message, error) { - var msgs []domain.Message - err := db.db.Select().OrderBy("CreatedAt").Limit(limit).Skip(offset).Reverse().Find(&msgs) - if err != nil && err != storm.ErrNotFound { - return nil, err - } - - return msgs, nil -} - -func (db *DB) DeleteMessage(msg *domain.Message) error { - tx, err := db.db.Begin(true) - if err != nil { - return err - } - defer tx.Rollback() - - query := tx.Select(q.Eq("MessageUUID", msg.UUID)) - - // List attachments - var atts []domain.Attachment - err = query.Find(&atts) - if err != storm.ErrNotFound { - if err != nil { - return err - } - - // Delete attachments - err = query.Delete(&domain.Attachment{}) - if err != nil { - return err - } - } - - // Delete message - if err := tx.DeleteStruct(msg); err != nil { - return err - } - - if err := tx.Commit(); err != nil { - return err - } - - for _, att := range atts { - if err := os.Remove(db.getAttachmentPath(&att)); err != nil { - log.Println("database.DB.DeleteMessage: could not delete attachment data:", err) - } - } - - return nil -} - -func (db *DB) CreateAttachment(att *domain.Attachment) error { - err := db.db.Save(att) - if err != nil { - return err - } - - return os.WriteFile(db.getAttachmentPath(att), att.Data, 0644) -} - -// getAttachmentPath returns the path to the attachment file on the file system. -func (db *DB) getAttachmentPath(att *domain.Attachment) string { - return path.Join(db.attDir, db.GetAttachmentFile(att)) -} - -func (db *DB) GetAttachmentFile(att *domain.Attachment) string { - return fmt.Sprintf("%s.%s", att.UUID, att.Type) -} - -func (db *DB) GetAttachmentFS() fs.FS { - return os.DirFS(db.attDir) -} - -func (db *DB) GetAttachment(uuid string) (*domain.Attachment, error) { - var att domain.Attachment - err := db.db.One("UUID", uuid, att) - if err != nil { - return nil, err - } - - return &att, nil -} - -func (db *DB) GetAttachmentData(att *domain.Attachment) ([]byte, error) { - data, err := os.ReadFile(db.getAttachmentPath(att)) - if err != nil { - return nil, err - } - - return data, nil -} - -func (db *DB) GetAttachments(msg *domain.Message) ([]domain.Attachment, error) { - var atts []domain.Attachment - err := db.db.Select(q.Eq("MessageUUID", msg.UUID)).Find(&atts) - if err != nil { - if err == storm.ErrNotFound { - return []domain.Attachment{}, nil - } - return nil, err - } - - return atts, nil -} diff --git a/right/database/message.go b/right/database/message.go new file mode 100644 index 00000000..291c3430 --- /dev/null +++ b/right/database/message.go @@ -0,0 +1,101 @@ +package database + +import ( + "log" + "os" + + "github.com/ItsNotGoodName/smtpbridge/domain" + "github.com/asdine/storm" + "github.com/asdine/storm/q" +) + +func (db *DB) CreateMessage(msg *domain.Message) error { + return db.db.Save(msg) +} + +func (db *DB) GetMessage(uuid string) (*domain.Message, error) { + var msg domain.Message + err := db.db.One("UUID", uuid, msg) + if err != nil { + return nil, err + } + + return &msg, nil +} + +func (db *DB) UpdateMessage(msg *domain.Message, updateFN func(msg *domain.Message) (*domain.Message, error)) error { + tx, err := db.db.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + var existingMSG domain.Message + if err := tx.One("UUID", msg.UUID, &existingMSG); err != nil { + return err + } + + updatedMSG, err := updateFN(&existingMSG) + if err != nil { + return err + } + + err = tx.Save(updatedMSG) + if err != nil { + return err + } + + return tx.Commit() +} + +func (db *DB) GetMessages(limit, offset int) ([]domain.Message, error) { + var msgs []domain.Message + err := db.db.Select().OrderBy("CreatedAt").Limit(limit).Skip(offset).Reverse().Find(&msgs) + if err != nil && err != storm.ErrNotFound { + return nil, err + } + + return msgs, nil +} + +func (db *DB) DeleteMessage(msg *domain.Message) error { + tx, err := db.db.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + query := tx.Select(q.Eq("MessageUUID", msg.UUID)) + + // List attachments + var atts []domain.Attachment + err = query.Find(&atts) + if err != storm.ErrNotFound { + if err != nil { + return err + } + + // Delete attachments + err = query.Delete(&domain.Attachment{}) + if err != nil { + return err + } + } + + // Delete message + if err := tx.DeleteStruct(msg); err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return err + } + + for _, att := range atts { + if err := os.Remove(db.getAttachmentPath(&att)); err != nil { + log.Println("database.DB.DeleteMessage: could not delete attachment data:", err) + } + } + + return nil +}