Skip to content

Commit

Permalink
Merge branch 'feature/sidebar-email-list' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
axllent committed Aug 4, 2024
2 parents 05da2a7 + 6baf13b commit 9040e04
Show file tree
Hide file tree
Showing 23 changed files with 672 additions and 186 deletions.
36 changes: 32 additions & 4 deletions internal/storage/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func Store(body *[]byte) (string, error) {

// List returns a subset of messages from the mailbox,
// sorted latest to oldest
func List(start, limit int) ([]MessageSummary, error) {
func List(start int, beforeTS int64, limit int) ([]MessageSummary, error) {
results := []MessageSummary{}
tsStart := time.Now()

Expand All @@ -175,6 +175,10 @@ func List(start, limit int) ([]MessageSummary, error) {
Limit(limit).
Offset(start)

if beforeTS > 0 {
q = q.Where("Created < ?", beforeTS)
}

if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
var created float64
var id string
Expand Down Expand Up @@ -428,12 +432,12 @@ func LatestID(r *http.Request) (string, error) {

search := strings.TrimSpace(r.URL.Query().Get("query"))
if search != "" {
messages, _, err = Search(search, r.URL.Query().Get("tz"), 0, 1)
messages, _, err = Search(search, r.URL.Query().Get("tz"), 0, 0, 1)
if err != nil {
return "", err
}
} else {
messages, err = List(0, 1)
messages, err = List(0, 0, 1)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -462,6 +466,13 @@ func MarkRead(id string) error {

BroadcastMailboxStats()

d := struct {
ID string
Read bool
}{ID: id, Read: true}

websockets.Broadcast("update", d)

return err
}

Expand Down Expand Up @@ -534,6 +545,13 @@ func MarkUnread(id string) error {

BroadcastMailboxStats()

d := struct {
ID string
Read bool
}{ID: id, Read: false}

websockets.Broadcast("update", d)

return err
}

Expand Down Expand Up @@ -621,6 +639,15 @@ func DeleteMessages(ids []string) error {

BroadcastMailboxStats()

// broadcast individual message deletions
for _, id := range toDelete {
d := struct {
ID string
}{ID: id}

websockets.Broadcast("delete", d)
}

return nil
}

Expand Down Expand Up @@ -671,8 +698,9 @@ func DeleteAllMessages() error {

logMessagesDeleted(total)

websockets.Broadcast("prune", nil)
BroadcastMailboxStats()

websockets.Broadcast("truncate", nil)

return err
}
2 changes: 1 addition & 1 deletion internal/storage/messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TestMessageSummary(t *testing.T) {
t.Fail()
}

summaries, err := List(0, 1)
summaries, err := List(0, 0, 1)
if err != nil {
t.Log("error ", err)
t.Fail()
Expand Down
7 changes: 6 additions & 1 deletion internal/storage/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
// The search is broken up by segments (exact phrases can be quoted), and interprets specific terms such as:
// is:read, is:unread, has:attachment, to:<term>, from:<term> & subject:<term>
// Negative searches also also included by prefixing the search term with a `-` or `!`
func Search(search, timezone string, start, limit int) ([]MessageSummary, int, error) {
func Search(search, timezone string, start int, beforeTS int64, limit int) ([]MessageSummary, int, error) {
results := []MessageSummary{}
allResults := []MessageSummary{}
tsStart := time.Now()
Expand All @@ -28,6 +28,11 @@ func Search(search, timezone string, start, limit int) ([]MessageSummary, int, e
}

q := searchQueryBuilder(search, timezone)

if beforeTS > 0 {
q = q.Where(`Created < ?`, beforeTS)
}

var err error

if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
Expand Down
12 changes: 6 additions & 6 deletions internal/storage/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestSearch(t *testing.T) {

search := uniqueSearches[searchIdx]

summaries, _, err := Search(search, "", 0, 100)
summaries, _, err := Search(search, "", 0, 0, 100)
if err != nil {
t.Log("error ", err)
t.Fail()
Expand All @@ -85,7 +85,7 @@ func TestSearch(t *testing.T) {
}

// search something that will return 200 results
summaries, _, err := Search("This is the email body", "", 0, testRuns)
summaries, _, err := Search("This is the email body", "", 0, 0, testRuns)
if err != nil {
t.Log("error ", err)
t.Fail()
Expand All @@ -109,7 +109,7 @@ func TestSearchDelete100(t *testing.T) {
}
}

_, total, err := Search("from:[email protected]", "", 0, 100)
_, total, err := Search("from:[email protected]", "", 0, 0, 100)
if err != nil {
t.Log("error ", err)
t.Fail()
Expand All @@ -122,7 +122,7 @@ func TestSearchDelete100(t *testing.T) {
t.Fail()
}

_, total, err = Search("from:[email protected]", "", 0, 100)
_, total, err = Search("from:[email protected]", "", 0, 0, 100)
if err != nil {
t.Log("error ", err)
t.Fail()
Expand All @@ -143,7 +143,7 @@ func TestSearchDelete1100(t *testing.T) {
}
}

_, total, err := Search("from:[email protected]", "", 0, 100)
_, total, err := Search("from:[email protected]", "", 0, 0, 100)
if err != nil {
t.Log("error ", err)
t.Fail()
Expand All @@ -156,7 +156,7 @@ func TestSearchDelete1100(t *testing.T) {
t.Fail()
}

_, total, err = Search("from:[email protected]", "", 0, 100)
_, total, err = Search("from:[email protected]", "", 0, 0, 100)
if err != nil {
t.Log("error ", err)
t.Fail()
Expand Down
21 changes: 14 additions & 7 deletions internal/storage/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/tools"
"github.com/axllent/mailpit/server/websockets"
"github.com/leporo/sqlf"
)

Expand Down Expand Up @@ -40,7 +41,7 @@ func SetMessageTags(id string, tags []string) ([]string, error) {
continue
}

name, err := AddMessageTag(id, t)
name, err := addMessageTag(id, t)
if err != nil {
return []string{}, err
}
Expand All @@ -53,18 +54,25 @@ func SetMessageTags(id string, tags []string) ([]string, error) {

for _, t := range currentTags {
if !tools.InArray(t, applyTags) {
if err := DeleteMessageTag(id, t); err != nil {
if err := deleteMessageTag(id, t); err != nil {
return []string{}, err
}
}
}
}

d := struct {
ID string
Tags []string
}{ID: id, Tags: applyTags}

websockets.Broadcast("update", d)

return tagNames, nil
}

// AddMessageTag adds a tag to a message
func AddMessageTag(id, name string) (string, error) {
func addMessageTag(id, name string) (string, error) {
// prevent two identical tags being added at the same time
addTagMutex.Lock()

Expand Down Expand Up @@ -114,11 +122,11 @@ func AddMessageTag(id, name string) (string, error) {
addTagMutex.Unlock()

// add tag to the message
return AddMessageTag(id, name)
return addMessageTag(id, name)
}

// DeleteMessageTag deleted a tag from a message
func DeleteMessageTag(id, name string) error {
// DeleteMessageTag deletes a tag from a message
func deleteMessageTag(id, name string) error {
if _, err := sqlf.DeleteFrom(tenant("message_tags")).
Where(tenant("message_tags.ID")+" = ?", id).
Where(tenant("message_tags.Key")+` IN (SELECT Key FROM `+tenant("message_tags")+` LEFT JOIN tags ON `+tenant("TagID")+"="+tenant("tags.ID")+` WHERE Name = ?)`, name).
Expand Down Expand Up @@ -173,7 +181,6 @@ func GetAllTagsCount() map[string]int64 {
OrderBy("Name").
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
tags[name] = total
// tags = append(tags, name)
}); err != nil {
logger.Log().Errorf("[db] %s", err.Error())
}
Expand Down
2 changes: 1 addition & 1 deletion internal/storage/tags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestTags(t *testing.T) {
assertEqual(t, strings.Join(newTags, "|"), strings.Join(returnedTags, "|"), "Message tags do not match")

// remove first tag
if err := DeleteMessageTag(id, newTags[0]); err != nil {
if err := deleteMessageTag(id, newTags[0]); err != nil {
t.Log("error ", err)
t.Fail()
}
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dayjs": "^1.11.10",
"dompurify": "^3.1.6",
"ical.js": "^2.0.1",
"mitt": "^3.0.1",
"modern-screenshot": "^4.4.30",
"prismjs": "^1.29.0",
"rapidoc": "^9.3.4",
Expand Down
38 changes: 30 additions & 8 deletions server/apiv1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import (
"strconv"
"strings"

"github.com/araddon/dateparse"
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/internal/htmlcheck"
"github.com/axllent/mailpit/internal/linkcheck"
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/spamassassin"
"github.com/axllent/mailpit/internal/storage"
"github.com/gorilla/mux"
"github.com/jhillyerd/enmime"
)

// GetMessages returns a paginated list of messages as JSON
Expand Down Expand Up @@ -48,9 +51,9 @@ func GetMessages(w http.ResponseWriter, r *http.Request) {
// Responses:
// 200: MessagesSummaryResponse
// default: ErrorResponse
start, limit := getStartLimit(r)
start, beforeTS, limit := getStartLimit(r)

messages, err := storage.List(start, limit)
messages, err := storage.List(start, beforeTS, limit)
if err != nil {
httpError(w, err.Error())
return
Expand Down Expand Up @@ -120,9 +123,9 @@ func Search(w http.ResponseWriter, r *http.Request) {
return
}

start, limit := getStartLimit(r)
start, beforeTS, limit := getStartLimit(r)

messages, results, err := storage.Search(search, r.URL.Query().Get("tz"), start, limit)
messages, results, err := storage.Search(search, r.URL.Query().Get("tz"), start, beforeTS, limit)
if err != nil {
httpError(w, err.Error())
return
Expand Down Expand Up @@ -442,7 +445,7 @@ func DeleteMessages(w http.ResponseWriter, r *http.Request) {
}
}

w.Header().Add("Content-Type", "application/plain")
w.Header().Add("Content-Type", "text/plain")
_, _ = w.Write([]byte("ok"))
}

Expand Down Expand Up @@ -548,12 +551,20 @@ func HTMLCheck(w http.ResponseWriter, r *http.Request) {
}
}

msg, err := storage.GetMessage(id)
raw, err := storage.GetMessageRaw(id)
if err != nil {
fourOFour(w)
return
}

e := bytes.NewReader(raw)

msg, err := enmime.ReadEnvelope(e)
if err != nil {
httpError(w, err.Error())
return
}

if msg.HTML == "" {
httpError(w, "message does not contain HTML")
return
Expand Down Expand Up @@ -704,9 +715,10 @@ func httpJSONError(w http.ResponseWriter, msg string) {
}

// Get the start and limit based on query params. Defaults to 0, 50
func getStartLimit(req *http.Request) (start int, limit int) {
func getStartLimit(req *http.Request) (start int, beforeTS int64, limit int) {
start = 0
limit = 50
beforeTS = 0 // timestamp

s := req.URL.Query().Get("start")
if n, err := strconv.Atoi(s); err == nil && n > 0 {
Expand All @@ -718,7 +730,17 @@ func getStartLimit(req *http.Request) (start int, limit int) {
limit = n
}

return start, limit
b := req.URL.Query().Get("before")
if b != "" {
t, err := dateparse.ParseLocal(b)
if err != nil {
logger.Log().Warnf("ignoring invalid before: date \"%s\"", b)
} else {
beforeTS = t.UnixMilli()
}
}

return start, beforeTS, limit
}

// GetOptions returns a blank response
Expand Down
4 changes: 2 additions & 2 deletions server/handlers/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ func RedirectToLatestMessage(w http.ResponseWriter, r *http.Request) {

search := strings.TrimSpace(r.URL.Query().Get("query"))
if search != "" {
messages, _, err = storage.Search(search, "", 0, 1)
messages, _, err = storage.Search(search, "", 0, 0, 1)
if err != nil {
httpError(w, err.Error())
return
}
} else {
messages, err = storage.List(0, 1)
messages, err = storage.List(0, 0, 1)
if err != nil {
httpError(w, err.Error())
return
Expand Down
Loading

0 comments on commit 9040e04

Please sign in to comment.