mirror of
https://github.com/axllent/mailpit.git
synced 2025-04-19 12:12:26 +02:00
Merge branch 'release/0.0.4'
This commit is contained in:
commit
e363ece5a0
48
.chglog/CHANGELOG.tpl.md
Executable file
48
.chglog/CHANGELOG.tpl.md
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
Notable changes to Mailpit will be documented in this file.
|
||||||
|
|
||||||
|
|
||||||
|
{{ if .Versions -}}
|
||||||
|
{{ if .Unreleased.CommitGroups -}}
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
{{ if .Unreleased.CommitGroups -}}
|
||||||
|
{{ range .Unreleased.CommitGroups -}}
|
||||||
|
### {{ .Title }}
|
||||||
|
{{ range .Commits -}}
|
||||||
|
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{ range .Versions }}
|
||||||
|
{{- if .CommitGroups -}}
|
||||||
|
## {{ .Tag.Name }}
|
||||||
|
|
||||||
|
{{ range .CommitGroups -}}
|
||||||
|
### {{ .Title }}
|
||||||
|
{{ range .Commits -}}
|
||||||
|
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{- if .MergeCommits -}}
|
||||||
|
### Pull Requests
|
||||||
|
{{ range .MergeCommits -}}
|
||||||
|
- {{ .Header }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{- if .NoteGroups -}}
|
||||||
|
{{ range .NoteGroups -}}
|
||||||
|
### {{ .Title }}
|
||||||
|
{{ range .Notes }}
|
||||||
|
{{ .Body }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
12
.chglog/RELEASE.tpl.md
Executable file
12
.chglog/RELEASE.tpl.md
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
{{ if .Versions -}}
|
||||||
|
{{ range .Versions }}
|
||||||
|
{{- if .CommitGroups -}}
|
||||||
|
{{ range .CommitGroups -}}
|
||||||
|
### {{ .Title }}
|
||||||
|
{{ range .Commits -}}
|
||||||
|
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
28
.chglog/config.yml
Executable file
28
.chglog/config.yml
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
style: github
|
||||||
|
template: CHANGELOG.tpl.md
|
||||||
|
info:
|
||||||
|
title: CHANGELOG
|
||||||
|
repository_url: https://github.com/axllent/mailpit
|
||||||
|
options:
|
||||||
|
commits:
|
||||||
|
# filters:
|
||||||
|
# Type:
|
||||||
|
# - feat
|
||||||
|
# - fix
|
||||||
|
# - perf
|
||||||
|
# - refactor
|
||||||
|
commit_groups:
|
||||||
|
title_maps:
|
||||||
|
feature: Feature
|
||||||
|
fix: Fix
|
||||||
|
# perf: Performance Improvements
|
||||||
|
# refactor: Code Refactoring
|
||||||
|
header:
|
||||||
|
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
|
||||||
|
pattern_maps:
|
||||||
|
- Type
|
||||||
|
- Scope
|
||||||
|
- Subject
|
||||||
|
notes:
|
||||||
|
keywords:
|
||||||
|
- BREAKING CHANGE
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -16,6 +16,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
- run: go test ./storage -v
|
- run: go test ./storage -v
|
||||||
- run: go test ./storage -bench=.
|
- run: go test ./storage -bench=.
|
||||||
|
|
||||||
|
33
CHANGELOG.md
33
CHANGELOG.md
@ -1,15 +1,34 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.3]
|
Notable changes to Mailpit will be documented in this file.
|
||||||
|
|
||||||
- Bugfix: Update to clover-v2.0.0-alpha.2 to fix sorting
|
|
||||||
|
|
||||||
|
|
||||||
## [0.0.2]
|
## 0.0.4
|
||||||
|
|
||||||
- Unread message statistics & updates
|
### Bugfix
|
||||||
|
- Update to clover-v2.0.0-alpha.2 to fix sorting
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
- Add search tests
|
||||||
|
|
||||||
|
### UI
|
||||||
|
- Add date to console log
|
||||||
|
- Add space in To fields
|
||||||
|
- Cater for messages without From email address
|
||||||
|
- Minor UI & logging changes
|
||||||
|
- Add space in To fields
|
||||||
|
- cater for messages without From email address
|
||||||
|
|
||||||
|
|
||||||
## [0.0.1-beta]
|
## 0.0.3
|
||||||
|
|
||||||
|
### Bugfix
|
||||||
|
- Update to clover-v2.0.0-alpha.2 to fix sorting
|
||||||
|
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
- Unread statistics
|
||||||
|
|
||||||
|
|
||||||
- First release
|
|
||||||
|
@ -28,7 +28,7 @@ func Log() *logrus.Logger {
|
|||||||
log.Out = os.Stdout
|
log.Out = os.Stdout
|
||||||
log.SetFormatter(&logrus.TextFormatter{
|
log.SetFormatter(&logrus.TextFormatter{
|
||||||
FullTimestamp: true,
|
FullTimestamp: true,
|
||||||
TimestampFormat: "15:04:05",
|
TimestampFormat: "2006/01/02 15:04:05",
|
||||||
ForceColors: true,
|
ForceColors: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -408,7 +408,7 @@ export default {
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
This will permanently delete all messages.
|
This will permanently delete {{ formatNumber(total) }} message<span v-if="total > 1">s</span>.
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
@ -418,5 +418,4 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
@ -80,7 +80,8 @@ export default {
|
|||||||
<th>From</th>
|
<th>From</th>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="message.From">
|
<span v-if="message.From">
|
||||||
{{ message.From.Name + " <" + message.From.Address +">" }}
|
<span v-if="message.From.Name">{{ message.From.Name + " " }}</span>
|
||||||
|
<span v-if="message.From.Address"><{{ message.From.Address }}></span>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
[ Unknown ]
|
[ Unknown ]
|
||||||
|
@ -185,6 +185,8 @@ func Store(mailbox string, b []byte) (string, error) {
|
|||||||
fromData := addressToSlice(env, "From")
|
fromData := addressToSlice(env, "From")
|
||||||
if len(fromData) > 0 {
|
if len(fromData) > 0 {
|
||||||
from = fromData[0]
|
from = fromData[0]
|
||||||
|
} else if env.GetHeader("From") != "" {
|
||||||
|
from = &mail.Address{Name: env.GetHeader("From")}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := CloverStore{
|
obj := CloverStore{
|
||||||
@ -224,7 +226,7 @@ func Store(mailbox string, b []byte) (string, error) {
|
|||||||
|
|
||||||
count++
|
count++
|
||||||
if count%100 == 0 {
|
if count%100 == 0 {
|
||||||
logger.Log().Infof("%d messages added (%s per 100)", count, time.Since(per100start))
|
logger.Log().Infof("100 messages added in %s", time.Since(per100start))
|
||||||
|
|
||||||
per100start = time.Now()
|
per100start = time.Now()
|
||||||
}
|
}
|
||||||
@ -311,7 +313,7 @@ func List(mailbox string, start, limit int) ([]data.Summary, error) {
|
|||||||
|
|
||||||
// Search returns a summary of items mathing a search. It searched the SearchText field.
|
// Search returns a summary of items mathing a search. It searched the SearchText field.
|
||||||
func Search(mailbox, search string, start, limit int) ([]data.Summary, error) {
|
func Search(mailbox, search string, start, limit int) ([]data.Summary, error) {
|
||||||
sq := fmt.Sprintf("(?i)%s", regexp.QuoteMeta(search))
|
sq := fmt.Sprintf("(?i)%s", cleanString(regexp.QuoteMeta(search)))
|
||||||
q, err := db.FindAll(clover.NewQuery(mailbox).
|
q, err := db.FindAll(clover.NewQuery(mailbox).
|
||||||
Skip(start).
|
Skip(start).
|
||||||
Limit(limit).
|
Limit(limit).
|
||||||
@ -393,6 +395,8 @@ func GetMessage(mailbox, id string) (*data.Message, error) {
|
|||||||
fromData := addressToSlice(env, "From")
|
fromData := addressToSlice(env, "From")
|
||||||
if len(fromData) > 0 {
|
if len(fromData) > 0 {
|
||||||
from = fromData[0]
|
from = fromData[0]
|
||||||
|
} else if env.GetHeader("From") != "" {
|
||||||
|
from = &mail.Address{Name: env.GetHeader("From")}
|
||||||
}
|
}
|
||||||
|
|
||||||
date, err := env.Date()
|
date, err := env.Date()
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/axllent/mailpit/config"
|
"github.com/axllent/mailpit/config"
|
||||||
|
"github.com/jhillyerd/enmime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -123,6 +126,78 @@ func TestRetrieveMimeEmail(t *testing.T) {
|
|||||||
assertEqual(t, len(attachmentData.Content), msg.Attachments[0].Size, "attachment size does not match")
|
assertEqual(t, len(attachmentData.Content), msg.Attachments[0].Size, "attachment size does not match")
|
||||||
inlineData, err := GetAttachmentPart(DefaultMailbox, id, msg.Inline[0].PartID)
|
inlineData, err := GetAttachmentPart(DefaultMailbox, id, msg.Inline[0].PartID)
|
||||||
assertEqual(t, len(inlineData.Content), msg.Inline[0].Size, "inline attachment size does not match")
|
assertEqual(t, len(inlineData.Content), msg.Inline[0].Size, "inline attachment size does not match")
|
||||||
|
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearch(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
msg := enmime.Builder().
|
||||||
|
From(fmt.Sprintf("From %d", i), fmt.Sprintf("from-%d@example.com", i)).
|
||||||
|
Subject(fmt.Sprintf("Subject line %d end", i)).
|
||||||
|
Text([]byte(fmt.Sprintf("This is the email body %d <jdsauk;dwqmdqw;>.", i))).
|
||||||
|
To(fmt.Sprintf("To %d", i), fmt.Sprintf("to-%d@example.com", i))
|
||||||
|
|
||||||
|
env, err := msg.Build()
|
||||||
|
if err != nil {
|
||||||
|
t.Log("error ", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err := env.Encode(buf); err != nil {
|
||||||
|
t.Log("error ", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Store(DefaultMailbox, buf.Bytes()); err != nil {
|
||||||
|
t.Log("error ", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < 101; i++ {
|
||||||
|
// search a random something that will return a single result
|
||||||
|
searchIndx := rand.Intn(4) + 1
|
||||||
|
var search string
|
||||||
|
switch searchIndx {
|
||||||
|
case 1:
|
||||||
|
search = fmt.Sprintf("from-%d@example.com", i)
|
||||||
|
case 2:
|
||||||
|
search = fmt.Sprintf("to-%d@example.com", i)
|
||||||
|
case 3:
|
||||||
|
search = fmt.Sprintf("Subject line %d end", i)
|
||||||
|
default:
|
||||||
|
search = fmt.Sprintf("the email body %d jdsauk dwqmdqw", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries, err := Search(DefaultMailbox, search, 0, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("error ", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, len(summaries), 1, "1 search result expected")
|
||||||
|
|
||||||
|
assertEqual(t, summaries[0].From.Name, fmt.Sprintf("From %d", i), "\"From\" name does not match")
|
||||||
|
assertEqual(t, summaries[0].From.Address, fmt.Sprintf("from-%d@example.com", i), "\"From\" address does not match")
|
||||||
|
assertEqual(t, summaries[0].To[0].Name, fmt.Sprintf("To %d", i), "\"To\" name does not match")
|
||||||
|
assertEqual(t, summaries[0].To[0].Address, fmt.Sprintf("to-%d@example.com", i), "\"To\" address does not match")
|
||||||
|
assertEqual(t, summaries[0].Subject, fmt.Sprintf("Subject line %d end", i), "\"Subject\" does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// search something that will return 200 rsults
|
||||||
|
summaries, err := Search(DefaultMailbox, "This is the email body", 0, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("error ", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assertEqual(t, len(summaries), 200, "200 search results expected")
|
||||||
|
|
||||||
|
db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkImportText(b *testing.B) {
|
func BenchmarkImportText(b *testing.B) {
|
||||||
|
@ -42,17 +42,20 @@ func createSearchText(env *enmime.Envelope) string {
|
|||||||
b.WriteString(a.FileName + " ")
|
b.WriteString(a.FileName + " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
d := b.String()
|
d := cleanString(b.String())
|
||||||
|
|
||||||
// remove/replace new lines
|
|
||||||
re := regexp.MustCompile(`(\r?\n|\t|>|<|"|:|\,|;)`)
|
|
||||||
d = re.ReplaceAllString(d, " ")
|
|
||||||
// remove duplicate whitespace and trim
|
|
||||||
d = strings.ToLower(strings.Join(strings.Fields(strings.TrimSpace(d)), " "))
|
|
||||||
|
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanString removed unwanted characters from stored search text and search queries
|
||||||
|
func cleanString(str string) string {
|
||||||
|
// remove/replace new lines
|
||||||
|
re := regexp.MustCompile(`(\r?\n|\t|>|<|"|:|\,|;)`)
|
||||||
|
str = re.ReplaceAllString(str, " ")
|
||||||
|
// remove duplicate whitespace and trim
|
||||||
|
return strings.ToLower(strings.Join(strings.Fields(strings.TrimSpace(str)), " "))
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-prune runs every 5 minutes to automatically delete oldest messages
|
// Auto-prune runs every 5 minutes to automatically delete oldest messages
|
||||||
// if total is greater than the threshold
|
// if total is greater than the threshold
|
||||||
func pruneCron() {
|
func pruneCron() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user