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 }