fake-smtp/internal/query/get_inbox.go

240 lines
4.1 KiB
Go

package query
import (
"context"
"strings"
"time"
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
"forge.cadoles.com/wpetit/fake-smtp/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 InboxSearch struct {
To string
From string
Body string
Subject string
Headers map[string]string
After time.Time
Before time.Time
}
type GetInboxRequest struct {
OrderBy string
Limit int
Skip int
Reverse bool
Search *InboxSearch
}
type InboxData struct {
Emails []*model.Email
}
func HandleGetInbox(ctx context.Context, qry cqrs.Query) (interface{}, error) {
req, ok := qry.Request().(*GetInboxRequest)
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")
}
emails := make([]*model.Email, 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("HTML", req.Search.Body),
q.Re("Text", req.Search.Body),
))
}
if req.Search.Subject != "" {
matchers = append(matchers, q.Re("Subject", req.Search.Subject))
}
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(&emails); err != nil {
if err == storm.ErrNotFound {
return &InboxData{emails}, nil
}
return nil, errors.Wrap(err, "could not retrieve emails")
}
if req.Search == nil {
return &InboxData{emails}, nil
}
filtered := filterEmails(emails, req.Search)
return &InboxData{filtered}, nil
}
var matchers = []emailMatcherFunc{
matchTo,
matchFrom,
matchBefore,
matchAfter,
matchHeaders,
}
type emailMatcherFunc func(*model.Email, *InboxSearch) bool
func matchTo(eml *model.Email, search *InboxSearch) bool {
if search.To == "" {
return true
}
found := false
for _, addr := range eml.To {
if strings.Contains(addr.Name, search.To) || strings.Contains(addr.Address, search.To) {
found = true
break
}
}
return found
}
func matchFrom(eml *model.Email, search *InboxSearch) bool {
if search.From == "" {
return true
}
found := false
for _, addr := range eml.From {
if strings.Contains(addr.Name, search.From) || strings.Contains(addr.Address, search.From) {
found = true
break
}
}
return found
}
func matchAfter(eml *model.Email, search *InboxSearch) bool {
if search.After.IsZero() {
return true
}
return eml.SentAt.After(search.After)
}
func matchBefore(eml *model.Email, search *InboxSearch) bool {
if search.Before.IsZero() {
return true
}
return eml.SentAt.Before(search.Before)
}
func matchHeaders(eml *model.Email, search *InboxSearch) bool {
if eml.Headers == nil {
return true
}
matches := true
for searchKey, searchValue := range search.Headers {
for headerKey, headerValues := range eml.Headers {
if searchKey != headerKey {
continue
}
matchesHeader := true
for _, hv := range headerValues {
if !strings.Contains(hv, searchValue) {
matchesHeader = false
break
}
}
if !matchesHeader {
matches = false
break
}
}
if !matches {
break
}
}
return matches
}
func and(matchers ...emailMatcherFunc) emailMatcherFunc {
return func(eml *model.Email, search *InboxSearch) bool {
for _, match := range matchers {
if !match(eml, search) {
return false
}
}
return true
}
}
func filterEmails(emails []*model.Email, search *InboxSearch) []*model.Email {
filtered := make([]*model.Email, 0)
match := and(matchers...)
for _, eml := range emails {
if !match(eml, search) {
continue
}
filtered = append(filtered, eml)
}
return filtered
}